【问题标题】:How to make a timezone aware datetime object in Python?如何在 Python 中创建时区感知日期时间对象?
【发布时间】:2011-10-27 06:25:44
【问题描述】:

我需要做什么

我有一个不知道时区的 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
>>>

这并不奇怪,因为它实际上是在尝试进行转换。替换似乎是一个更好的选择(根据How do I get a value of datetime.today() in Python that is "timezone aware"?):

>>> 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,因此我准备接受在这种情况下默认使用 UTC 的风险。

【问题讨论】:

  • unaware.replace() 将返回 None 如果它正在就地修改 unaware 对象。 REPL 显示 .replace() 在此处返回一个新的 datetime 对象。
  • 我来这里时需要什么:import datetime; datetime.datetime.now(datetime.timezone.utc)
  • @MartinThoma 我会使用命名的tz arg 以提高可读性:datetime.datetime.now(tz=datetime.timezone.utc)
  • astimezone() 现在可以(从 3.6 开始)在一个 naive 对象上调用,并且可以省略它的参数(从 3.3 开始),所以解决方法就像 unaware.astimezone() 一样简单

标签: python datetime timezone python-datetime pytz


【解决方案1】:

一般来说,要让一个简单的日期时间时区感知,请使用localize method

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。)

【讨论】:

  • 好吧,我觉得很傻。 Replace 返回一个新的日期时间。它也在文档中说,我完全错过了。谢谢,这正是我想要的。
  • "替换返回一个新的日期时间。"是的。 REPL 给您的提示是它向您显示返回的值。 :)
  • 如果时区不是UTC,那么不要直接使用构造函数:aware = datetime(..., tz),而是使用.localize()
  • 值得一提的是,当地时间可能不明确。 tz.localize(..., is_dst=None) 断言它不是。
  • putz 有一个错误,将阿姆斯特丹时区设置为与 UTC 相差 20 分钟,这是 1937 年的一些古老时区。你有一份工作 pytz。
【解决方案2】:

所有这些示例都使用外部模块,但您可以仅使用 datetime 模块获得相同的结果,如 this SO answer 中所述:

from datetime import datetime, timezone

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

print(dt.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()

【讨论】:

  • 防止pytz 问题的非常好的答案,我很高兴我向下滚动了一点!确实不想在我的远程服务器上处理pytz :)
  • 请注意,from datetime import timezone 在 py3 中有效,但在 py2.7 中无效。
  • 你应该注意dt.replace(tzinfo=timezone.utc)返回一个新的日期时间,它不会修改dt。 (我将编辑以显示这一点)。
  • 您如何不使用 timezone.utc 提供不同的时区作为字符串(例如“America/Chicago”)?
  • @bumpkin 迟到总比没有好,我猜:tz = pytz.timezone('America/Chicago')
【解决方案3】:

我在 2011 年编写了这个 Python 2 脚本,但从未检查过它是否适用于 Python 3。

我已从 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)

【讨论】:

  • 如果dt_unware 代表不存在或不明确的当地时间,您可以使用localtz.localize(dt_unware, is_dst=None) 引发异常(注意:在您的答案的先前版本中没有这样的问题localtz 是UTC,因为 UTC 没有 DST 转换
  • @J.F.塞巴斯蒂安,应用了第一条评论
  • 感谢您展示转换的两个方向。
  • @Sérgio 当您将该参数放入 .replace 并得到“TypeError: replace() got an unexpected keyword argument 'tzinfo'”时?有什么办法可以解决这个问题?
【解决方案4】:

我在 Django 中使用此语句将不知道的时间转换为有意识的时间:

from django.utils import timezone

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

【讨论】:

  • 我确实喜欢这个解决方案 (+1),但它依赖于 Django,这不是他们想要的 (-1)。 =)
  • 您实际上并没有第二个参数。 default argument (None) 意味着本地时区被隐式使用。与 DST 相同(这是第三个参数_
  • 如果要转换为 UTC:dt_aware = timezone.make_aware(dt_unaware, timezone.utc)
【解决方案5】:

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'

Wikipedia list of available time zones


Windows has no系统时区数据库,所以这里需要一个额外的包:

pip install tzdata  

Python 3.6 到 3.8 中有一个允许使用zoneinfo 的后向端口:

pip install backports.zoneinfo

然后:

from backports.zoneinfo import ZoneInfo

【讨论】:

  • 在Windows上,还需要pip install tzdata
  • @MrFuppes 感谢您的提示!我明天将对此进行测试,并将其作为我的答案。你知道 Mac 上的情况吗?
  • @xjcl 在操作系统不提供时区数据库的任何平台上都需要pip install tzdata。 Mac 应该开箱即用。如果您的应用程序需要时区数据,则无条件安装tzdata(因为系统数据优先于tzdata)并没有什么坏处。
  • @Paul 很不幸,因为我真的希望有一个标准库解决方案
  • @xjcl tzdata 实际上是标准库的一部分(我们称之为“第一方包”),您只需 pip 安装它,因为它的发布节奏比 CPython 快得多。跨度>
【解决方案6】:

我同意之前的答案,如果您可以从 UTC 开始,那很好。但我认为人们使用具有 日期时间且具有非 UTC 本地时区的 tz 感知值也是一种常见情况。

如果您只是按名称进行,可能会推断 replace() 将适用并生成正确的日期时间感知对象。事实并非如此。

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

localize 是正确使用的函数。示例:

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>)

