Python的pytz时区函数返回的时区比实际偏差9分钟。

73

因为某些我还没搞清楚的原因,从下面的代码开始:

>>> from pytz import timezone
>>> timezone('America/Chicago')

我理解为:

<DstTzInfo 'America/Chicago' LMT-1 day, 18:09:00 STD>

我想应该是在什么时候收到:

<DstTzInfo 'America/Chicago' LMT-1 day, 18:00:00 STD>

我认为我的时区不是距离协调世界时6小时9分钟。

我查看了pytz的源代码,但我承认我没有完全弄清楚出了什么问题。

我已经向timezone()函数传递了其他值,它返回的值似乎是正确的。然而,与我的时区相关的信息不正确。

最后,坐在我旁边的同事确认该函数在他的机器上返回了正确的时区信息。

有人知道为什么我的时区('America/Chicago')会相差9分钟吗?我正在使用使用pip安装的pytz 2015.7版本。谢谢!


6
您正在获取本地平均时间。https://dev59.com/32gu5IYBdhLWcg3wP0tm tz = timezone('America/Chicago'); tz.localize(datetime.datetime.now()) - Padraic Cunningham
@PadraicCunningham,这绝对是我正在经历的问题。不过,你有没有想过为什么在我旁边的那个人的机器上运行相同的代码却得到了不同的结果呢? - elethan
1
我建议你的朋友升级而不是降级。 - Padraic Cunningham
1
@PadraicCunningham:不要使用 tz.localize(datetime.now());改用 datetime.now(tz)。前者在夏令时转换期间可能会失败。 - jfs
1
@PadraicCunningham:思考一下为什么localize()is_dst参数。思考一下为什么你在当前时间(.now(tz))不需要它。这个讨论与当前问题无关。请参见[ask]。 - jfs
显示剩余9条评论
4个回答

75

根据Carl Meyer在Google Groups Answer的回答,这种差异的原因是:这不是将无时区datetime对象转换为有时区对象的正确方法。

解释如下:

"pytz时区类不代表UTC偏移量,而是代表一个地理区域,在历史过程中,该区域可能经历了几个不同的UTC偏移量。给定区域的最早偏移量通常称为“LMT”(本地平均时间),它通常与UTC相差奇数分钟,表示标准化之前的偏移量(在大多数地方为19世纪后期)。"

(引自Google Groups中的引用)

基本上,您应该执行:

from datetime import datetime
import pytz

my_datetime = datetime(2015, 6, 11, 13, 30)
my_tz = pytz.timezone('America/Chicago')    
good_dt = my_tz.localize(my_datetime)

print(good_dt)

输出: 2015年06月11日13时30分00秒(美国东部时间)


@Antigluk,我回答中的代码是正确的。我看不出任何理由将其投票降低。另一方面,这个答案中的代码可能会在模糊或不存在的本地时间下失败(例如,在某些时区,如夏令时转换期间可能会发生),而我的答案中的代码即使在这些情况下也能正常工作。 - jfs
4
@jfs,是的,你说得对,撤销投票并不公平,无法取消它。 这个答案实际上鼓励大家使用localize()方法,其使用范围远不止于now()。 如果没有这个提示,第一反应可能是使用datetime(...,tzinfo = pytz.timezone('America / Chicago')),这是危险的,并且会导致使用本地均时时间。 因此,我认为在搜索此类内容时首先要找到localize()方法很重要。 - Antigluk
@Antigluk,“本地化”在我的回答中明确提到,并附有更多详细信息的链接。 - jfs
1
这是一个有关本地平均时间危险使用的演示,我在StackOverflow上看到过几个被接受的答案中都出现了这种情况。该参数似乎与本地化方式相同地应用时区。天哪,我想知道由于此问题存在多少错误:https://repl.it/@jawinn/time-is-off-by-almost-an-hour-when-applying-timezones - jwinn

16

除非您的本地时区具有固定的UTC偏移量,否则在不提供特定日期/时间的情况下谈论其具体值是毫无意义的。

如果您提供时间(例如当前时间),那么您会发现pytz会产生预期的UTC偏移量:

>>> from datetime import datetime
>>> import pytz
>>> datetime.now(pytz.timezone('America/Chicago')).strftime('%Z%z')
'CST-0600'

请参见:

如果您未提供特定的日期/时间,则 pytz 可能会从给定时区的可用 UTC 偏移集合中返回任意 UTC 偏移。最近的 pytz 版本返回与最早时间(通常是本地平均时)对应的 UTC 偏移,但您不应该依赖它。您和您的朋友可能使用不同的 pytz 版本,这可能解释了结果差异。


