使用本地时区在Python中获取正确的时区偏移量

29

首先,我要说一下我的时区是CET/CEST。它从CEST变成CET的确切时间(从GMT+2的夏令时到GMT+1的标准时间)总是在10月的最后一个星期日早上3点。2010年是在10月31日早上3点。

现在注意以下几点:

>>> import datetime
>>> import pytz.reference
>>> local_tnz = pytz.reference.LocalTimezone()
>>> local_tnz.utcoffset(datetime.datetime(2010, 10, 31, 2, 12, 30))
datetime.timedelta(0, 3600)

如上所述,这是错误的。

>>> local_tnz.utcoffset(datetime.datetime(2010, 10, 30, 2, 12, 30))
datetime.timedelta(0, 7200)
>>> local_tnz.utcoffset(datetime.datetime(2010, 10, 31, 2, 12, 30))
datetime.timedelta(0, 7200)

现在它突然正确了 :/

我知道已经有几个关于这个问题的提问,但是给出的解决方案总是“使用本地化”,但我的问题是LocalTimezone没有提供该方法。

事实上,我有几个毫秒级别的时间戳,我需要本地时区的utc偏移量(不仅仅是我的时区,还有任何使用程序的人的时区)。 其中之一是1288483950000或Sun Oct 31 2010 02:12:30 GMT+0200(CEST)在我的时区。

目前,我通过以下方式获取日期时间对象:

datetime.datetime.fromtimestamp(int(int(millis)/1E3)) 

可以使用以下代码获取时区偏移(单位为分钟):

-int(local_tnz.utcoffset(date).total_seconds()/60)

