无法减去没有时区信息和有时区信息的日期时间

481

我在PostgreSQL中有一个时区感知的timestamptz字段。当我从表中提取数据时,我希望减去现在的时间,以便得到它的年龄。

我的问题是datetime.datetime.now()datetime.datetime.utcnow()似乎都返回未指定时区的时间戳,这导致我得到以下错误:

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

有没有办法避免这种情况(最好不使用第三方模块)。
编辑:感谢提供建议,但是尝试调整时区似乎会给我带来错误。因此,我将在PG中使用无时区时间戳,并始终使用以下方式进行插入:
NOW() AT TIME ZONE 'UTC'

这样,所有的时间戳都将默认为UTC时间(即使这样做更麻烦)。


https://dev59.com/sWw05IYBdhLWcg3wuUKZ - tirenweb
12个回答

460

41
这似乎是唯一的方法。Python 对时区的支持如此糟糕,以至于需要第三方模块才能正确处理时间戳,这似乎相当无力。 - Ian
39
(仅供记录)实际上,加入有关时区的信息可能是一个更好的想法:https://dev59.com/fm855IYBdhLWcg3wHwaH#4530166 - Tadeck
9
天真的日期时间对象本质上是模糊的,因此应该避免使用它们。相反, 添加 tzinfo 非常简单。 - jfs
3
根据定义,UTC不需要时区。如果另一个日期时间对象有时区信息,则可以确定与UTC的偏移量。UTC时间可能没有时区信息,但它们并非“naive”,Python 处理这一点有误。 - Kylotan
2
@Kylotan:在这个上下文中,UTC是一个时区(由tzinfo类表示)。请查看datetime.timezone.utcpytz.utc。例如,1970-01-01 00:00:00是有歧义的,您必须添加时区以消除歧义:1970-01-01 00:00:00 UTC。您可以看到,您必须添加新信息;时间戳本身是有歧义的。 - jfs
显示剩余7条评论

404

正确的解决方法是添加时区信息,例如在Python 3中获取当前时间作为一个知道时区的datetime对象:

from datetime import datetime, timezone

now = datetime.now(timezone.utc)

在旧版本的Python中,你可以自己定义utc tzinfo对象(来自datetime文档的例子):

from datetime import tzinfo, timedelta, datetime

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

那么:

now = datetime.now(utc)

19
在我看来,比起采纳答案中提倡的删除tz,这个方法更好。 - Shautieh
6
以下是Python时区列表:https://dev59.com/a2Yr5IYBdhLWcg3wNHmh - atlas_scoffed
要将时区添加到整个 Pandas 列/系列而不是单个项目,请尝试以下内容:df['time'] = df['time'].dt.tz_localize(timezone.utc) - Dave X

79

我知道有些人专门使用Django来作为抽象化这种类型的数据库交互的接口。Django提供了可用于此目的的实用工具:

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

即使您只是使用此类型的接口,也需要设置基本的Django设置基础结构(在设置中,您需要包括USE_TZ=True以获取感知日期时间)。

仅凭这一点可能远远不足以激励您将Django用作接口,但还有许多其他优点。另一方面,如果您因混淆Django应用程序而到达此处(就像我一样),那么也许这可以帮助您......


2
你需要将 USE_TZ=True,才能在此处获取一个意识到时区的日期时间。 - jfs
2
是的 - 我忘了提到你需要按照J.F.Sebastian所描述的设置你的settings.py(我想这是一个“设置并忘记”的实例)。 - sage
这也可以轻松转换为其他时区,例如 + timedelta(hours=5, minutes=30) 用于IST。 - ABcDexter

46

这是一个非常简单清晰的解决方案
只需两行代码

# First we obtain de timezone info o some datatime variable    

tz_info = your_timezone_aware_variable.tzinfo

# Now we can subtract two variables using the same time zone info
# For instance
# Lets obtain the Now() datetime but for the tz_info we got before

diff = datetime.datetime.now(tz_info)-your_timezone_aware_variable

结论:您必须使用相同的时间信息来管理您的日期时间变量


有问题吗?我写的代码已经测试过了,我正在一个Django项目中使用它。它非常非常清晰和简单。 - ePi272314
“不正确”指的是你回答中的最后一句话:“...必须添加...而不是UTC”-- UTC时区在这里可以工作,因此该语句是不正确的。 - jfs
1
要明确一点,diff = datetime.now(timezone.utc) - your_timezone_aware_variable 是可行的(上面的(a - b)公式解释了为什么即使a.tzinfo不等于b.tzinfo(a - b)也可以工作)。 - jfs

24

你不需要任何超出标准库的东西

datetime.datetime.now().astimezone()

如果您只更改时区,它不会调整时间。如果您的系统已经是UTC,则.replace(tz ='UTC')就可以了。

>>> x=datetime.datetime.now()
datetime.datetime(2020, 11, 16, 7, 57, 5, 364576)

>>> print(x)
2020-11-16 07:57:05.364576

>>> print(x.astimezone()) 
2020-11-16 07:57:05.364576-07:00

>>> print(x.replace(tzinfo=datetime.timezone.utc)) # wrong
2020-11-16 07:57:05.364576+00:00

11
我也遇到了同样的问题。经过大量搜索后,我找到了一个解决方案。
问题是,当我们从模型或表单中获取datetime对象时,它是“带有偏移量”的,而如果我们通过系统获取时间,则是“无偏移量”的。
所以我做的是使用timezone.now()获取当前时间,并通过from django.utils import timezone导入时区,并在项目设置文件中设置USE_TZ = True

11
在标题中最直接的问题的答案似乎被遗漏了:
naive_date.replace(tzinfo=aware_date.tzinfo) - aware_date

6
我想到了一个超级简单的解决方案:
import datetime

def calcEpochSec(dt):
    epochZero = datetime.datetime(1970,1,1,tzinfo = dt.tzinfo)
    return (dt - epochZero).total_seconds()

它可以处理带时区和不带时区的日期时间值。且无需额外的库或数据库解决方法。


6

psycopg2模块具有自己的时区定义,因此我最终编写了自己的utcnow包装器:

def pg_utcnow():
    import psycopg2
    return datetime.utcnow().replace(
        tzinfo=psycopg2.tz.FixedOffsetTimezone(offset=0, name=None))

只需使用pg_utcnow,每当您需要与PostgreSQL timestamptz进行比较时,即可获得当前时间。


任何返回零UTC偏移量的tzinfo对象都可以,例如:https://dev59.com/qnRA5IYBdhLWcg3w4R_o#25662061。 - jfs

2

我在 Django (版本为 1.9.1)中发现 timezone.make_aware(datetime.datetime.now()) 很有用。不幸的是,你不能仅仅将一个 datetime 对象偏移感知,然后使用 timetz() 函数。你需要创建一个 datetime 对象并基于它做比较。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接