如何使datetime对象具有时区意识(非天真的)

818 投票
16 回答
753979 浏览
提问于 2025-04-16 23:37

我需要做什么

我有一个不带时区的日期时间对象,我需要给它加上一个时区,这样才能和其他带时区的日期时间对象进行比较。我不想为了这个老旧的情况,把整个应用程序都改成不带时区的。

我尝试过什么

首先,来展示一下问题:

Python 2.6.1 (r261:67515, Jun 24 2010, 21:47:49) 
[GCC 4.2.1 (Apple Inc. build 5646)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import datetime
>>> import pytz
>>> unaware = datetime.datetime(2011,8,15,8,15,12,0)
>>> unaware
datetime.datetime(2011, 8, 15, 8, 15, 12)
>>> aware = datetime.datetime(2011,8,15,8,15,12,0,pytz.UTC)
>>> aware
datetime.datetime(2011, 8, 15, 8, 15, 12, tzinfo=<UTC>)
>>> aware == unaware
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't compare offset-naive and offset-aware datetimes

我首先尝试了 astimezone:

>>> unaware.astimezone(pytz.UTC)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: astimezone() cannot be applied to a naive datetime
>>>

这次失败其实并不意外,因为它实际上是在尝试进行转换。于是我觉得使用 replace 可能是个更好的选择(参考 如何在 Python 中获取一个“带时区”的 datetime.today() 值?):

>>> unaware.replace(tzinfo=pytz.UTC)
datetime.datetime(2011, 8, 15, 8, 15, 12, tzinfo=<UTC>)
>>> unaware == aware
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't compare offset-naive and offset-aware datetimes
>>> 

但是如你所见,replace 似乎只是设置了 tzinfo,并没有让对象真正意识到时区。我准备退而求其次,先把输入字符串处理一下,加上时区再解析(我在解析时使用 dateutil,如果这有关系的话),但这看起来实在是太麻烦了。

另外,我在 Python 2.6 和 Python 2.7 中都试过,结果是一样的。

背景

我正在为一些数据文件编写解析器。有一种旧格式我需要支持,其中的日期字符串没有时区指示。我已经修复了数据源,但仍然需要支持这个老旧的数据格式。由于各种商业原因,一次性转换老旧数据并不是一个选项。虽然我通常不喜欢硬编码一个默认时区的想法,但在这种情况下,这似乎是最好的选择。我比较有把握的是,所有相关的老旧数据都是 UTC 格式,所以我准备在这种情况下冒这个风险,默认使用 UTC。

16 个回答

119

我在2011年写了这个Python 2的脚本,但从来没有检查过它在Python 3上是否能正常运行。

我把从带时区的日期时间(dt_aware)改成了不带时区的日期时间(dt_unaware):

dt_unaware = dt_aware.replace(tzinfo=None)

然后又把不带时区的日期时间(dt_unaware)改回了带时区的日期时间(dt_aware):

from pytz import timezone
localtz = timezone('Europe/Lisbon')
dt_aware = localtz.localize(dt_unware)
423

所有这些例子都使用了外部模块,但你也可以仅仅使用datetime模块来实现相同的效果,正如在这个SO回答中所展示的:

from datetime import datetime, timezone

dt = datetime.now()
dt = dt.replace(tzinfo=timezone.utc)

print(dt.isoformat())
# '2017-01-12T22:11:31+00:00'

这样依赖的东西更少,也没有pytz的问题。

注意:如果你想在python3和python2中都使用这个,你也可以用这个来导入时区(这里是硬编码为UTC):

try:
    from datetime import timezone
    utc = timezone.utc
except ImportError:
    #Hi there python2 user
    class UTC(tzinfo):
        def utcoffset(self, dt):
            return timedelta(0)
        def tzname(self, dt):
            return "UTC"
        def dst(self, dt):
            return timedelta(0)
    utc = UTC()
865

一般来说,要让一个普通的日期时间对象变得懂得时区信息,可以使用localize 方法

import datetime
import pytz

unaware = datetime.datetime(2011, 8, 15, 8, 15, 12, 0)
aware = datetime.datetime(2011, 8, 15, 8, 15, 12, 0, pytz.UTC)

now_aware = pytz.utc.localize(unaware)
assert aware == now_aware

对于UTC时区来说,其实不需要使用localize,因为这里没有夏令时的计算需要处理:

now_aware = unaware.replace(tzinfo=pytz.UTC)

这样做是可以的。(.replace会返回一个新的日期时间对象,它不会改变原来的unaware对象。)

撰写回答