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
相关产品推荐
相关产品推荐

