如何在Python中获得datetime.today()的值,即“timezone aware”?

我试图从datetime.datetime.today()的值中减去一个日期值,以计算某事物发生的时间。但它抱怨道:

TypeError: can't subtract offset-naive and offset-aware datetimes

datetime.datetime.today()的返回值似乎不是“时区感知”,而我的其他日期值是。我如何从datetime.datetime.today()获得一个时区感知的返回值?

理想的解决方案是让它自动知道时区。

现在,它给我的时间是当地时间,恰好是PST,即UTC - 8小时。最坏的情况下,是否有一种方法可以手动输入一个时区值到datetime.datetime.today()返回的datetime对象中,并将其设置为UTC-8?

529433 次浏览

pytz是一个Python库,允许使用Python 2.3或更高版本进行精确的跨平台时区计算。

对于stdlib,这是不可能的。

所以上可以看到类似的问题。

在标准库中,如果不创建自己的时区类,就没有跨平台的方法来创建感知时区。(编辑: Python 3.9在标准库中引入了zoneinfo,它提供了此功能。)

在Windows上,有win32timezone.utcnow(),但它是pywin32的一部分。我宁愿建议使用pytz图书馆,它有一个不断更新的大多数时区的数据库。

使用本地时区可能非常棘手(参见“进一步阅读”;因此,您可能更希望在整个应用程序中使用UTC,特别是在计算两个时间点之间的差值这样的算术操作中。

你可以像这样得到当前的日期/时间:

import pytz
from datetime import datetime
datetime.utcnow().replace(tzinfo=pytz.utc)

注意,datetime.today()datetime.now()返回的是当地的时间,而不是UTC时间,所以对它们应用.replace(tzinfo=pytz.utc)是不正确的。

另一种很好的方法是:

datetime.now(pytz.utc)

它更短一点,做同样的事情。


进一步阅读/观看为什么在许多情况下更喜欢UTC:

另一种构造表示当前时间的时区感知datetime对象的方法:

import datetime
import pytz


pytz.utc.localize( datetime.datetime.utcnow() )

你可以通过运行以下命令从PyPI安装pytz:

$ pipenv install pytz

获取特定时区的当前时间:

import datetime
import pytz
my_date = datetime.datetime.now(pytz.timezone('US/Pacific'))

记得先安装pytz

如果你正在使用Django,你可以设置不感知tz的日期(只能设置UTC)。

评论在settings.py中的下面一行:

USE_TZ = True

下面是一个适用于Python 2和Python 3的标准库解决方案:

from datetime import datetime


now = datetime.now(utc) # Timezone-aware datetime.utcnow()
today = datetime(now.year, now.month, now.day, tzinfo=utc) # Midnight

其中today是一个感知datetime实例,表示UTC中的一天的开始(午夜),而utc是一个tzinfo对象(文档中的示例):

from datetime import tzinfo, timedelta


ZERO = timedelta(0)


class UTC(tzinfo):
def utcoffset(self, dt):
return ZERO


def tzname(self, dt):
return "UTC"


def dst(self, dt):
return ZERO


utc = UTC()

相关:有几种方法可以获得给定UTC时间的午夜(一天的开始)性能比较。 注意:它更复杂,到获取一个具有非固定UTC偏移量的时区的午夜.

使用dateutil,如Python datetime.datetime.now() that is timezone aware . >所述:

from dateutil.tz import tzlocal
# Get the current date/time with the timezone.
now = datetime.datetime.now(tzlocal())

utc时区中获取一个能识别时区的日期就足以让日期减法工作了。

但是如果你想要在当前时区中有一个时区感知的日期,tzlocal是最好的方法:

from tzlocal import get_localzone  # pip install tzlocal
from datetime import datetime
datetime.now(get_localzone())

PS dateutil有一个类似的函数(dateutil.tz.tzlocal)。但是尽管共享名称,但它有一个完全不同的代码库,如J.F. Sebastian的指出可能会给出错误的结果。

下面是用stdlib生成它的一种方法:

import time
from datetime import datetime


FORMAT='%Y-%m-%dT%H:%M:%S%z'
date=datetime.strptime(time.strftime(FORMAT, time.localtime()),FORMAT)

日期将存储本地日期和UTC偏移量,而不是UTC时区的日期,因此如果需要标识日期是在哪个时区生成的,可以使用此解决方案。在这个例子中,在我的本地时区:

date
datetime.datetime(2017, 8, 1, 12, 15, 44, tzinfo=datetime.timezone(datetime.timedelta(0, 7200)))


date.tzname()
'UTC+02:00'

关键是将%z指令添加到表示形式FORMAT中,以指示生成的时间结构的UTC偏移量。其他表示格式可以在datetime模块文档中查阅

如果需要UTC时区的日期,可以用time.gmtime ()替换time.localtime ()

date=datetime.strptime(time.strftime(FORMAT, time.gmtime()),FORMAT)


date
datetime.datetime(2017, 8, 1, 10, 23, 51, tzinfo=datetime.timezone.utc)


date.tzname()
'UTC'

编辑

这只适用于python3。z指令在python 2 _strptime.py代码上不可用

在Python 3.2+中:datetime.timezone.utc:

标准库使得将UTC指定为时区变得更加容易:

>>> import datetime
>>> datetime.datetime.now(datetime.timezone.utc)
datetime.datetime(2020, 11, 27, 14, 34, 34, 74823, tzinfo=datetime.timezone.utc)

你也可以使用astimezone获取一个包含本地时间偏移量的datetime:

>>> datetime.datetime.now(datetime.timezone.utc).astimezone()
datetime.datetime(2020, 11, 27, 15, 34, 34, 74823, tzinfo=datetime.timezone(datetime.timedelta(seconds=3600), 'CET'))

(在Python 3.6+中,可以将最后一行缩短为:datetime.datetime.now().astimezone())

如果你想要一个只使用标准库并且在Python 2和Python 3中都能工作的解决方案,请参阅jfs的回答

在Python 3.9+: zoneinfo中使用IANA时区数据库:

在Python 3.9中,你可以使用标准库zoneinfo指定特定的时区,如下所示:

>>> from zoneinfo import ZoneInfo
>>> datetime.datetime.now(ZoneInfo("America/Los_Angeles"))
datetime.datetime(2020, 11, 27, 6, 34, 34, 74823, tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles'))

zoneinfo从操作系统获取它的时区数据库,或者从第一方PyPI包tzdata(如果可用的话)获取。

如果你在python中获得当前时间和日期,然后在python中导入日期和时间,pytz包后你将获得当前日期和时间,如..

from datetime import datetime
import pytz
import time
str(datetime.strftime(datetime.now(pytz.utc),"%Y-%m-%d %H:%M:%S%t"))

只使用标准库的一行程序从Python 3.3开始工作。你可以使用astimezone(作为由johnchen902建议)获得一个本地时区感知的datetime对象:

from datetime import datetime, timezone


aware_local_now = datetime.now(timezone.utc).astimezone()


print(aware_local_now)
# 2020-03-03 09:51:38.570162+01:00


print(repr(aware_local_now))
# datetime.datetime(2020, 3, 3, 9, 51, 38, 570162, tzinfo=datetime.timezone(datetime.timedelta(0, 3600), 'CET'))

另一种选择,在我看来是更好的一个,是使用Pendulum而不是pytz。考虑下面的简单代码:

>>> import pendulum


>>> dt = pendulum.now().to_iso8601_string()
>>> print (dt)
2018-03-27T13:59:49+03:00
>>>

要安装Pendulum并查看它们的文档,请执行在这里。它有大量的选项(比如简单的ISO8601, RFC3339和许多其他格式的支持),更好的性能和更简单的代码。

使用如下所示的时区来获得一个支持时区的日期时间。默认为UTC:

from django.utils import timezone
today = timezone.now()

特别是对于非utc时区:

唯一具有自己方法的时区是timezone.utc,但如果需要,可以使用timedelta &timezone,并使用.replace强制它。

In [1]: from datetime import datetime, timezone, timedelta


In [2]: def force_timezone(dt, utc_offset=0):
...:     return dt.replace(tzinfo=timezone(timedelta(hours=utc_offset)))
...:


In [3]: dt = datetime(2011,8,15,8,15,12,0)


In [4]: str(dt)
Out[4]: '2011-08-15 08:15:12'


In [5]: str(force_timezone(dt, -8))
Out[5]: '2011-08-15 08:15:12-08:00'

使用timezone(timedelta(hours=n))作为时区是这里真正的银弹,它还有很多其他有用的应用。

下面是一个使用可读时区的解决方案,它与today()一起工作:

from pytz import timezone


datetime.now(timezone('Europe/Berlin'))
datetime.now(timezone('Europe/Berlin')).today()

所有时区列表如下:

import pytz


pytz.all_timezones
pytz.common_timezones # or

来自howchoo的Tyler写了一篇非常棒的文章,帮助我更好地了解了Datetime对象,链接如下

Working with Datetime

实际上,我只是在两个datetime对象的末尾添加了以下内容

.replace(tzinfo=pytz.utc)

例子:

import pytz
import datetime from datetime


date = datetime.now().replace(tzinfo=pytz.utc)

try pnp_datetime,所有被使用和返回的时间都是带时区的,并且不会导致任何偏移naive和偏移感知问题。

>>> from pnp_datetime.pnp_datetime import Pnp_Datetime
>>>
>>> Pnp_Datetime.utcnow()
datetime.datetime(2020, 6, 5, 12, 26, 18, 958779, tzinfo=<UTC>)

需要强调的是,从Python 3.6开始,你只需要标准库来获得一个表示本地时间的时区感知datetime对象(操作系统的设置)。使用astimezone ()

import datetime


datetime.datetime(2010, 12, 25, 10, 59).astimezone()
# e.g.
# datetime.datetime(2010, 12, 25, 10, 59, tzinfo=datetime.timezone(datetime.timedelta(seconds=3600), 'Mitteleuropäische Zeit'))


datetime.datetime(2010, 12, 25, 12, 59).astimezone().isoformat()
# e.g.
# '2010-12-25T12:59:00+01:00'


# I'm on CET/CEST

(见@johnchen902的评论)。

注意,这里有一个小警告,不要期望任何“dst意识”;来自timedelta时区。