如何在python中识别未知的日期时间时区


504

我需要做什么

我有一个不带时区的datetime对象,我需要向其添加一个时区,以便能够将其与其他时区可感知的datetime对象进行比较。对于这一旧情况,我不想将我的整个应用程序转换为时区。

我尝试过的

首先,演示该问题:

Python 2.6.1 (r261:67515, Jun 24 2010, 21:47:49) 
[GCC 4.2.1 (Apple Inc. build 5646)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import datetime
>>> import pytz
>>> unaware = datetime.datetime(2011,8,15,8,15,12,0)
>>> unaware
datetime.datetime(2011, 8, 15, 8, 15, 12)
>>> aware = datetime.datetime(2011,8,15,8,15,12,0,pytz.UTC)
>>> aware
datetime.datetime(2011, 8, 15, 8, 15, 12, tzinfo=<UTC>)
>>> aware == unaware
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't compare offset-naive and offset-aware datetimes

首先,我尝试了astimezone:

>>> unaware.astimezone(pytz.UTC)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: astimezone() cannot be applied to a naive datetime
>>>

这次失败并不令人惊讶,因为它实际上是在尝试进行转换。替换似乎是一个更好的选择(根据Python:如何获取“时区感知”的datetime.today()值?):

>>> unaware.replace(tzinfo=pytz.UTC)
datetime.datetime(2011, 8, 15, 8, 15, 12, tzinfo=<UTC>)
>>> unaware == aware
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't compare offset-naive and offset-aware datetimes
>>> 

但是正如您所看到的,replace似乎设置了tzinfo,但没有使对象知道。我准备回过头来解析输入字符串以在解析它之前有一个时区(如果重要的话,我正在使用dateutil进行解析),但这似乎令人难以置信。

另外,我在python 2.6和python 2.7中都尝试过,结果相同。

语境

我正在为某些数据文件编写解析器。我需要支持一种旧格式,其中日期字符串没有时区指示符。我已经修复了数据源,但是我仍然需要支持旧数据格式。由于各种业务BS的原因,不能一次转换旧数据。通常,我不喜欢对默认时区进行硬编码的想法,在这种情况下,这似乎是最好的选择。我完全有把握地知道所有有问题的旧数据都位于UTC中,因此在这种情况下,我准备接受默认设置的风险。


1
unaware.replace()None如果正在unaware就地修改对象,则将返回。REPL显示在此.replace()返回一个新datetime对象。
jfs

3
我来这里时需要的是:import datetime; datetime.datetime.now(datetime.timezone.utc)
Martin Thoma

1
@MartinThoma我将使用命名的tzarg使其更具可读性:datetime.datetime.now(tz=datetime.timezone.utc)
Acumenus

Answers:


591

通常,要使原始的datetime时区感知,请使用localize方法

import datetime
import pytz

unaware = datetime.datetime(2011, 8, 15, 8, 15, 12, 0)
aware = datetime.datetime(2011, 8, 15, 8, 15, 12, 0, pytz.UTC)

now_aware = pytz.utc.localize(unaware)
assert aware == now_aware

对于UTC时区,localize由于没有夏令时计算可处理,因此实际上没有必要使用:

now_aware = unaware.replace(tzinfo=pytz.UTC)

作品。(.replace返回一个新的日期时间;它不会修改unaware。)


10
好吧,我觉得很傻。替换返回一个新的日期时间。它说在文档中也是如此,而我完全错过了。谢谢,这正是我想要的。
Mark Tozzi

2
“替换返回新的日期时间。” 是的 REPL给您的提示是它正在向您显示返回的值。:)
卡尔·克内希特尔

谢谢,我曾经使用过dt_aware至unware dt_unware = datetime.datetime(*(dt_aware.timetuple()[:6])),
Sérgio

4
如果时区不是UTC,则不要直接使用构造函数:aware = datetime(..., tz).localize()而改用。
jfs

1
值得一提的是,当地时间可能不明确。tz.localize(..., is_dst=None)断言不是。
jfs

166

所有这些示例都使用一个外部模块,但是您可以仅使用datetime模块来达到相同的结果,SO答案中也有介绍:

from datetime import datetime
from datetime import timezone

dt = datetime.now()
dt.replace(tzinfo=timezone.utc)

