如何使datetime对象具有时区意识(非天真的)
我需要做什么
我有一个不带时区的日期时间对象,我需要给它加上一个时区,这样才能和其他带时区的日期时间对象进行比较。我不想为了这个老旧的情况,把整个应用程序都改成不带时区的。
我尝试过什么
首先,来展示一下问题:
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 个回答
我在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)
所有这些例子都使用了外部模块,但你也可以仅仅使用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()
一般来说,要让一个普通的日期时间对象变得懂得时区信息,可以使用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
对象。)