如何获取系统时区设置并将其传递给pytz.timezone?

82

我们可以使用 time.tzname 获取本地时区名称,但该名称与 pytz.timezone 不兼容。

事实上, time.tzname 返回的名称是有歧义的。此方法在我的系统中返回('CST','CST'),但'CST'可能表示四个时区:

  • 北美中部时区-观察北美中部时区
  • 中国标准时间
  • 中原标准时间-“中原标准时间”一词现在在台湾很少使用
  • 澳大利亚中部标准时间(ACST)

6个回答

85

tzlocal模块返回与本地时区相对应的pytz时区信息对象:

import time
from datetime import datetime

import pytz # $ pip install pytz
from tzlocal import get_localzone # $ pip install tzlocal

# get local timezone    
local_tz = get_localzone() 

# test it
# utc_now, now = datetime.utcnow(), datetime.now()
ts = time.time()
utc_now, now = datetime.utcfromtimestamp(ts), datetime.fromtimestamp(ts)

local_now = utc_now.replace(tzinfo=pytz.utc).astimezone(local_tz) # utc -> local
assert local_now.replace(tzinfo=None) == now

即使在本地时间可能不明确的夏令时转换期间,它也能正常工作。

local_tz 对于过去的日期也适用,即使当时本地时区的UTC偏移量不同。但是以基于dateutil.tz.tzlocal()的解决方案在这种情况下会失败,例如在欧洲/莫斯科时区(来自2013年的示例):

>>> import os, time
>>> os.environ['TZ'] = 'Europe/Moscow'
>>> time.tzset()
>>> from datetime import datetime
>>> from dateutil.tz import tzlocal
>>> from tzlocal import get_localzone
>>> dateutil_tz = tzlocal()
>>> tzlocal_tz = get_localzone()
>>> datetime.fromtimestamp(0, dateutil_tz)                              
datetime.datetime(1970, 1, 1, 4, 0, tzinfo=tzlocal())
>>> datetime.fromtimestamp(0, tzlocal_tz)
datetime.datetime(1970, 1, 1, 3, 0, tzinfo=<DstTzInfo 'Europe/Moscow' MSK+3:00:00 STD>)

dateutil在1970年1月1日返回了错误的UTC+4偏移量而不是正确的UTC+3。

对于那些在2017年遇到这个问题的人们,dateutil.tz.tzlocal()仍然存在问题。上面的例子现在有效,因为莫斯科当前的UTC偏移量为UTC+3(巧合的是它等于1970年的UTC偏移量)。为了演示错误,我们可以选择一个UTC偏移量为UTC+4的日期:

>>> import os, time
>>> os.environ['TZ'] = 'Europe/Moscow'
>>> time.tzset()
>>> from datetime import datetime
>>> from dateutil.tz import tzlocal
>>> from tzlocal import get_localzone
>>> dateutil_tz = tzlocal()
>>> tzlocal_tz = get_localzone()
>>> ts = datetime(2014, 6,1).timestamp() # get date in 2014 when gmtoff=14400 in Moscow
>>> datetime.fromtimestamp(ts, dateutil_tz)
datetime.datetime(2014, 5, 31, 23, 0, tzinfo=tzlocal())
>>> datetime.fromtimestamp(ts, tzlocal_tz)
datetime.datetime(2014, 6, 1, 0, 0, tzinfo=<DstTzInfo 'Europe/Moscow' MSK+4:00:00 STD>)

dateutil 在2014-06-01返回了错误的UTC+3时区偏移量,而不是正确的UTC+4。


1
有趣的是,tzlocal 模块返回一个 pytz 时区? - Martijn Pieters
1
是的,pytz tzinfo的对象。我已经更新了答案,明确提到了它。 - jfs
18
时区似乎遵循霍夫斯塔德定律的某种规律:即使你考虑到它们比你想象的更加复杂,它们仍然比你想象的更加复杂。 - Jason
5
对于那些在2017年遇到这个问题的人,dateutil.tz.tzlocal()不再存在故障,并且不会表现出上述行为。 - WhyNotHugo
4
@WhyNotHugo: dateutil.tz.tzlocal()仍然无法使用。我已经更新了答案。 - jfs
显示剩余2条评论

43
自 Python 3.6 开始,你可以简单地运行 naive_datetime.astimezone() ,系统时区将被添加到 naive_datetime 对象中。
如果在没有参数(或 tz=None)的情况下调用,则假定目标时区为系统本地时区。转换后的 datetime 实例的 .tzinfo 属性将被设置为一个时区实例,该实例从操作系统获得区域名称和偏移量。 https://docs.python.org/3/library/datetime.html#datetime.datetime.astimezone 示例:
>>> import datetime
>>> datetime.datetime.now().astimezone().isoformat(timespec='minutes')
'2018-10-02T13:09+03:00'

1
@Bharat,你能解释一下为什么吗? - David Olmo Pérez
2
这个答案正是我所需要的,而且甚至不需要第三方库! - timgeb
1
我想知道在调用 nowastimezone 时,以及夏令时的转换(或用户更改系统时区)之间是否存在理论上的竞争条件。 - Martin Cejp

39

使用python-dateutil软件包中的tzlocal函数

from dateutil.tz import tzlocal

localtimezone = tzlocal()

在内部,这是一个使用time.timezonetime.altzone(根据time.daylight进行切换)的类,但从中创建了一个合适的时区对象。

您可以使用此方法替代pytz时区。

另一种选择是从操作系统中读取当前配置的时区,但这在不同的操作系统上有很大差异。在Mac OS X上,您需要读取systemsetup -gettimezone的输出:

$ systemsetup -gettimezone
Time Zone: Europe/Copenhagen

