Python:确定本地时区

135

我想要比较日志文件中的UTC时间戳和本地时间戳。当创建本地datetime对象时,我使用类似以下的代码:

>>> local_time=datetime.datetime(2010, 4, 27, 12, 0, 0, 0, 
                                 tzinfo=pytz.timezone('Israel'))

我想找一个自动工具,可以将tzinfo=pytz.timezone('Israel')替换为当前的本地时区。

有什么建议吗?


1
你的问题本身就值得怀疑。根据你所做的事情,很有可能会在夏令时转换(或任何其他本地时区更改的情况)接近时产生竞争条件。 - Kevin
3
如果您所指的“本地时间”是操作系统设置,那么您可以通过像 local_time=datetime.datetime(2010, 4, 27, 12, 0, 0, 0).astimezone()(Python >= 3.6)这样的带有时区信息的日期时间对象来获取它。[文档] - FObersteiner
19个回答

193

在Python 3.x中,可以通过以下方式确定本地时区:

>>> import datetime
>>> print(datetime.datetime.now(datetime.timezone.utc).astimezone().tzinfo)
AEST

这是datetime的一个棘手用法代码

对于Python版本小于3.6的情况,您需要

>>> import datetime
>>> print(datetime.datetime.now(datetime.timezone(datetime.timedelta(0))).astimezone().tzinfo)
AEST

61
仅需使用datetime.datetime.now().astimezone().tzinfo即可。 - Polv
7
datetime.datetime.utcnow().astimezone().tzinfo 似乎也是正确的,对我来说更易于阅读,因为它明确表示它首先将使用UTC。由于没有任何时区最初绑定到任何 datetime,所以在内部不应该有任何区别。 - sjngm
13
@Polv @sjngm并不完全正确,因为这仅适用于Python版本大于等于3.6。在3.6之前的原始时间中,您无法调用astimezone()方法。 - Fynn Becker
4
聪明,但如果您的程序长期运行,例如在夏令时更改时会起作用吗? - Wilmer
11
这看起来不正确。如果我使用TZ=Australia/Sydney,它会给出datetime.timezone(datetime.timedelta(seconds=36000), 'AEST')。 这是一个固定偏移的时区,在应用于夏令时生效的日期六个月后将会得到错误的答案。 - James Henstridge
显示剩余9条评论

51

尝试使用dateutil,它有一个tzlocal类型,可以满足你的需要。


34
这不是非常标准的套餐... 是否有更正统的解决方案? - gatoatigrado
2
dateutil 在处理过去某些时区的日期时会出现错误。而对于它能够正常工作的情况,你可以使用纯标准库解决方案 - jfs
10
from dateutil.tz import tzlocal - Andy Hayden
2
@Jthorpe dateutil并未过时,而且正在积极开发中。 - tacaswell
显示剩余4条评论

42

比较日志文件中的UTC时间戳与本地时间戳。

以便于移植的方式很难找到本地时区的Olson TZ名称。幸运的是,您不需要它来执行比较。

tzlocal 模块返回对应于本地时区的pytz时区:

from datetime import datetime

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

tz = get_localzone()
local_dt = tz.localize(datetime(2010, 4, 27, 12, 0, 0, 0), is_dst=None)
utc_dt = local_dt.astimezone(pytz.utc) #NOTE: utc.normalize() is unnecessary here

与迄今为止提出的其他解决方案不同,上述代码避免了以下问题:

  • 本地时间可能会产生歧义即对于某些本地时间,精确比较可能是不可能的
  • 过去日期相同的本地时区名称可以具有不同的utc偏移量。 一些支持时区感知日期时间对象(例如dateutil)的库未能考虑到这一点

注意:要从naive datetime获得时区感知datetime对象,应使用*

local_dt = tz.localize(datetime(2010, 4, 27, 12, 0, 0, 0), is_dst=None)

代替:
#XXX fails for some timezones
local_dt = datetime(2010, 4, 27, 12, 0, 0, 0, tzinfo=tz)

*is_dst=None会在给定的当地时间存在歧义或不存在时引发异常。

如果你确定所有本地时间戳使用相同的(当前的)utc偏移量作为本地时区,那么你可以仅使用stdlib进行比较:

# convert a naive datetime object that represents time in local timezone to epoch time
timestamp1 = (datetime(2010, 4, 27, 12, 0, 0, 0) - datetime.fromtimestamp(0)).total_seconds()

# convert a naive datetime object that represents time in UTC to epoch time
timestamp2 = (datetime(2010, 4, 27, 9, 0) - datetime.utcfromtimestamp(0)).total_seconds()

timestamp1timestamp2可以直接比较。

注意:

  • timestamp1公式仅在纪元时的UTC偏移量(datetime.fromtimestamp(0))与当前时间相同时有效。
  • fromtimestamp()会创建一个本地时区的无时区信息datetime对象。
  • utcfromtimestamp()会创建一个UTC的无时区信息datetime对象。

1
正是我所需要的! - nagylzs
4
我不明白为什么获取本地时区不包含在Python核心模块中... - rfmoz
3
如果我们排除需要使用pytz时区的特殊情况,Python自始至终都有标准库中的本地时区。Python 3.9已经接受了PEP 615 -- 支持IANA时区数据库的标准库(它提供了与pytz相同的tzdata访问)。 - jfs
2
@jfs 我想说的是支持 Olson 时区,抱歉。很高兴知道这个新的 PEP 615,它将增加很大的价值,谢谢分享。 - rfmoz

25
我也曾经问过自己同样的问题,而我在1中找到了答案:
请看8.1.7节:strftime格式“%z”(小写字母,大写字母Z也返回时区,但不是4位数字格式,而是时区缩写的形式,例如[3]中所示),返回标准电子邮件标题中使用的“+/- 4DIGIT”形式(请参见RFC 2822第3.3节,参见[2],它废除了其他指定电子邮件标题时区的方式)。
因此,如果您想要以这种格式显示您的时区,请使用:
time.strftime("%z")

