You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python 3.8 datetime夏令时转换是否存在Bug?

夏令时切换时区下的当日起始时间转换问题

问题场景

尝试将带Europe/Sofia时区信息的datetime转换为该时区当日起始时间(午夜),再转回UTC时出现异常。原测试代码及运行输出如下:

原测试代码

#!/usr/bin/env python

import sys
import pytz
from datetime import datetime, timedelta


def main():
    tz = pytz.timezone('Europe/Sofia')
    dt = pytz.utc.localize(datetime(year=2019, month=3, day=31, hour=14, minute=45))
    print(f'We start with the datetime in UTC: {dt}')
    dt = dt.astimezone(tz)
    print(f'Then convert it to Europe/Sofia timezone: {dt}')

    result = dt.replace(hour=0, minute=0, second=0, microsecond=0)
    print(f'Then make it the start of the day midnight: {result}')
    result = result.astimezone(pytz.utc)
    print(f'Then convert it back to UTC (the final result we want): {result}')
    print(f'But notice the time is wrong now when converted back to Europe/Sofia: {result.astimezone(tz)}')

    result = dt - timedelta(hours=dt.hour, minutes=dt.minute, seconds=dt.second, microseconds=dt.microsecond)
    print(f'Using a timedelta instead of replace has the same result: {result}')
    print(f'The time is still wrong when converted back to Europe/Sofia: {result.astimezone(pytz.utc).astimezone(tz)}')

    return 0


if __name__ == '__main__':
    sys.exit(main())

运行输出

We start with the datetime in UTC: 2019-03-31 14:45:00+00:00
Then convert it to Europe/Sofia timezone: 2019-03-31 17:45:00+03:00
Then make it the start of the day midnight: 2019-03-31 00:00:00+03:00
Then convert it back to UTC (the final result we want): 2019-03-30 21:00:00+00:00
But notice the time is wrong now when converted back to Europe/Sofia: 2019-03-30 23:00:00+02:00
Using a timedelta instead of replace has the same result: 2019-03-31 00:00:00+03:00
The time is still wrong when converted back to Europe/Sofia: 2019-03-30 23:00:00+02:00

问题核心:2019年3月31日凌晨3点Europe/Sofia时区有夏令时切换(偏移从+2变为+3),预期当日午夜的偏移应为+2,但用replace()或timedelta得到的是+3偏移,导致转回时区后时间错误。

原因分析

这不是Python的Bug,是pytz处理带时区datetime的特性:

  • 调用replace()或通过timedelta计算时,仅修改了时间字段,没有重新计算时区偏移。原dt的偏移是+3(夏令时切换后的时间),修改后得到的2019-03-31 00:00:00仍沿用+3偏移,但该时间点在该时区实际还未进入夏令时,偏移应为+2,属于无效时间,转回UTC再转回来时会被自动修正为有效时间(2019-03-30 23:00+02)。

解决方案

要正确获取夏令时切换当日的午夜时间,需使用pytz的localize()方法,或借助更友好的时区处理库:

方案1:使用pytz.localize()方法

提取日期部分后,用时区本地化当日午夜,is_dst=None会强制校验时间有效性:

result = tz.localize(datetime(dt.year, dt.month, dt.day), is_dst=None)

此方法会自动匹配该日期午夜对应的正确时区偏移,得到2019-03-31 00:00:00+02:00。

方案2:使用python-dateutil库(更简洁)

dateutil.tz会自动处理夏令时切换的偏移问题,无需手动调用localize():

from dateutil import tz

tz = tz.gettz('Europe/Sofia')
dt = datetime(year=2019, month=3, day=31, hour=14, minute=45, tzinfo=tz.UTC)
dt = dt.astimezone(tz)
result = dt.replace(hour=0, minute=0, second=0, microsecond=0)

修改后的测试代码示例

#!/usr/bin/env python

import sys
import pytz
from datetime import datetime, timedelta


def main():
    tz = pytz.timezone('Europe/Sofia')
    dt = pytz.utc.localize(datetime(year=2019, month=3, day=31, hour=14, minute=45))
    print(f'We start with the datetime in UTC: {dt}')
    dt = dt.astimezone(tz)
    print(f'Then convert it to Europe/Sofia timezone: {dt}')

    # 正确方式:用localize构建当日午夜
    result = tz.localize(datetime(dt.year, dt.month, dt.day), is_dst=None)
    print(f'Correct start of the day midnight: {result}')
    result = result.astimezone(pytz.utc)
    print(f'Convert back to UTC: {result}')
    print(f'Convert back to Europe/Sofia (now correct): {result.astimezone(tz)}')

    return 0


if __name__ == '__main__':
    sys.exit(main())

修改后运行输出

We start with the datetime in UTC: 2019-03-31 14:45:00+00:00
Then convert it to Europe/Sofia timezone: 2019-03-31 17:45:00+03:00
Correct start of the day midnight: 2019-03-31 00:00:00+02:00
Convert back to UTC: 2019-03-30 22:00:00+00:00
Convert back to Europe/Sofia (now correct): 2019-03-31 00:00:00+02:00

内容的提问来源于stack exchange,提问作者Alan Hamlett

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 02:17:35