如何使日期时间对象感知(不幼稚)



我需要做什么

我有一个无法识别时区的日期时间对象,

我需要向其添加一个时区,以便能够将其与其他时区感知日期时间对象进行比较。我不想将我的整个应用程序转换为不知道这个遗留案例的时区。

我尝试过什么

首先,演示问题:

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

如您所见,替换似乎设置了 tzinfo,但不能使对象感知。 我正准备回退到在解析之前修改输入字符串以具有时区(如果这很重要,我正在使用 dateutil 进行解析(,但这似乎非常笨拙。

另外,我在Python 2.6和Python 2.7中都尝试过这个,结果是一样的。

上下文

我正在为一些数据文件编写解析器。我需要支持一种旧格式,其中日期字符串没有时区指示器。我已经修复了数据源,但我仍然需要支持旧数据格式。由于各种业务BS原因,无法选择一次性转换旧数据。虽然总的来说,我不喜欢硬编码默认时区的想法,但在这种情况下,它似乎是最好的选择。我有理由确信所有有问题的遗留数据都是 UTC 格式,因此我准备接受在这种情况下默认为 UTC 的风险。

通常,要使天真的日期时间时区感知,请使用 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

所有这些示例都使用外部模块,但仅使用 datetime 模块即可获得相同的结果,如以下 SO 答案所示:

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

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

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

from django.utils import timezone
dt_aware = timezone.make_aware(dt_unaware, timezone.get_current_timezone())

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'

维基百科可用时区列表

<小时 />

Windows没有系统时区数据库,因此这里需要一个额外的包:

pip install tzdata  
<小时 />

有一个向后移植允许在Python 3.6到3.8中使用zoneinfo

pip install backports.zoneinfo

然后:

from backports.zoneinfo import ZoneInfo

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

如果你只是按名称,你可能会推断 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>)

使用 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.replaceNone原始时区信息相同。

这编纂了@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。

适用于那些只想让时区知道日期时间的人

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

对于那些想要从 Python 3.9 stdlib 开始使用非 UTC 时区的日期时间的人

import datetime
from zoneinfo import ZoneInfo
datetime.datetime(2019, 12, 7, tzinfo=ZoneInfo("America/Los_Angeles")) 
另一种

datetime对象不幼稚的方法:

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

在时区之间切换

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

Python 的新手,我遇到了同样的问题。我发现这个解决方案非常简单,对我来说它工作得很好(Python 3.6(:

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

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

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

以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')
  • 根据文档datetime.utcnow

    • 警告: 由于许多datetime方法将朴素datetime对象视为本地时间,因此最好使用感知日期时间来表示 UTC 时间。因此,以 UTC 格式创建表示当前时间的对象的建议方法是调用 datetime.now(timezone.utc)

    • 此选项在其他答案中涵盖,但文档引用未涵盖。
  • 根据文档datetime.utcfromtimestamp

    • 警告:因为天真datetime对象...通过调用datetime.fromtimestamp(timestamp, tz=timezone.utc) .

    • 此选项未在其他答案中涵盖。
  • python 3.11.2测试

from datetime import datetime, timezone
import time  # for timestamp
import pytz  # for aware comparison
now = datetime.now(tz=timezone.utc)
aware = datetime(now.year, now.month, now.day, now.hour, now.minute, now.second, now.microsecond, tzinfo=pytz.UTC)
print(now == aware)
[out]: True
fts = datetime.fromtimestamp(time.time(), tz=timezone.utc)
aware = datetime(fts.year, fts.month, fts.day, fts.hour, fts.minute, fts.second, fts.microsecond, tzinfo=pytz.UTC)
print(fts == aware)
[out]: True
<</div> div class="one_answers">

在所有提到的方法中,当它是Unix时间戳时,有一个非常简单的解决方案使用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')

相关内容

  • 没有找到相关文章

最新更新