在 Debian 和 Ubuntu 系统上,您可以阅读 /etc/timezone 文件:

$ cat /etc/timezone
Europe/Oslo

对于RedHat及其衍生系统,您需要从/etc/sysconfig/clock读取:

$ grep ZONE /etc/sysconfig/clock
ZONE="Europe/Oslo"

2
谢谢,但 pytz.timezone 需要时区名称。 - user805627
@user805627:你可以使用它来代替pytz时区。 - Martijn Pieters
2
但是pytz已经在我的项目中使用了很多地方,我不想因为这个问题而替换它。 - user805627
@user805627:我给你提供了一些替代方案。 - Martijn Pieters
2
如果当地时区的UTC偏移在过去的某个时间不同,dateutil.tz.tzlocal()将无法处理过去的日期。 - jfs

10
一个非常简单的解决方法:

一个非常简单的解决此问题的方法:

import time

def localTzname():
    offsetHour = time.timezone / 3600
    return 'Etc/GMT%+d' % offsetHour

更新: @MartijnPieters说 '这个方法在夏令时不起作用.' 那么这个版本怎么样?

import time

def localTzname():
    if time.daylight:
        offsetHour = time.altzone / 3600
    else:
        offsetHour = time.timezone / 3600
    return 'Etc/GMT%+d' % offsetHour

嗯,夏令时应该会改变时区吗?我从来没有使用过夏令时。我认为夏令时只会改变时间,而不会改变时区。 - user805627
time.altzone 是夏令时区,time.daylight 是夏令时指示器。但是,是的,时区会随着夏令时改变。 - Martijn Pieters
6
time.daylight并不能告诉你当前是否正在实行夏令时,它只是表示这个时区是否有夏令时。你可以按照以下方式计算UTC偏移量:is_dst = time.daylight and time.localtime().tm_isdst > 0。你可以将POSIX时间戳传递给localtime(),以在不同的时间点计算is_dstutc_offset = - (time.altzone if is_dst else time.timezone)(注意:符号与名称中使用的相反)。 - jfs
5
这将无法在半小时和45分钟时区中使用。 - sam hocevar
19
日期和时间的数学运算就像密码学一样,自己动手很危险。还是交给专家吧。 - Mark E. Haase

2
我不确定这是否对您有用,但我认为它可以回答您更一般的问题:
如果您有一个处于模糊时区中的日期,比如CST,simple-date(仅适用于Python 3.2+,抱歉)可以自动搜索,并允许您偏好某些国家。
例如:
>>> SimpleDate('2013-07-04 18:53 CST')
Traceback [...
simpledate.AmbiguousTimezone: 3 distinct timezones found: <DstTzInfo 'Australia/Broken_Hill' CST+9:30:00 STD>; <DstTzInfo 'America/Regina' LMT-1 day, 17:01:00 STD>; <DstTzInfo 'Asia/Harbin' LMT+8:27:00 STD> (timezones=('CST',), datetime=datetime.datetime(2013, 7, 4, 18, 53), is_dst=False, country=None, unsafe=False)
>>> SimpleDate('2013-07-04 18:53 CST', country='CN')
SimpleDate('2013-07-04 18:53 CST')
>>> SimpleDate('2013-07-04 18:53 CST', country='CN').utc
SimpleDate('2013-07-04 10:53 UTC', tz='UTC')

注意,通过指定国家,您可以减少可能值的范围,从而允许转换为UTC。
这是通过在PyTZ中对时区进行搜索来实现的:
>>> SimpleDate('2013-07-04 18:53 CST', country='CN', debug=True)
...
PyTzFactory: Have country code CN
PyTzFactory: Country code CN has 5 timezones
PyTzFactory: Expanded country codes to 5 timezones
PyTzFactory: Expanding ('CST',)
PyTzFactory: Name lookup failed for CST
PyTzFactory: Found CST using Asia/Shanghai
PyTzFactory: Found CST using Asia/Harbin
PyTzFactory: Found CST using Asia/Chongqing
PyTzFactory: Found CST using Asia/Urumqi
PyTzFactory: Found CST using Asia/Kashgar
PyTzFactory: Expanded timezone to 5 timezones
PyTzFactory: New offset 8:00:00 for Asia/Shanghai
PyTzFactory: Known offset 8:00:00 for Asia/Harbin
PyTzFactory: Known offset 8:00:00 for Asia/Chongqing
PyTzFactory: Known offset 8:00:00 for Asia/Urumqi
PyTzFactory: Known offset 8:00:00 for Asia/Kashgar
PyTzFactory: Have 1 distinct timezone(s)
PyTzFactory: Found Asia/Shanghai
...
SimpleDate('2013-07-04 18:53 CST')

最后,直接回答问题,它还包装了tzlocal,正如在另一个答案中提到的那样,因此如果您没有提供时区,它将自动执行您所期望的操作。例如,我住在智利,所以
>>> SimpleDate()
SimpleDate('2013-07-04 19:21:25.757222 CLT', tz='America/Santiago')
>>> SimpleDate().tzinfo
<DstTzInfo 'America/Santiago' CLT-1 day, 20:00:00 STD>

提供我的区域设置的时区(无论是否模糊)。

-6
import pytz

假设你有一个存储 UTC DateTime 值的 OBJ 列表对象。

tz=pytz.timezone('Asia/Singapore')

请看下面的URL以获取相应的时区位置字符串参数 是否有Pytz时区列表? 现在我们的tz是一个拥有新加坡时间的对象。
result=[]
for i in OBJ:
    i=i+tz.utcoffset(i)
    result.append(i)

结果列表对象具有您所在时区的日期时间值


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