很不幸,这在许多情况下是错误的 :(。

有什么想法吗?

注意:我正在使用python3.2.4,但在这种情况下应该无所谓。

编辑:

感谢@JamesHolderness,我已找到解决方案:

def datetimeFromMillis(millis):
    return pytz.utc.localize(datetime.datetime.utcfromtimestamp(int(int(millis)/1E3)))

def getTimezoneOffset(date):
    return -int(date.astimezone(local_tz).utcoffset().total_seconds()/60)

使用 tzlocal 模块中的 tzlocal.get_localzone() 将 local_tz 设为本地时区。

使用Python 2.7.5和pytz 2013b,我得到了不同且一致的结果:分别是datetime.timedelta(0,3600)datetime.timedelta(0,7200)datetime.timedelta(0,3600)。在运行测试之前,我将时区设置为CET。 - martineau
1
尝试手动浏览 utcoffset 和 _isdst 函数中的代码行,可以在 cli 上查看代码:http://bazaar.launchpad.net/~stub/pytz/devel/view/head:/src/pytz/reference.py看看是否能发现任何错误。 - ojs
4个回答

43
根据维基百科,夏令时的转换发生在01:00 UTC。
- 在00:12 UTC时,您仍处于中欧夏令时(即UTC+02:00),因此当地时间为02:12。 - 在01:12 UTC时,您回到标准的中欧时间(即UTC+01:00),因此当地时间再次为02:12。
当从夏令时改回标准时间时,当地时间从02:59回到02:00,小时重复。因此,在询问02:12(当地时间)的UTC偏移量时,答案可以是+01:00或+02:00 - 这取决于您所说的02:12版本。
进一步调查pytz库后,我认为您的问题可能是不应该使用pytz.reference实现,因为它可能无法很好地处理这些歧义。引用源代码中的注释:

参考Python文档中的tzinfo实现。 仅用于测试,因为它们只对1987年至2006年正确。不要在真正的代码中使用这些。

在pytz中处理模糊时间

你应该做的是构建一个适当时区的timezone对象:

import pytz
cet = pytz.timezone('CET')

然后,您可以使用utcoffset方法来计算该时区中日期/时间的UTC偏移量。

dt = datetime.datetime(2010, 10, 31, 2, 12, 30)
offset = cet.utcoffset(dt)

注意,上面的示例会抛出一个AmbiguousTimeError异常,因为它无法确定你指的是两个版本中的哪一个02:12:30。幸运的是,通过设置is_dst参数,pytz将让您指定您想要的夏令时版本还是标准版本。例如:
offset = cet.utcoffset(dt, is_dst = True)

请注意,即使时间不会产生歧义,将此参数设置在所有对utcoffset的调用上也不会有害。根据文档,在DST转换期间存在歧义时,才会使用它来解决这种歧义。
处理时间戳时,最好尽可能长时间地将它们存储为UTC值,否则您可能会丢失有价值的信息。因此,首先使用datetime.utcfromtimestamp方法将其转换为UTC日期时间。
dt = datetime.datetime.utcfromtimestamp(1288483950)

然后使用pytz将时间本地化为UTC,这样时区就会附加到datetime对象上。
dt = pytz.utc.localize(dt)

最后,您可以将UTC日期时间转换为本地时区,并像这样获取时区偏移量:

offset = dt.astimezone(cet).utcoffset()

请注意,这组计算将为1288483950和1288487550产生正确的偏移量,尽管两个时间戳都由CET时区的02:12:30表示。

确定本地时区

如果您需要使用计算机的本地时区而不是固定时区,则无法直接从pytz中实现。您也不能仅使用time.tzname中的时区名称构造一个pytz.timezone对象,因为这些名称不总是被pytz所识别。

解决方案是使用tzlocal模块 - 它的唯一目的是在pytz中提供此缺失的功能。您可以像这样使用它:

import tzlocal
local_tz = tzlocal.get_localzone()

get_localzone() 函数返回一个 pytz.timezone 对象,因此您应该能够在上面示例中使用 cet 变量的所有位置中使用该值。


我使用的是最新版本,结果在同一天的01:12 CE(S)T也是错误的,所以这与日期的歧义无关。此外,在这些转换期间假定DST是常规惯例,JavaScript也是这样做的。 - Joren Van Severen
1
我很好奇是什么让你认为在这些过渡期间有任何约定。在我的电脑上,JavaScript 实现因浏览器而异。尝试在浏览器控制台中键入 new Date(2010, 9, 31, 2, 12, 30, 0).getTime()。在 Chrome 上,我得到的是 1288487550000(即 01:12 UTC)。在 Firefox 上,我得到的是 1288483950000(即 00:12 UTC)。 - James Holderness
1
我开始理解问题了,使用时间戳时没有歧义,这就是为什么可以正确确定新日期(1288483950000)的时区和确切日期。因此,我需要一种在Python中从时间戳计算时区偏移量的方法... - Joren Van Severen
@JorenVanSeveren 看起来你不应该使用pytz.reference实现 - 我已经更新了我的答案并提供了更多信息。 - James Holderness
1
@JorenVanSeveren,恐怕您不能指望time.tzname在所有情况下都能正常工作。我已经更新了我的答案,并提供了获取本地时区的解决方案。 - James Holderness
显示剩余6条评论

14

给定一个时间戳(单位为毫秒),只使用stdlib,您可以获取本地时区的UTC偏移量:

#!/usr/bin/env python
from datetime import datetime

millis = 1288483950000
ts = millis * 1e-3
# local time == (utc time + utc offset)
utc_offset = datetime.fromtimestamp(ts) - datetime.utcfromtimestamp(ts)

如果我们忽略闰秒,那么就不会有歧义或不存在的时间。

如果操作系统维护历史时区数据库,例如Ubuntu上对于任何过去/现在的日期都应该可以工作,但可能会在Windows上破坏使用了不同utc偏移量的过去日期。

这里是相同的内容,使用tzlocal模块,在*nix和Win32系统上都应该可以工作:

#!/usr/bin/env python
from datetime import datetime
from tzlocal import get_localzone # pip install tzlocal

millis = 1288483950000
ts = millis * 1e-3
local_dt = datetime.fromtimestamp(ts, get_localzone())
utc_offset = local_dt.utcoffset()

参见如何使用Python标准库将Python UTC时间转换为本地时间?

要获取UTC偏移量(Python 3.2+)的分钟数:

from datetime import timedelta

minutes = utc_offset / timedelta(minutes=1)

不要使用pytz.reference.LocalTimezone()这只是用于测试


1
import pytz, datetime
tz = timezone('CET')
tz.utcoffset(datetime.datetime.now()).total_seconds()

7200.0


-1

other_datetime = "YYYY-mm-ddTHH:MM:SSother_offset"

T 可以是空格或字符 'T' 本身。

other_offset 通常表示为 "+HHMM" 或 "-HHMM"

然后你只需要这样做:

  1. 获取我的时区偏移量 := my_offset
  2. my_datetime = other_datetime + minutes( my_offset - other_offset)

请记住,my_offset 和 other_offset 可以是正数或负数。

因此,例如,您可能会做出以下操作:

my_datetime = other_datetime + minutes( (-7:00) - (-8:00) ) = other_datetime + minutes(+1:00) = other_datetime + 60 minutes

等等

请注意双重否定。


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