我需要做什么
我有一个不知道时区的 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,所以我准备接受在这种情况下违约的风险。
unaware
对象,unaware.replace()
将返回 None
。 REPL 显示 .replace()
在此处返回一个新的 datetime
对象。
import datetime; datetime.datetime.now(datetime.timezone.utc)
tz
arg 以提高可读性:datetime.datetime.now(tz=datetime.timezone.utc)
astimezone()
现在可以(从 3.6 开始)在一个天真的对象上调用,并且它的参数可以省略(从 3.3 开始),所以解决方案就像 unaware.astimezone()
一样简单
通常,要使一个简单的 datetime 时区感知,请使用 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
。)
所有这些示例都使用外部模块,但您可以仅使用 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
。 (我将编辑以显示这一点)。
tz = pytz.timezone('America/Chicago')
我在 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 转换
我在 Django 中使用此语句将不知道的时间转换为有意识的时间:
from django.utils import timezone
dt_aware = timezone.make_aware(dt_unaware, timezone.get_current_timezone())
dt_aware = timezone.make_aware(dt_unaware, timezone.utc)
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
pip install tzdata
pip install tzdata
。 Mac 应该开箱即用。如果您的应用程序需要时区数据,则无条件安装 tzdata
(因为系统数据优先于 tzdata
)不会有任何坏处。
tzdata; sys_platform == "win32"
(来自:stackoverflow.com/a/35614580/705296)
我同意以前的答案,如果您可以从 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>)
replace(tzinfo=...)
会破坏您的日期时间。例如,我得到了 -07:53
而不是 -08:00
。请参阅stackoverflow.com/a/13994611/1224827
replace(tzinfo=...)
出现意外行为的可重现示例?
replace()
,但它没有用。
使用 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
相同。
.replace(tzinfo=dateutil.tz.UTC)
.replace(tzinfo=datetime.timezone.utc)
这编纂了@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。
对于那些只想制作时区感知日期时间的人
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"))
aware = datetime.datetime(2011, 8, 15, 8, 15, 12, 0, pytz.UTC)
与您编写的相同,它们刚刚将参数命名为 no?
让 datetime
对象不幼稚的另一种方法:
>>> from datetime import datetime, timezone
>>> datetime.now(timezone.utc)
datetime.datetime(2021, 5, 1, 22, 51, 16, 219942, tzinfo=datetime.timezone.utc)
对 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
datetime.utcnow()
不返回时区感知日期时间(与其名称相反)
以 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')
在时区之间切换
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
最重要的是,当它是 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')
不定期副业成功案例分享
aware = datetime(..., tz)
,而是使用.localize()
。tz.localize(..., is_dst=None)
断言它不是 。