为何用ZoneInfo创建的datetime的tzinfo时区字符串不一致?
问题描述
为何使用zoneinfo.ZoneInfo时区创建的datetime.datetime实例,其tzinfo属性返回的时区字符串与创建时的不一致?
根据Python官方文档,ZoneInfo是datetime.tzinfo抽象基类的具体实现,可通过构造函数、replace方法或astimezone附加到datetime的tzinfo属性。
但当使用Australia/Melbourne创建datetime后,调用astimezone()获取的tzinfo为datetime.timezone(datetime.timedelta(seconds=39600), 'AEDT'),而AEDT无法被ZoneInfo识别,执行datetime.datetime.now(tz=zoneinfo.ZoneInfo("AEDT"))会抛出ZoneInfoNotFoundError。
请帮忙理解二者差异,并说明如何实现AEDT与Australia/Melbourne等不同时区字符串的互操作。
一、二者差异的核心原因
1. 时区标识类型本质不同
Australia/Melbourne是IANA标准时区区域名,对应一套完整的时区规则(包含夏令时切换、历史偏移量调整等逻辑),ZoneInfo依赖这类名称加载完整的时区配置。AEDT是时区缩写,仅表示某个特定时段的UTC偏移量(此处为+11小时),缩写不具备唯一性(不同地区可能复用同一缩写),也不包含任何时区规则,因此无法被ZoneInfo识别。
2. astimezone()的返回机制
对带有ZoneInfo的datetime实例调用astimezone()时:
- 若未指定目标时区,Python会将其转换为系统本地时区;如果本地当前处于夏令时(比如墨尔本的夏令时时段),返回的是固定偏移量类型的
datetime.timezone实例,而非ZoneInfo实例,此时它的tzname()会返回对应的夏令时缩写(如AEDT)。 - 这类
timezone实例仅记录当前的UTC偏移和缩写,不包含时区区域的完整规则,因此和原ZoneInfo实例本质上是两种不同的tzinfo实现。
二、实现AEDT与Australia/Melbourne的互操作
1. 从时区缩写(AEDT)转换到时区区域
由于缩写不唯一,无法直接通过缩写反向匹配唯一时区区域,但可以结合偏移量和业务场景筛选:
import zoneinfo from datetime import datetime, timezone, timedelta # AEDT对应UTC+11小时偏移 target_offset = timedelta(hours=11) matching_zones = [] # 遍历所有可用时区,筛选当前时段使用AEDT的时区 for zone_name in zoneinfo.available_timezones(): tz = zoneinfo.ZoneInfo(zone_name) now_in_zone = datetime.now(tz) if now_in_zone.tzname() == "AEDT" and now_in_zone.utcoffset() == target_offset: matching_zones.append(zone_name) # 输出结果会包含Australia/Melbourne等符合条件的时区 print(matching_zones)
注:如果业务场景明确是澳大利亚墨尔本,直接指定Australia/Melbourne即可,无需遍历筛选。
2. 从时区区域(Australia/Melbourne)获取对应缩写
直接通过带ZoneInfo的datetime实例调用tzname()方法,即可得到当前时段的时区缩写(夏令时返回AEDT,冬令时返回AEST):
from datetime import datetime import zoneinfo tz = zoneinfo.ZoneInfo("Australia/Melbourne") dt = datetime.now(tz) print(dt.tzname()) # 输出AEDT或AEST,取决于当前时段
3. 将timezone实例转回ZoneInfo实例
如果手里是datetime.timezone实例(比如从astimezone()得到的AEDT实例),可以结合业务场景匹配到对应的ZoneInfo,再完成转换:
from datetime import datetime, timezone, timedelta import zoneinfo # 假设当前有一个带AEDT时区的datetime实例 dt_with_timezone = datetime.now(timezone(timedelta(hours=11), "AEDT")) # 指定目标时区区域 target_tz = zoneinfo.ZoneInfo("Australia/Melbourne") # 转换为带ZoneInfo的datetime实例 dt_with_zoneinfo = dt_with_timezone.astimezone(target_tz) print(type(dt_with_zoneinfo.tzinfo)) # 输出<class 'zoneinfo.ZoneInfo'> print(dt_with_zoneinfo.tzinfo) # 输出Australia/Melbourne
内容的提问来源于stack exchange,提问作者mon

