Python ISO 周编号年份:日期还是十二月,为什么已经属于下一年的第一周

前天 3阅读

周报用日期的 year 加上 isocalendar 得到的周号作为分组键,到了年末突然把同一周拆开。2024年12月30日的公历年份是2024,它的ISO周编号年份却是2025。把两套年份与周号混拼,得到的标签可能指向完全不同的一周。

ISO周从星期一开始,每个周编号年有52或53个完整周,第一周包含该公历年的第一个星期四。下面只使用 date,保存为 demo.py 后运行 python demo.py,不依赖机器的当前日期或系统时区。

Python ISO 周编号年份:日期还是十二月,为什么已经属于下一年的第一周

AI模型生成概念示意:七天组成的完整周跨过两张年度日历的边界,周标签仍需保持统一;不是日历应用截图。

from datetime import date

samples = [date(2024, 12, 30), date(2025, 1, 1), date(2021, 1, 1)]
expected = [(2025, 1, 1), (2025, 1, 3), (2020, 53, 5)]

for day, wanted in zip(samples, expected, strict=True):
    iso = day.isocalendar()
    print(day.isoformat(), "calendar year:", day.year, "ISO:", tuple(iso))
    assert tuple(iso) == wanted
    assert date.fromisocalendar(*iso) == day

day = samples[0]
iso = day.isocalendar()
wrong_key = (day.year, iso.week)
right_key = (iso.year, iso.week)
print("mixed key:", wrong_key)
print("ISO key:", right_key)
assert wrong_key != right_key
assert samples[1].isocalendar()[:2] == right_key

monday = date.fromisocalendar(iso.year, iso.week, 1)
print("week starts:", monday.isoformat())
assert monday == date(2024, 12, 30)

try:
    date.fromisocalendar(2021, 53, 1)
except ValueError:
    print("2021 week 53: rejected")
else:
    raise AssertionError("invalid ISO week accepted")

跨年的同一周,应共享同一组键

前三行的ISO三元组依次是 (2025, 1, 1)、(2025, 1, 3) 和 (2020, 53, 5)。它们表示周编号年份、周号和星期,星期一为一,星期日为七。2024年12月30日与2025年1月1日同属2025年第1周;2021年1月1日则仍在2020年第53周。

mixed key: (2024, 1) 是把公历年份硬拼到ISO周号上所得的错误结果。ISO key: (2025, 1) 才是这次周报需要的标签。如果已有数据库混用了两者,应从原始日期重新计算,不能只按标签里的年份统一加一,因为并非所有年末日期都跨入下一周编号年。

用完整规则做往返验证

每个样本都通过 fromisocalendar 往返还原到原始日期。这能确认三个字段属于同一套规则。week starts: 2024-12-30 进一步给出这周星期一的实际公历日期,适合生成周报的日期范围,而不是假设2025年第1周一定从2025年1月开始。

最后 2021 week 53: rejected 提醒我们,不是每个周编号年都有第53周。不要把周号只校验成一到五十三就认为合法,年份与周号还要联合验证。让 fromisocalendar 负责组合合法性,可以避免手写大小年判断时漏掉边界。

周号不能单独作为永久分组键,不同年份都会有第1周。可以保存ISO年份与周号两个字段,也可以保存周一的公历日期;展示时再生成清楚的标签。若产品使用星期日开周、财务周或零售4-4-5日历,那是另一套约定,不能直接拿ISO结果代替。

本例输入已经是业务日期,所以没有时区换算。如果原始数据是时间戳,应先转换为业务约定时区,再取得日期并分周。不要在服务器时区和用户时区之间混用日期,否则即使ISO算法正确,跨午夜事件仍可能落入另一周。

资料核对日期:2026年10月2日。代码在本地 Python 3.12.14 实际运行并通过断言;结果对应文中固定输入。

参考资料

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。