我假设这个解决方案可以工作,并在弄清如何降级到我的同事的 pytz 版本之前将其标记为正确。现在我和他使用完全相同的版本,但是从交互式提示符中,我们对 pytz.timezone('America/Chicago') 的调用得到不同的结果(即,我得到 18:09:00,他得到 18:00:00)。如果这不是 pytz 版本引起的差异,还有什么其他原因可能导致这种差异?这可能是系统级配置差异吗? - elethan
2
你的答案中的代码确实可以工作。我的问题是,在我的机器上调用 pytz.timezone('America/Chicago') 总是返回 <DstTzInfo 'America/Chicago' LMT-1 day, 18:09:00 STD>,而在我测试过的其他所有机器上(即使使用相同版本的 pytz),它总是返回 <DstTzInfo 'America/Chicago' LMT-1 day, 18:00:00 STD>,我的初始问题是询问这种差异的原因。我从你的答案中推断出这是由于版本差异引起的,但事实并非如此。 - elethan
1- 这个值(从没有特定时间的tz的UTC偏移量)是没有意义的。你明白这一点吗? 2- 这个值取决于pytz版本。虽然我猜测你使用了错误的pytz版本,但它可能还取决于其他因素。无论如何,不重要。如果时区具有非固定的UTC偏移量(例如America/Chicago),您永远不应该依赖于显示的UTC偏移量而不提供时间。如果您提供时间,则应在不同平台上获得相同的值(假设使用相同的tz数据库版本)。 - jfs
2
我理解你所说的,并且同意你的看法。我只是好奇你是否知道可能导致相同版本的 pytz 出现差异的原因。显然,如果你没有指定时间,pytz 不会随机选择偏移量,因为结果是一致的(即使选择方法可能会随着版本不同而改变)。我想知道它为什么会在不同的机器上选择不同的偏移量。 - elethan
1- 在最近的pytz版本(至少从2014.3开始),它总是使用与最早时间对应的UTC偏移量(正如我在答案中所说)。这意味着该值也取决于tz数据库版本(如果您的发行版修补程序使用非捆绑的tzdata)。我没有看到使用不同算法的旧版本的代码(最新时间? - 默认值可能隐藏错误)。2- 不同结果最有可能的原因是您使用了不同的pytz版本。检查pytz.__version__pytz.OLSON_VERSION。检查pytz.__path__返回预期值。 - jfs
显示剩余2条评论

15

为了满足我的好奇心,最近我又深入探究了这个问题。

起初,似乎差异来源于不同版本的pytz。然而,即使使用相同版本的pytz,我的机器似乎仍在使用基于LMT的UTC偏移量,而其他机器则使用基于CDT或CST的偏移量。我将pytz的版本降级到一个我确认会得到与我的机器上不同结果的版本,发现这并不是问题的根源。

根据我与@J.F.Sebastian的交谈,我认为唯一可能的另一种情况是系统层面的差异。我进一步研究了pytz的源代码,并发现其获取至少一些时区信息的文件位于/usr/share/zoneinfo/中。因此,我查看了文件/usr/share/zoneinfo/America/Chicago,虽然它是一个二进制文件,但其中的一部分可以读取。文件的一半处有一个时区列表:LMTCDTCSTESTCWTCPT。正如您所见,LMT是列表中的第一个名称,正如@J.F.Sebastian所建议的那样,在原始问题描述的情况下,pytz似乎会使用它。

这就是Ubuntu 15.10中列表的外观。然而,在我得到结果为-600而不是-609的早期Ubuntu版本(例如Trusty和Precise)中,相同的列表是CDTCSTESTCWTCPT

我承认这来自于大量盲目探索和半懂不懂,但是看起来这就是导致我在不同机器上看到的差异的原因。至于为什么zoneinfo文件在不同版本之间不同,以及这些差异对Ubuntu意味着什么,我不知道,但我想与那些同样好奇的人分享我的发现,并可能从社区中获得有见地的更正/补充信息。


1
注意:pytz库使用捆绑的zoneinfo(子目录)存储时区信息。如果你看到使用了/usr/share/zoneinfo,那么说明你使用的是修改过的pytz包(例如,如果你使用sudo apt-get python-tz安装了pytz,那么pytz会使用Debian中打补丁的open_resource()函数,以使用tzdata包中的系统时区信息)。如果原版和打补丁的版本具有相同的__version__pytz.OLSON_VERSION,那么这是不好的,但你不应该感到惊讶它们会产生不同的结果(打补丁的版本可能会欺骗OLSON_VERSION,即系统可能使用更新的tzdata版本)。 - jfs

0

正如您所提到的,在pytz模块中原始文件存在一些差异:(在我的情况下使用中央时间)

xxxx......lib/python2.7/site-packages/pytz/zoneinfo/US/Central

In [66]: start = start.replace(tzinfo=central)

In [67]: start.isoformat()
Out[67]: '2018-02-26T00:00:00-05:51'

如果您使用操作系统的标准文件(我在Mac、Ubuntu和CentOS上进行了测试)

/usr/share/zoneinfo/US/Central

mv xxxx...../lib/python2.7/site-packages/pytz/zoneinfo/US/Central xxxx...../lib/python2.7/site-packages/pytz/zoneinfo/US/Central-bak

ln -s /usr/share/zoneinfo/US/Central xxxx...../lib/python2.7/site-packages/pytz/zoneinfo/US/Central

问题已解决

In [7]: central = timezone('US/Central')

In [8]: central
Out[8]: <DstTzInfo 'US/Central' CST-1 day, 18:00:00 STD>

In [10]: start = start.replace(tzinfo=central)

In [11]: start.isoformat()
Out[11]: '2018-02-27T00:00:00-06:00'


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