[1] http://docs.python.org/2/library/datetime.html

[2] https://www.rfc-editor.org/rfc/rfc2822#section-3.3

[3] 时区缩写:http://en.wikipedia.org/wiki/List_of_time_zone_abbreviations,仅供参考。


3
问题是要找到对应于本地时区的tzinfo对象,而不是当前UTC偏移量作为字符串。[time.timezone, .altzone](https://dev59.com/u2Ys5IYBdhLWcg3wAfLO#comment25200533_13218990)可以给出当前的UTC偏移量。时区偏移或缩写是有歧义的。获取可用于过去、现在和未来日期的本地时区并不容易。查看[`tzlocal`模块的源代码](https://github.com/regebro/tzlocal),以了解如何实现此功能的示例。 - jfs
简单而有效 - Mohammad Banisaeid

12

以下似乎适用于3.7+,使用标准库:

from datetime import timedelta
from datetime import timezone
import time

def currenttz():
    if time.daylight:
        return timezone(timedelta(seconds=-time.altzone),time.tzname[1])
    else:
        return timezone(timedelta(seconds=-time.timezone),time.tzname[0])

10

首先获取pytz和tzlocal模块

pip install pytz tzlocal

那么

from tzlocal import get_localzone
local = get_localzone()

那么您可以做诸如以下的事情:

from datetime import datetime
print(datetime.now(local))

7
在以色列服务器上,创建一个包含本地时区ISO表示形式(+04:00)的ISO格式字符串:
>>> datetime.now(datetime.now().astimezone().tzinfo).isoformat()
'2021-09-07T01:02.030042+04:00'

这将创建一个“时区感知”的日期对象,可以与任何其他UTC或本地时间的日期时间对象进行比较。但是,如果您在旧金山的服务器上在完全相同的时间运行此代码,则ISO表示(以及日期/时间字符串本身)将会改变:

在美国加利福尼亚州旧金山的服务器上:

>>> datetime.now(datetime.now().astimezone().tzinfo).isoformat()
'2021-09-06T14:01:02.030042-07:00'

在这两种情况下,datetime对象都是相互兼容的。因此,如果你对它们进行减法运算,你将得到一个时间差为0:

在任何地方的Python3.6+服务器上:

>>> (datetime.fromisoformat('2021-09-06T14:01:02.030042-07:00') -
...  datetime.fromisoformat('2021-09-07T01:01:02.030042+04:00'))
datetime.timedelta(0)

6
这是稍微简洁一些的 @vbem 解决方案版本:
from datetime import datetime as dt

dt.utcnow().astimezone().tzinfo

唯一实质性的区别是我将datetime.datetime.now(datetime.timezone.utc)替换为datetime.datetime.utcnow()。为了简洁起见,我还将datetime.datetime别名为dt

就我的目的而言,我想要以秒为单位的UTC偏移量。如下所示:

dt.utcnow().astimezone().utcoffset().total_seconds()

2
ValueError: astimezone() 无法应用于一个naive datetime(Python 3.4) - pbarill
1
是的,在我使用这个解决方案发布了一些代码后,使用Linux的用户报告了同样的错误。如果我没记错的话,根本原因是操作系统时间没有配置到时区(即它是“naive” UTC),这会破坏代码。除了将该代码包装在try/except块中之外,我还没有研究过其他解决方案。 - David Marx
好的,谢谢。从那时起,我放弃了使用纯stdlib解决方案的想法,转而使用pytztzlocal。当Python已经内置了这么多功能时,我对添加第三方依赖库持谨慎态度,但是在处理时区时,显然需要外部帮助。 - pbarill

6

以下是一种仅使用标准库获取本地时区的方法(仅适用于*nix环境):

>>> '/'.join(os.path.realpath('/etc/localtime').split('/')[-2:])
'Australia/Sydney'

您可以使用此功能创建一个pytz时区:
>>> import pytz
>>> my_tz_name = '/'.join(os.path.realpath('/etc/localtime').split('/')[-2:])
>>> my_tz = pytz.timezone(my_tz_name)
>>> my_tz
<DstTzInfo 'Australia/Sydney' LMT+10:05:00 STD>

你可以将这些模式应用于 datetime

>>> import datetime
>>> now = datetime.datetime.now()
>>> now
datetime.datetime(2014, 9, 3, 9, 23, 24, 139059)

>>> now.replace(tzinfo=my_tz)
>>> now
datetime.datetime(2014, 9, 3, 9, 23, 24, 139059, tzinfo=<DstTzInfo 'Australia/Sydney' LMT+10:05:00 STD>)

3
为了避免重复造轮子,请查看tzlocal源代码 - jfs
这将使用TZ变量来覆盖环境时区,导致其失效。 - Karsten
1
在 MacOS 上是否存在 '/etc/localtime'? - Lucas
或者在Windows上?;-) - quant_dev
2
@Lucas,是的,它可以,我刚刚检查了一下,我使用的是macOS Catalina 10.15.7。 - Johan Walles

4

避免非标准模块(似乎是datetime模块缺少的方法):

from datetime import datetime
utcOffset_min = int(round((datetime.now() - datetime.utcnow()).total_seconds())) / 60   # round for taking time twice
utcOffset_h = utcOffset_min / 60
assert(utcOffset_min == utcOffset_h * 60)   # we do not handle 1/2 h timezone offsets

print 'Local time offset is %i h to UTC.' % (utcOffset_h)

2
如果您不关心过去的夏令时或UTC偏移量(就像您的解决方案所示),您可以直接使用-time.timezone - jfs

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