python:如何获取”时区感知”的datetime.today()值?

Python: How to get a value of datetime.today() that is “timezone aware”?

我正试图从EDOCX1的值(0)中减去一个日期值,以计算出某物在多久以前。但它抱怨:

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

datetime.today()似乎不"知道时区",而我的另一个日期值是。如何获得时区感知的datetime.today()值?现在它给了我当地时间,正好是太平洋标准时间,也就是UTC-8小时。最坏的情况是,我是否可以手动将时区值输入到由datetime.today()返回的datetime对象中,并将其设置为UTC-8?当然,理想的解决方案是自动知道时区。


在标准库中,没有跨平台的方法可以在不创建自己的时区类的情况下创建感知时区。

在Windows上有win32timezone.utcnow(),但这是pywin32的一部分。我宁愿建议使用PYTZ库,它有一个经常更新的大多数时区数据库。

使用本地时区可能非常困难(请参阅下面的"进一步阅读"链接),因此您可能希望在整个应用程序中使用UTC,尤其是对于计算两个时间点之间的差异等算术操作。

您可以这样获得当前日期/时间:

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

记住,datetime.today()datetime.now()返回本地时间,而不是UTC时间,因此对它们应用.replace(tzinfo=pytz.utc)是不正确的。

另一个很好的方法是:

1
datetime.now(pytz.utc)

稍微短一点,效果也一样。

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

  • PYTZ文件
  • 每个开发人员都应该了解的关于时间的信息——许多实际用例的开发提示
  • 时间和时区的问题-计算机爱好者-有趣的、令人大开眼界的关于使用时区的复杂性的解释(视频)


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

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


在Python3中,标准库使指定UTC作为时区变得更容易:

1
2
3
>>> import datetime
>>> datetime.datetime.now(datetime.timezone.utc)
datetime.datetime(2016, 8, 26, 14, 34, 34, 74823, tzinfo=datetime.timezone.utc)

如果您想要一个只使用标准库的解决方案,并且该解决方案在python 2和python 3中都有效,请参见j.f.sebastien的答案。


下面是一个同时在python 2和3上工作的stdlib解决方案:

1
2
3
4
from datetime import datetime

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

其中,today是一个以UTC表示一天的开始时间(午夜),utc是一个tzinfo对象(来自文档的示例):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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偏移量的时区,获得午夜更为复杂。


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

1
2
3
4
import datetime
import pytz

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


如果使用django,则可以设置不知道TZ的日期(仅限UTC)。

在settings.py中注释以下行:

1
USE_TZ = True


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

1
2
3
4
5
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时区的日期,因此,如果需要确定生成日期的时区,可以使用此解决方案。在本例和本地时区中:

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

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

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

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

1
2
3
4
5
6
7
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代码上不可用


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

对于stdlib,这是不可能的。

在So上看到类似的问题。


为什么不使用这里描述的dateutil:http://joelinoff.com/blog/?P=802

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


您可以使用datetime通过解析使用%z指令(在python 3.3中引入)的time.strftime调用的输出来创建本地timezone,如jcazor所示。您可以使用该timezone创建一个带有一行程序的已知datetime实例:

1
2
3
4
5
6
7
8
import time
from datetime import datetime

aware_local_now = datetime.now(
    tz=datetime.strptime(time.strftime("%z", time.localtime()),"%z").tzinfo)

print(aware_local_now)
2018-03-01 13:41:26.753644-08:00

utc时区中获得一个时区感知日期就足够进行日期减法了。

但是,如果您希望在当前时区中有一个时区感知日期,那么可以使用tzlocal

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

ps dateutil具有类似的功能(dateutil.tz.tzlocal)。但是,尽管共享这个名字,它有一个完全不同的代码库,正如J.F.Sebastian指出的那样,这可能会产生错误的结果。


我将此答案添加为经常使用非UTC时区的人。

唯一有自己方法的时区是timezone.utc,但是如果需要,可以使用timedelta&timezone,并强制使用.replace,使用任何UTC偏移来修改时区。

1
2
3
4
5
6
7
8
9
10
11
12
13
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))作为时区是这里真正的银弹,它还有许多其他有用的应用。


对时区感知日期时间使用如下所示的时区,默认值为UTC:

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

如果在python中得到当前的时间和日期,那么在python中导入日期和时间,之后pytz包将得到当前的日期和时间,比如……

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

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

1
2
3
4
5
6
>>> import pendulum

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

要安装摇锤并查看其文档,请转到此处。它有很多选项(如简单的ISO8601、RFC339和许多其他格式支持),性能更好,并且往往产生更简单的代码。