print(dt.replace(tzinfo=timezone.utc).isoformat())
'2017-01-12T22:11:31+00:00'

更少的依赖项,没有pytz问题。

注意:如果您希望将其与python3和python2一起使用,则也可以将其用于时区导入(针对UTC进行硬编码):

try:
    from datetime import timezone
    utc = timezone.utc
except ImportError:
    #Hi there python2 user
    class UTC(tzinfo):
        def utcoffset(self, dt):
            return timedelta(0)
        def tzname(self, dt):
            return "UTC"
        def dst(self, dt):
            return timedelta(0)
    utc = UTC()

10
对于防止pytz问题,非常好的答案,我很高兴我向下滚动了一下!pytz确实不想在我的远程服务器上处理:)
Tregoreg '17

7
请注意,该方法from datetime import timezone适用于py3,但不适用于py2.7。
7yl4r

11
您应该注意,dt.replace(tzinfo=timezone.utc)返回的是新的日期时间,它不会dt在原地进行修改。(我将进行编辑以显示此内容)。
Blairg23年

2
您怎么可能不使用timezone.utc,而是将其他时区作为字符串提供(例如“ America / Chicago”)?
–umpkin

2
@bumpkin迟到总比没有好,我猜:tz = pytz.timezone('America/Chicago')
弗洛里安(Florian)

82

我曾经使用过从dt_aware到dt_unaware

dt_unaware = dt_aware.replace(tzinfo=None)

和dt_unware到dt_aware

from pytz import timezone
localtz = timezone('Europe/Lisbon')
dt_aware = localtz.localize(dt_unware)

但之前回答也是一个很好的解决方案。


2
localtz.localize(dt_unware, is_dst=None)如果dt_unware表示当地时间不存在或模棱两可,则可以使用它引发异常(注意:您的答案的上一个修订版中的localtzUTC 所在的版本中没有这样的问题,因为UTC没有DST转换
jfs 2014年

@JF塞巴斯蒂安(JF Sebastian),发表第一条评论
焦(Sérgio)

1
感谢您展示转换的两个方向。
克里斯蒂安·

41

我在Django中使用以下语句将无意识的时间转换为有意识的时间:

from django.utils import timezone

dt_aware = timezone.make_aware(dt_unaware, timezone.get_current_timezone())

2
我确实喜欢此解决方案(+1),但是它依赖于Django,而这并不是他们想要的(-1)。=)
mkoistinen

3
您实际上不是第二个参数。该默认参数(无)将意味着本地时区被隐含使用。与DST相同(这是第三个参数
Oli,2013年

14

我同意之前的回答,如果可以开始使用UTC,也可以。但我认为这也是人们使用tz感知值(其日期时间具有非UTC本地时区)的常见情况

如果只是按名称命名,则可能会推断replace()将适用,并产生正确的日期时间感知对象。不是这种情况。

replace(tzinfo = ...)的行为似乎是随机的。因此,它是没有用的。不要使用这个!

本地化是正确使用的功能。例:

localdatetime_aware = tz.localize(datetime_nonaware)

或更完整的示例:

import pytz
from datetime import datetime
pytz.timezone('Australia/Melbourne').localize(datetime.now())

给我一个当前本地时间的时区感知日期时间值:

datetime.datetime(2017, 11, 3, 7, 44, 51, 908574, tzinfo=<DstTzInfo 'Australia/Melbourne' AEDT+11:00:00 DST>)

3
这需要更多的投票,如果尝试replace(tzinfo=...)在非UTC的时区进行操作,则会浪费您的日期时间。我得到-07:53而不是-08:00例如。参见stackoverflow.com/a/13994611/1224827
Blairg23年

您可以举一个replace(tzinfo=...)具有意外行为的可复制示例吗?
xjcl

11

使用dateutil.tz.tzlocal()来获取时区在你的使用datetime.datetime.now()datetime.datetime.astimezone()

from datetime import datetime
from dateutil import tz

unlocalisedDatetime = datetime.now()

localisedDatetime1 = datetime.now(tz = tz.tzlocal())
localisedDatetime2 = datetime(2017, 6, 24, 12, 24, 36, tz.tzlocal())
localisedDatetime3 = unlocalisedDatetime.astimezone(tz = tz.tzlocal())
localisedDatetime4 = unlocalisedDatetime.replace(tzinfo = tz.tzlocal())

请注意,这datetime.astimezone将首先将您的datetime对象转换为UTC,然后转换为时区,这datetime.replace与使用原始时区信息为进行调用相同None


1
如果要使其成为UTC:.replace(tzinfo=dateutil.tz.UTC)
Martin Thoma,

2
一次少进口商品:.replace(tzinfo=datetime.timezone.utc)
kubanczyk

9

这将@Sérgio和@unutbu的答案整理成代码。它将与pytz.timezone对象或IANA时区字符串“兼容” 。

def make_tz_aware(dt, tz='UTC', is_dst=None):
    """Add timezone information to a datetime object, only if it is naive."""
    tz = dt.tzinfo or tz
    try:
        tz = pytz.timezone(tz)
    except AttributeError:
        pass
    return tz.localize(dt, is_dst=is_dst) 

似乎应该做什么datetime.localize()(或.inform().awarify()),接受tz参数的字符串和时区对象,如果未指定时区,则默认为UTC。


1
谢谢,这帮助我将原始日期时间对象“商标”为“ UTC”,而无需系统首先假定它是本地时间,然后重新计算值!
Nikhil VJ

2

Python 3.9添加了zoneinfo模块,因此现在仅需要标准库!

from zoneinfo import ZoneInfo
from datetime import datetime
unaware = datetime(2020, 10, 31, 12)

附加时区:

>>> unaware.replace(tzinfo=ZoneInfo('Asia/Tokyo'))
datetime.datetime(2020, 10, 31, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='Asia/Tokyo'))
>>> str(_)
'2020-10-31 12:00:00+09:00'