【讨论】:

  • 这需要更多的支持,尝试在 UTC 以外的时区执行 replace(tzinfo=...) 会弄乱您的日期时间。例如,我得到了-07:53 而不是-08:00。见stackoverflow.com/a/13994611/1224827
  • 你能举一个replace(tzinfo=...)出现意外行为的可重现例子吗?
  • 非常感谢。我浪费了很多时间尝试使用replace(),但它没有用。
【解决方案7】:

使用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

【讨论】:

  • 如果你想设置为 UTC:.replace(tzinfo=dateutil.tz.UTC)
  • 少一个导入,只需要:.replace(tzinfo=datetime.timezone.utc)
【解决方案8】:

这将@Sérgio 和@unutbu 的answers 编入代码。它可以与pytz.timezone 对象或IANA Time Zone 字符串一起“正常工作”。

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。

【讨论】:

  • 谢谢,这帮助我将原始日期时间对象“标记”为“UTC”,而无需系统首先假设它是本地时间,然后重新计算值!
【解决方案9】:

对于那些只想制作时区感知日期时间的人

import datetime

datetime.datetime(2019, 12, 7, tzinfo=datetime.timezone.utc)

对于那些想要具有非 utc 时区的日期时间的人从 python 3.9 stdlib 开始

import datetime
from zoneinfo import ZoneInfo

datetime.datetime(2019, 12, 7, tzinfo=ZoneInfo("America/Los_Angeles")) 

【讨论】:

  • 这与主要答案有何不同?
  • 我不在乎使用本地化功能。对于那些试图快速解决问题的人来说,这个答案更简洁(我希望是被接受的答案)。
  • localize 函数只是用来测试 assert 方法的吧?它实际上不需要吗? aware = datetime.datetime(2011, 8, 15, 8, 15, 12, 0, pytz.UTC) 和你写的一样,他们刚刚将参数命名为 no?
  • 断言用于演示如何在时区感知和不感知之间更改日期时间。 I 实际上为了清楚起见提供了关键字参数。如果您愿意,您可以省略关键字并依赖位置参数。不过,kwargs 不太容易出错。
  • tzinfo 命名参数未在接受的答案中提及。
【解决方案10】:

datetime 对象不幼稚的另一种方式:

>>> from datetime import datetime, timezone
>>> datetime.now(timezone.utc)
datetime.datetime(2021, 5, 1, 22, 51, 16, 219942, tzinfo=datetime.timezone.utc)

【讨论】:

    【解决方案11】:

    对 Python 很陌生,我遇到了同样的问题。我发现这个解决方案非常简单,对我来说效果很好(Python 3.6):

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

    【讨论】:

      【解决方案12】:

      这是一个简单的解决方案,可以最大限度地减少对代码的更改:

      from datetime import datetime
      import pytz
      
      start_utc = datetime.utcnow()
      print ("Time (UTC): %s" % start_utc.strftime("%d-%m-%Y %H:%M:%S"))
      

      时间(UTC):09-01-2021 03:49:03

      tz = pytz.timezone('Africa/Cairo')
      start_tz = datetime.now().astimezone(tz)
      print ("Time (RSA): %s" % start_tz.strftime("%d-%m-%Y %H:%M:%S"))
      

      时间(RSA):09-01-2021 05:49:03

      【讨论】:

      • 请注意datetime.utcnow() 不会返回时区感知日期时间(与其名称相反)
      【解决方案13】:

      以 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')
      

      【讨论】:

        【解决方案14】:

        时区转换

        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
        

        【讨论】:

          【解决方案15】:

          在所有提到的方法中,当它是 Unix timestamp 时。这是一个使用 pandas 的非常简单的解决方案。

          import pandas as pd
          unix_timestamp = 1513393355
          pst_tz = pd.Timestamp(unix_timestamp, unit='s', tz='US/Pacific')
          utc_tz = pd.Timestamp(unix_timestamp , unit='s', tz='UTC')
          

          【讨论】:

          • 请不要使用 pandas 时区功能。它基于已弃用的 pytz。
          猜你喜欢
          • 1970-01-01
          • 2016-08-06
          • 2020-10-15
          • 2019-04-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-06-30
          相关资源
          最近更新 更多