Python日期时间对象显示错误的时区偏移量。

58

我正在使用 datetime 和 pytz 在 Python 中创建一个日期时间对象,但显示的偏移量是错误的。

import datetime
from pytz import timezone

start = datetime.datetime(2011, 6, 20, 0, 0, 0, 0, timezone('Asia/Kolkata'))
print start

显示的输出为

datetime.datetime(2011, 6, 20, 0, 0, tzinfo=<DstTzInfo 'Asia/Kolkata' HMT+5:53:00 STD>)

请注意,'Asia/Kolkata' 是IST,即GMT+5:30,而不是HMT+5:53。这是一个标准的Linux时区,我为什么会弄错它,应该如何解决?


pytz错误报告工具。 - kojiro
@kojiro:这不是一个bug。请参考http://pytz.sourceforge.net/。 - Ferdinand Beyer
@FerdinandBeyer 当你写下你的评论时,它并不是一个 bug,但现在是了。datetime 接口现在可以正常工作,这可以通过 Python 3.9 中引入的 zoneinfo 模块 得到证明 - 它不需要 localize 函数才能正常工作。 - Mark Ransom
参见:pytz中的奇怪时区问题 - FObersteiner
2个回答

97

参见:http://bytes.com/topic/python/answers/676275-pytz-giving-incorrect-offset-timezone

在评论中,有人建议使用tzinfo.localize()而不是datetime构造函数,这样就解决了问题。

>>> tz = timezone('Asia/Kolkata')
>>> dt = tz.localize(datetime.datetime(2011, 6, 20, 0, 0, 0, 0))
>>> dt
datetime.datetime(2011, 6, 20, 0, 0, tzinfo=<DstTzInfo 'Asia/Kolkata' IST+5:30:00 STD>)

更新:实际上,官方 pytz 网站指出,您应该始终使用 localizeastimezone 而不是将时区对象传递给 datetime.datetime


20
这太疯狂了。感谢您的回答。 - Simanas
字节链接中的解释非常棒,谢谢你! - Maxim
6
如果pytz默认使用分钟偏移量非常不常见的时区,并且在使用.localize()方法时不使用它们,那么通过添加一些小的解释来说明这样做的原因,可以使这个答案更好。这听起来有点奇怪。 - Alfe
@Alfe 我认为没有人知道为什么 pytz 使用它所使用的默认值。我猜测默认值是其表中最旧的条目,而不是最新的条目,后者更有可能是正确的。这可能是故意的,以便在您没有使用 localize 时显而易见。localize 可以正常工作的原因是因为它传递了日期时间,因此可以使用它来确定正确的历史上下文并进行调整。Python 3.9 中添加的 zoneinfo 模块不需要 localize,我认为这是因为 datetime 已经改进以更好地支持它。 - Mark Ransom

7

这个问题在Python >= 3.9版本中得到了解决,标准库中的zoneinfo模块提供了解决方案。在Python >= 3.9版本中的解决方案可能是停止使用pytz。

In [1]: import datetime

In [2]: from zoneinfo import ZoneInfo

In [3]: start = datetime.datetime(2011, 6, 20, 0, 0, 0, 0, ZoneInfo('Asia/Kolkata'))

In [4]: print(start)
2011-06-20 00:00:00+05:30

这种极其令人困惑的行为的原因是,时区曾经没有标准化为:30或:00偏移量。在20世纪之交左右,大多数时区都采用了标准偏移量。在原帖的例子中,时区于1906年切换。对于美国/中央时区,这发生在1901年。
from datetime import datetime, timedelta, date
from pytz import timezone

d = datetime.combine(date.today(), time.min)
for tz in ('Asia/Kolkata', "US/Central"):
    while d > datetime(1800, 1, 1):
        localized = timezone(tz).localize(d)
        if localized.isoformat()[-2:] not in ("00", "30"):
            print(tz)
            print(localized.isoformat())
            print(timezone(tz).localize(d + timedelta(days=1)).isoformat())
            break
        d -= timedelta(days=1)

这将输出:

Asia/Kolkata
1906-01-01T00:00:00+05:21
1906-01-02T00:00:00+05:30
US/Central
1901-12-13T00:00:00-05:51
1901-12-14T00:00:00-06:00

Pytz似乎只在没有日期信息时使用最旧的偏移量,即使它很久以前就存在了。在一些非常自然的构造中,比如将tzinfo传递给datetime构造函数,时区对象并没有获得这些数据。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接