跳到正文
效率工具实用笔记关于本站
表格与数据

UTC和北京时间怎么换算:用有时区的datetime做固定UTC+8换算

约 11 分钟阅读AI 辅助整理

用自建示例讲清aware与naive的区别,用timezone.utc、timedelta和astimezone在固定UTC+8偏移下换算UTC与北京时间,并说明为什么不能把固定偏移当成所有地区全年时差。

本文要点

  1. 要让一个datetime明确表示UTC,需要给它带上tzinfo

  2. 要得到一个真正标注为UTC+8的aware对象,用astimezone

UTC和北京时间怎么换算:用有时区的datetime做固定UTC+8换算的知识要点示意图
依据已审核正文摘取要点制作的示意图,非实物照片;请结合正文中的条件与范围阅读。要点摘自已审核正文;完整条件与说明见正文。查看高清图解 ↗
本文目录

先分清naive和aware:没有时区信息的datetime不能直接当UTC用

如果你手里有一个datetime对象,第一步不是加减8小时,而是先确认它是不是aware。

按Python官方datetime文档的说明,datetime对象分两类:aware对象带有tzinfo,能把自己定位到某个具体时刻;naive对象不带足够的时区信息,无法明确对应哪个时刻。文档的原话是,naive对象究竟代表UTC、本地时间还是别的时区,完全取决于程序怎么约定。

这意味着:一个没写时区的2024-03-15 08:00,你不能默认它是UTC,也不能默认它是北京时间。它只是一个“墙上时间”读数。

判断方法(依据文档给出的条件):一个datetime对象d是aware,需要同时满足d.tzinfo不是None,且d.tzinfo.utcoffset(d)不返回None;否则它是naive。

本文只处理一个固定场景:把UTC时间与固定UTC+8偏移的北京时间互相换算。所有示例都是自建的练习数据,放在练习目录里,例如exercise/tz_demo.py。不涉及夏令时规则、不涉及其他时区、不声称所有地区全年固定时差。

import datetime as dt

naive = dt.datetime(2024, 3, 15, 8, 0)
print(naive.tzinfo)  # None

上面这个对象是naive。后面所有换算都从aware对象出发。

用timezone.utc构造aware的UTC时间

要让一个datetime明确表示UTC,需要给它带上tzinfo。datetime模块自带一个具体的tzinfo子类:timezone,它能表示相对UTC的固定偏移。timezone.utc就是UTC本身的单例。

构造方式有两种,效果一致:

import datetime as dt

utc_a = dt.datetime(2024, 3, 15, 8, 0, tzinfo=dt.timezone.utc)
utc_b = dt.datetime(2024, 3, 15, 8, 0).replace(tzinfo=dt.timezone.utc)
print(utc_a == utc_b)  # True

两种写法都得到aware对象,tzinfo是timezone.utc,偏移为0。

这里要避免一个常见写法:不要用datetime.utcnow()来拿“当前UTC”。文档明确提示,utcnow()返回的是naive对象,tzinfo为None,而且因为很多方法会把naive对象当本地时间处理,文档建议用aware对象表示UTC,推荐写法是datetime.now(timezone.utc)。文档同时标注utcnow()自3.12起已弃用。

所以本文的起点统一是:aware对象,tzinfo为timezone.utc。

用timedelta加8小时:得到的是固定偏移,不是时区规则

最直观的换算方式是给UTC时间加8小时:

import datetime as dt

utc = dt.datetime(2024, 3, 15, 8, 0, tzinfo=dt.timezone.utc)
beijing_like = utc + dt.timedelta(hours=8)
print(beijing_like)

timedelta表示一个时长,文档说明它内部只存days、seconds、microseconds三个单位,其他参数会被换算并归一化。timedelta(hours=8)就是8小时这个时长。

但这里有一个关键点:datetime加timedelta之后,结果的tzinfo和输入相同。文档在datetime支持的运算里写明,datetime2 = datetime1 + timedelta,结果具有和输入相同的tzinfo属性,而且即使输入是aware对象,也不会做任何时区调整。

也就是说,上面这段代码得到的是一个仍然标着timezone.utc、但读数已经加了8小时的对象。它的读数看起来像北京时间,但tzinfo还是UTC,偏移还是0。这个对象如果拿去和其他aware对象比较,会按UTC解释,结果就错了。

所以加timedelta适合做“时长推算”,不适合做“时区换算”。要真正换时区,用下一节的astimezone。

用astimezone换到固定UTC+8:读数变、时刻不变

要得到一个真正标注为UTC+8的aware对象,用astimezone。先构造一个固定偏移8小时的timezone:

import datetime as dt

