我需要做什么

我有一个不了解时区的datetime对象,我需要向它添加一个时区,以便能够将它与其他了解时区的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中都尝试了这一点,得到了相同的结果。

上下文

I am writing a parser for some data files. There is an old format I need to support where the date string does not have a timezone indicator. I've already fixed the data source, but I still need to support the legacy data format. A one time conversion of the legacy data is not an option for various business BS reasons. While in general, I do not like the idea of hard-coding a default timezone, in this case it seems like the best option. I know with reasonable confidence that all the legacy data in question is in UTC, so I'm prepared to accept the risk of defaulting to that in this case.


当前回答

我同意之前的答案,如果你可以用UTC开始,那就没问题。但我认为,对于人们来说,使用具有非UTC本地时区的datetime的tz感知值也是一种常见的情况。

如果只按名称命名,可能会推断replace()将适用并生成正确的日期时间感知对象。事实并非如此。

替换(tzinfo=…)它的行为似乎是随机的。因此,它是无用的。不要用这个!

localalize是正确的函数。例子:

localdatetime_aware = tz.localize(datetime_nonaware)

或者一个更完整的例子:

import pytz
from datetime import datetime
pytz.timezone('Australia/Melbourne').localize(datetime.now())

给我当前本地时间的时区感知datetime值:

datetime.datetime(2017, 11, 3, 7, 44, 51, 908574, tzinfo=<DstTzInfo 'Australia/Melbourne' AEDT+11:00:00 DST>)

其他回答

还有另一种让datetime对象不幼稚的方法:

>>> from datetime import datetime, timezone
>>> datetime.now(timezone.utc)
datetime.datetime(2021, 5, 1, 22, 51, 16, 219942, tzinfo=datetime.timezone.utc)

使用dateutil.tz.tzlocal()来获取你使用datetime.datetime.now()和datetime.datetime.astimezone()时的时区:

from datetime import datetime
from dateutil import tz

unlocalisedDatetime = datetime.now()

localisedDatetime1 = datetime.now(tz = tz.tzlocal())
localisedDatetime2 = datetime(2017, 6, 24, 12, 24, 36, tz.tzlocal())
localisedDatetime3 = unlocalisedDatetime.astimezone(tz = tz.tzlocal())
localisedDatetime4 = unlocalisedDatetime.replace(tzinfo = tz.tzlocal())

注意datetime。astimezone首先将datetime对象转换为UTC,然后转换为时区,这与调用datetime相同。将原时区信息替换为None。

以上所有提到的方法,当它是Unix时间戳时,有一个非常简单的解决方案使用pandas。

import pandas as pd

unix_timestamp = 1513393355
pst_tz = pd.Timestamp(unix_timestamp, unit='s', tz='US/Pacific')
utc_tz = pd.Timestamp(unix_timestamp, unit='s', tz='UTC')

对于那些只是想使一个时区感知datetime

import datetime

datetime.datetime(2019, 12, 7, tzinfo=datetime.timezone.utc)

对于那些在python 3.9 stdlib中希望使用非utc时区的datetime的人

import datetime
from zoneinfo import ZoneInfo

datetime.datetime(2019, 12, 7, tzinfo=ZoneInfo("America/Los_Angeles")) 

我同意之前的答案,如果你可以用UTC开始,那就没问题。但我认为,对于人们来说,使用具有非UTC本地时区的datetime的tz感知值也是一种常见的情况。

如果只按名称命名,可能会推断replace()将适用并生成正确的日期时间感知对象。事实并非如此。

替换(tzinfo=…)它的行为似乎是随机的。因此,它是无用的。不要用这个!

localalize是正确的函数。例子:

localdatetime_aware = tz.localize(datetime_nonaware)

或者一个更完整的例子:

import pytz
from datetime import datetime
pytz.timezone('Australia/Melbourne').localize(datetime.now())

给我当前本地时间的时区感知datetime值:

datetime.datetime(2017, 11, 3, 7, 44, 51, 908574, tzinfo=<DstTzInfo 'Australia/Melbourne' AEDT+11:00:00 DST>)