如何在Python中获取“时区感知”的datetime.today()值?


310

我正在尝试从的值中减去一个日期值,datetime.today()以计算某物是多久以前的。但它抱怨:

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

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

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

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



10
似乎我们可以datetime.now().astimezone()从Python 3.6开始使用
johnchen902'7

Answers:


362

在标准库中,没有跨平台的方法来创建感知时区而不创建自己的时区类。

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


75
怎么样datetime.now(pytz.utc),而不是datetime.utcnow().replace(tzinfo = pytz.utc)
eumiro 2010年

5
now(utc)今天不返回(除非在UTC是午夜),而是返回UTC的当前时间。您还需要.replace(hour=0, minute=0, ...)开始新的一天(例如datetime.today()
jfs

1
文件说,today()返回当前时间,而不是午夜。如果存在需要午夜使用的情况,是的,需要相应地进行更换。由于最初的问题是关于日期时间差异的,所以我认为不需要午夜。
AndiDog 2014年

1
@AndiDog:我的评论暗示我(错误地)认为datetime.today()combine(date.today(), time())datetime具有.now().today()方法(正如您已经正确指出的)都返回(几乎)相同的东西。没有date.now()办法。date并且datetime对象不可互换。使用日期时间对象而不是date对象会产生细微的错误。datetime.today()如果与几乎重复,我看不出存在任何理由datetime.now()
jfs 2014年

6
添加到此答案中,如果您恰巧使用django,请始终使用timezone.now()而不是,datetime.now()因为如果,它将自动使用UTC USE_TZ = Truetimezone位于django.utils.timezone,文档: docs.djangoproject.com/en/1.11/topics/i18n/timezones
Ryan

107

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

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

2
看到这个
wim 2015年

1
您不应该使用本地化时间(输出除外)。使用基于时区的日期时间时,很多事情都会出错:一个简单的timedelta不会考虑夏令时,除非您开始使用UTC时间。始终使用基于UTC的时区感知。必要时将输出转换为本地时区。
MrE

4
要重申与@MrE的不同意见,我以前已经在接受的答案的评论中表达过:有完全正当的理由使用本地化的日期时间,并且“除输出外,您不应该使用本地化的时间”是过于广泛的建议。假设您要在夏令时边界之前几个小时的日期时间中增加1天,时钟将在该时间上倒退一个小时。您想要什么结果?如果您认为时间应该相同,请使用本地化的日期时间。如果您认为应该早一个小时,请使用UTC或未使用时区的日期时间。有意义的是取决于域的。
Mark Amery

我完全同意@MarkAmery,您可能想添加或减去几天或几个小时,而不关心时区问题(例如您的示例),此注释与将时区校正后的时间传递给客户端有关。由于Python主要用于后端进程,因此它将时间传递给客户端。服务器应始终以UTC格式传递日期/时间,而客户端应将其转换为自己的本地日期/时间/时区,否则会发生不好的事情:只需检查输出,datetime.datetime(2016, 11, 5, 9, 43, 45, tzinfo=pytz.timezone('US/Pacific'))看看是否符合您的期望
MrE

“服务器应始终以UTC形式传递日期/时间,而客户端应将其转换为自己的本地日期/时间/时区” -不,这并非普遍如此。有时使用客户端的时区是不合适的,因此需要将适当的时区作为数据的一部分进行传输。如果作为伦敦人,我在他们的网站上查看了旧金山国际象棋俱乐部的开会时间,那么我应该在旧金山时间而不是伦敦时间看到他们。
马克·阿默里

69

在Python 3中,标准库使将UTC指定为时区变得容易得多:

>>> 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中均可使用的解决方案,请参见jfs的答案

如果您需要当地时区而不是UTC,请参见MihaiCapotă的答案


19

这是一个适用于Python 2和3的stdlib解决方案:

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偏移量的时区的午夜更为复杂。


16

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

import datetime
import pytz

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

请注意,pytz.utcpytz.UTC都已定义(且相同)
drevicko

2
这个答案比公认的要好,因为它更通用:replace()ing时区在大多数其他用途中通常容易出错,而localize()ing是将时区分配给朴素时间戳的首选方法。
安东尼·哈奇金斯2014年

@AntonyHatchkins:.localize()方法在不明确的本地时间(非UTC输入)失败。在这种情况下,@ philfreo的使用答案.now(pytz_timezone)仍然有效。
jfs

如python docs中所指定的,其作用与之.now(pytz_timezone)完全相同localize(utcnow)-首先,它以UTC生成当前时间,然后为其分配时区:“ <...>在这种情况下,结果等于tz.fromutc(datetime.utcnow().replace(tzinfo=tz))”。这两个答案都是正确的,并且始终有效。
安东尼·哈奇金斯

1
现在可以安全地使时区知道的唯一幼稚(非UTC)时间是:底层系统应该知道UTC值,而pytz通过OLSON db应该知道如何将其转换为世界上的任何时区。由于夏令时期间的模棱两可,因此很难让其他任何天真的(非UTC)时区都知道。这不是问题.localize(输入is_dst值使其可以在任何日期使用)。那是夏时制的固有问题。
安东尼·哈奇金斯2015年

16

从Python 3.3开始,仅使用标准库的单行代码就可以使用。您可以datetime使用来获取本地时区感知对象astimezone(如johnchen902建议):

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

2
该文档对您有很大帮助,可在以下位置找到:docs.python.org/3.8/library/…。这个令人难以置信的基本功能嵌入在文档中一个晦涩的段落中,因此该stackoverflow答案实际上是拥有此信息的整个互联网上唯一的地方。在文档中,还可以看到,由于Python 3.6,datetime.now()可以在不带任何参数的情况下调用该方法并返回正确的本地结果(datetime假定naive 处于本地时区)。
atimholt

8

如果您使用的是Django,则可以将日期设置为非tz感知(仅UTC)。

在settings.py中注释以下行:

USE_TZ = True

8
您在哪里看到这个问题中提到的django?
vonPetrushev

1
某种友善的灵魂在这里删除了我先前的评论道歉,因此再次:对我感到羞耻,错误答案,因为问题不是特定于Django的。我离开它是因为它仍然可以帮助某些用户,但是当分数接近0时,我将其删除。如果此答案不合适,请随时投票。
laffuste

6

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

使用stdlib,这是不可能的。

SO上看到类似的问题。


6

这是使用stdlib生成它的一种方法:

import time
from datetime import datetime

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

date将存储本地日期和相对于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.localtime()替换为time.gmtime()

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代码上不可用


ValueError:'z'是格式为'%Y-%m-%dT%H:%M:%S%z'的错误指令
jno

您使用的是python 2,对吗?不幸的是,似乎z指令在python 2上不可用 。_strptime.py代码
jcazor


2

在时区中获取可识别时区的日期utc足以使日期减法起作用。

但是,如果您想在当前时区使用时区感知日期,tzlocal则可以采用以下方法:

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

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


python通常在服务器上使用。服务器上的本地时区通常没有意义,应始终设置为UTC。在某些情况下,以这种方式设置datetime tzinfo会失败。最好使用UTC,然后仅在输出时定位到所需的时区。例如,任何timedelta计算都不会考虑夏令时,因此应在UTC中进行,然后进行本地化。
MrE

@MrE错误的题外话吗?
安东尼·哈奇金斯

尝试使用在时区本地化的datetime对象观察夏时制,添加天数以更改夏时制状态,您会发现对本地化时区中的datetime对象进行操作会失败并且将不遵守夏时制。因此,我的评论是您应该始终在UTC时间执行任何datetime操作。
MrE

重点是:不要这样做,请在UTC中进行操作,然后使用datetime.astimezone(timezone)转换为输出时的本地时区。
MrE's

2

这是一个使用可读时区的解决方案,该解决方案适用于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

1

特别是对于非UTC时区:

唯一具有自己方法的时区是timezone.utc,但是如果需要,您可以使用timedeltatimezone,并使用强制使用UTC偏移量来伪装时区.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))时区是真正的灵丹妙药,它还有许多其他有用的应用程序。


0

如果您在python中获得了当前时间和日期,则在导入日期和时间后,您将在python中获得当前日期和时间,如下所示。

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

0

在我看来,另一种替代方法是使用Pendulum代替pytz。考虑以下简单代码:

>>> import pendulum

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

要安装Pendulum并查看其文档,请转到此处。它具有大量选项(例如简单的ISO8601,RFC3339和许多其他格式支持),更好的性能并倾向于产生更简单的代码。


不知道为什么在这里投票,这段代码在为我运行7/24的多个程序中起作用:)。我并不介意其他意见,但是请允许我检查一下为什么它对您不起作用。在此先感谢
ng10


0

来自“ howchoo”的Tyler撰写了一篇非常出色的文章,帮助我对Datetime Objects有了更好的了解,请点击以下链接

使用日期时间

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

.replace(tzinfo=pytz.utc)

例:

import pytz
import datetime from datetime

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

0

尝试pnp_datetime,所有使用和返回的时间都是带时区的,不会造成任何天真偏移和可感知偏移的问题。

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

0

应该强调的是,从Python 3.6开始,您只需要标准的lib即可获取表示本地时间(操作系统的设置)的时区感知日期时间对象。使用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的评论)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.