utc = dt.datetime(2024, 3, 15, 8, 0, tzinfo=dt.timezone.utc)
beijing = dt.timezone(dt.timedelta(hours=8))
converted = utc.astimezone(beijing)
print(converted)
print(converted.tzinfo)

输出里,converted的读数会是2024-03-15 16:00,tzinfo是UTC+08:00。

这里要理解的是:astimezone改变的是“表示方式”,不是“时刻”。同一个时刻,用UTC表示是08:00,用UTC+8表示是16:00。文档把aware对象描述为能定位到一个不受解释影响的具体时刻,换算前后指的就是这同一个时刻。

反向换算同理:把北京时间转回UTC,用同一个方法换到timezone.utc即可。

back = converted.astimezone(dt.timezone.utc)
print(back)  # 2024-03-15 08:00:00+00:00

注意:timezone(timedelta(hours=8))表示的是固定偏移8小时,它不包含任何地区规则。本文用它只是构造一个固定UTC+8的示例,不代表某个地区全年都是这个偏移。

检查结果:用相减和比较验证换算有没有出错

换算完不要只看读数,做两个检查能发现大部分错误。

检查一:两个aware对象相减,应该得到0。

import datetime as dt

utc = dt.datetime(2024, 3, 15, 8, 0, tzinfo=dt.timezone.utc)
beijing = dt.timezone(dt.timedelta(hours=8))
converted = utc.astimezone(beijing)
print(converted - utc)  # 0:00:00

文档说明,两个aware对象相减时,如果tzinfo不同,会先各自换算成naive UTC再相减。所以同一个时刻的两种表示相减结果是0。

检查二:aware和naive不能直接比较或相减。文档明确写,naive和aware的datetime对象永远不相等,顺序比较会抛TypeError,相减也会抛TypeError。如果你在换算后遇到这类报错,先回去看是不是有一个对象漏了tzinfo。

naive = dt.datetime(2024, 3, 15, 16, 0)
# naive == converted  # 不会相等
# naive < converted   # TypeError

检查三:确认tzinfo不是None。换算后打印converted.tzinfo,应该看到UTC+08:00,而不是None。

常见错误排查:加timedelta后比较结果不对

如果你按“加8小时”的方式换算,然后拿去比较,可能会得到意外结果。原因在上一节已经说明:加timedelta不改变tzinfo。

对照一下两种做法的差别:

import datetime as dt

utc = dt.datetime(2024, 3, 15, 8, 0, tzinfo=dt.timezone.utc)

wrong = utc + dt.timedelta(hours=8)          # 读数16:00,tzinfo仍是UTC
right = utc.astimezone(dt.timezone(dt.timedelta(hours=8)))  # 读数16:00,tzinfo是UTC+8

print(wrong.tzinfo)  # UTC
print(right.tzinfo)  # UTC+08:00
print(wrong == right)  # False

wrong和right读数一样,但比较结果是False,因为比较时会按各自的tzinfo换算成UTC:wrong被当成16:00 UTC,right被当成08:00 UTC。

排查顺序建议:

1. 打印两个对象的tzinfo,确认是不是None或是不是预期偏移。

2. 如果读数对但比较不对,检查是不是用了加timedelta而不是astimezone。

3. 如果相减或比较抛TypeError,检查是不是混了naive和aware。

以上排查顺序是编辑整理的,不是对某个程序做过的测试记录。

使用限制:固定UTC+8只是示例,不能当成所有地区的全年时差

本文从头到尾用的是timezone(timedelta(hours=8)),这是一个固定偏移,不包含任何地区规则。

文档对timezone类的说明是:它实现tzinfo抽象基类,表示相对UTC的固定偏移。文档同时指出,更深层次的时区细节由应用自己处理,因为世界各地的时区调整规则“更多是政治性的而非理性的,并且经常变化”,除了UTC之外没有适合所有应用的标准。

所以本文的适用范围是:

- 只讲timezone.utc、timedelta和astimezone在固定UTC+8示例下的用法。

- 只区分naive与aware,不推断某个地区在某个日期实际使用什么偏移。

- 不声称所有地区全年固定UTC+8,也不处理夏令时或历史偏移变化。

如果你的场景需要按真实地区规则换算,本文的固定偏移示例不够用,需要另行查阅该地区的时区规则资料。本文不提供这类规则,也不编造具体地区的偏移数值。

所有示例均为自建练习数据,放在练习目录中运行,不修改系统设置,不涉及真实业务数据。

观看知识要点视频 18 秒

18秒无旁白要点导览,文字来自已审核正文;完整步骤、条件及参考资料见下方。

参考资料

以下原始资料提供本文依据;请结合日期、适用条件和产品说明查阅。

请结合原始资料的适用条件阅读。

返回表格与数据 →