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

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

我需要做什么

我有一个不带时区的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中,因此在这种情况下,我准备接受默认设置的风险。

What I need to do

I have a timezone-unaware datetime object, to which I need to add a time zone in order to be able to compare it with other timezone-aware datetime objects. I do not want to convert my entire application to timezone unaware for this one legacy case.

What I’ve Tried

First, to demonstrate the problem:

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

First, I tried 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
>>>

It’s not terribly surprising this failed, since it’s actually trying to do a conversion. Replace seemed like a better choice (as per Python: How to get a value of datetime.today() 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
>>> 

But as you can see, replace seems to set the tzinfo, but not make the object aware. I’m getting ready to fall back to doctoring the input string to have a timezone before parsing it (I’m using dateutil for parsing, if that matters), but that seems incredibly kludgy.

Also, I’ve tried this in both python 2.6 and python 2.7, with the same results.

Context

I am writing a parser for some data files. There is an old format I need to support where the date string does not have a timezone indicator. I’ve already fixed the data source, but I still need to support the legacy data format. A one time conversion of the legacy data is not an option for various business BS reasons. While in general, I do not like the idea of hard-coding a default timezone, in this case it seems like the best option. I know with reasonable confidence that all the legacy data in question is in UTC, so I’m prepared to accept the risk of defaulting to that in this case.


回答 0

通常,要使原始的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。)

In general, to make a naive datetime timezone-aware, use the 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

For the UTC timezone, it is not really necessary to use localize since there is no daylight savings time calculation to handle:

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

works. (.replace returns a new datetime; it does not modify unaware.)


回答 1

所有这些示例都使用一个外部模块,但是您可以仅使用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()

All of these examples use an external module, but you can achieve the same result using just the datetime module, as also presented in this SO answer:

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'

Fewer dependencies and no pytz issues.

NOTE: If you wish to use this with python3 and python2, you can use this as well for the timezone import (hardcoded for 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()

回答 2

我曾经使用过从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)

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

I had use from dt_aware to dt_unaware

dt_unaware = dt_aware.replace(tzinfo=None)

and dt_unware to dt_aware

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

but answer before is also a good solution.


回答 3

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

from django.utils import timezone

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

I use this statement in Django to convert an unaware time to an aware:

from django.utils import timezone

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

回答 4

我同意之前的回答,如果可以开始使用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>)

I agree with the previous answers, and is fine if you are ok to start in UTC. But I think it is also a common scenario for people to work with a tz aware value that has a datetime that has a non UTC local timezone.

If you were to just go by name, one would probably infer replace() will be applicable and produce the right datetime aware object. This is not the case.

the replace( tzinfo=… ) seems to be random in its behaviour. It is therefore useless. Do not use this!

localize is the correct function to use. Example:

localdatetime_aware = tz.localize(datetime_nonaware)

Or a more complete example:

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

gives me a timezone aware datetime value of the current local time:

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

回答 5

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

Use dateutil.tz.tzlocal() to get the timezone in your usage of datetime.datetime.now() and 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())

Note that datetime.astimezone will first convert your datetime object to UTC then into the timezone, which is the same as calling datetime.replace with the original timezone information being None.


回答 6

这将@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。

This codifies @Sérgio and @unutbu’s answers. It will “just work” with either a pytz.timezone object or an IANA Time Zone string.

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) 

This seems like what datetime.localize() (or .inform() or .awarify()) should do, accept both strings and timezone objects for the tz argument and default to UTC if no time zone is specified.


回答 7

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

Python 3.9 adds the zoneinfo module so now only the the standard library is needed!

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

Attach a timezone:

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

Attach the system’s local timezone:

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

Subsequently it is properly converted to other timezones:

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


There is a backport to allow use in Python 3.6 to 3.8:

sudo pip install backports.zoneinfo

Then:

from backports.zoneinfo import ZoneInfo

回答 8

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

In the format of unutbu’s answer; I made a utility module that handles things like this, with more intuitive syntax. Can be installed with 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')

回答 9

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

import datetime
import pytz

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

for those that just want to make a timezone aware datetime

import datetime
import pytz

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

回答 10

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

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

quite new to Python and I encountered the same issue. I find this solution quite simple and for me it works fine (Python 3.6):

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

回答 11

在时区之间切换

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

Changing between timezones

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