附加系统的本地时区:

>>> unaware.replace(tzinfo=ZoneInfo('localtime'))
datetime.datetime(2020, 10, 31, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='localtime'))
>>> str(_)
'2020-10-31 12:00:00+01:00'

随后,它将正确转换为其他时区:

>>> unaware.replace(tzinfo=ZoneInfo('localtime')).astimezone(ZoneInfo('Asia/Tokyo'))
datetime.datetime(2020, 10, 31, 20, 0, tzinfo=backports.zoneinfo.ZoneInfo(key='Asia/Tokyo'))
>>> str(_)
'2020-10-31 20:00:00+09:00'

可用时区的维基百科列表


有一个backport允许在Python 3.6至3.8中使用

sudo pip install backports.zoneinfo

然后:

from backports.zoneinfo import ZoneInfo

0

以unutbu的答案格式;我制作了一个实用程序模块,以更直观的语法处理此类问题。可以通过pip安装。

import datetime
import saturn

unaware = datetime.datetime(2011, 8, 15, 8, 15, 12, 0)
now_aware = saturn.fix_naive(unaware)

now_aware_madrid = saturn.fix_naive(unaware, 'Europe/Madrid')

0

对于那些只想使时区知道日期时间的人

import datetime
import pytz

datetime.datetime(2019, 12, 7, tzinfo=pytz.UTC)

0

对Python来说还很陌生,我遇到了同样的问题。我发现此解决方案非常简单,对我来说也可以正常工作(Python 3.6):

unaware=parser.parse("2020-05-01 0:00:00")
aware=unaware.replace(tzinfo=tz.tzlocal()).astimezone(tz.tzlocal())

0

在时区之间切换

import pytz
from datetime import datetime

other_tz = pytz.timezone('Europe/Madrid')

# From random aware datetime...
aware_datetime = datetime.utcnow().astimezone(other_tz)
>> 2020-05-21 08:28:26.984948+02:00

# 1. Change aware datetime to UTC and remove tzinfo to obtain an unaware datetime
unaware_datetime = aware_datetime.astimezone(pytz.UTC).replace(tzinfo=None)
>> 2020-05-21 06:28:26.984948

# 2. Set tzinfo to UTC directly on an unaware datetime to obtain an utc aware datetime
aware_datetime_utc = unaware_datetime.replace(tzinfo=pytz.UTC)
>> 2020-05-21 06:28:26.984948+00:00

# 3. Convert the aware utc datetime into another timezone
reconverted_aware_datetime = aware_datetime_utc.astimezone(other_tz)
>> 2020-05-21 08:28:26.984948+02:00

# Initial Aware Datetime and Reconverted Aware Datetime are equal
print(aware_datetime1 == aware_datetime2)
>> True
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.