无法减去天真偏移和可感知偏移的日期时间


305

timestamptz在PostgreSQL中有一个时区识别字段。当我从表中提取数据时,我想现在减去时间,以便确定时间。

我遇到的问题是,无论是datetime.datetime.now()datetime.datetime.utcnow()似乎回到时区不知道时间戳,这导致我得到这个错误:

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

有没有一种方法可以避免这种情况(最好不要使用第三方模块)。

编辑:感谢您的建议,但是尝试调整时区似乎给了我错误..所以我只打算在PG中使用不知道时区的时间戳,并始终使用以下命令插入:

NOW() AT TIME ZONE 'UTC'

这样,默认情况下,我所有的时间戳都是UTC(即使这样做比较烦人)。

Answers:


316

您是否尝试删除时区意识?

来自http://pytz.sourceforge.net/

naive = dt.replace(tzinfo=None)

可能还必须添加时区转换。

编辑:请注意这个答案的年龄。以下是Python 3的答案。


32
这似乎是唯一的方法。似乎很跛脚Python的了,它需要一个第三方模块与时间戳正常工作的时区这样蹩脚的支持..
伊恩·

33
(仅供参考)实际上添加有关时区的信息可能是一个更好的主意:stackoverflow.com/a/4530166/548696
Tadeck 2012年

7
天真的日期时间对象本质上是模棱两可的,因此应避免使用它们。相反,添加tzinfo
jfs

1
@Kylotan:在这种情况下,UTC是一个时区(由tzinfo类表示)。看看datetime.timezone.utc还是pytz.utc。例如,1970-01-01 00:00:00模糊不清,您必须添加时区才能消除歧义:1970-01-01 00:00:00 UTC。您会看到,您必须添加新信息;时间戳本身是模棱两可的。
jfs

1
@JFSebastian:问题在于,utcnow不应返回没有时区的朴素对象或时间戳。在文档中,“一个有意识的对象用于表示一个特定的时间,该时间不容解释。” 根据定义,UTC中的任何时间都符合此标准。
Kylotan 2015年

213

正确的解决方案是添加时区信息,例如,将当前时间作为Python 3中已知的datetime对象获取:

from datetime import datetime, timezone

now = datetime.now(timezone.utc)

在较旧的Python版本上,您可以utc自己定义tzinfo对象(例如datetime docs中的示例):

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)

10
比消除tz更好,因为公认的答案提倡恕我直言。
Shautieh


61

我知道有人专门使用Django作为抽象此类数据库交互的接口。Django提供了可用于此目的的实用程序:

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

您确实需要设置基本的Django设置基础结构,即使您只是使用这种类型的界面(在设置中,也需要包括在内USE_TZ=True以获取已知的日期时间)。

就其本身而言,这可能还远远不足以激发您使用Django作为界面,但是还有许多其他好处。另一方面,如果您是因为要破坏Django应用(如我所做的那样)而在这里偶然发现的,那么这可能会有所帮助...


1
您需要USE_TZ=True在此处获取有效的日期时间。
jfs 2015年

2
是的-我忘记提及您需要按照JFSebastian的描述来设置settings.py(我想这是“设置并忘记”的一个实例)。
2015年

而且这也可以轻松转换为其他时区,例如+ timedelta(hours=5, minutes=30)IST
ABcDexter

25

这是一个非常简单明了的解决方案
两行代码

# 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 2015年

“不正确”指的是答案中的最后一句话:“ ...必须加...不加UTC” -UTC时区在这里起作用,因此该语句不正确。
jfs 2015年

明确地说,我的意思是:diff = datetime.now(timezone.utc) - your_timezone_aware_variable有效((a - b)上面的公式说明了(a - b)即使a.tzinfo无效也可以正常工作的原因b.tzinfo)。
jfs

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对象都可以。
jfs

6

我也面临同样的问题。经过大量搜索之后,我找到了解决方案。

问题是,当我们从模型或表单获取datetime对象时,它是偏移量感知的;如果通过系统获取时间,则它是偏移量天真的

所以我要做的是使用timezone.now()获得当前时间,并从django.utils import timezone导入时区,并将USE_TZ = True放入项目设置文件中。


2

我想出了一个超简单的解决方案:

import datetime

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

它适用于时区感知和时区原始日期时间值。并且不需要其他库或数据库解决方法。


1

我发现timezone.make_aware(datetime.datetime.now())在Django中很有帮助(我在1.9.1上)。不幸的是,您不能简单地使datetime对象具有偏移意识timetz()。您必须做一个,datetime并在此基础上进行比较。


1

有一些紧迫的原因导致您无法在PostgreSQL本身中处理年龄计算吗?就像是

select *, age(timeStampField) as timeStampAge from myTable

2
是的,有..但是我主要是问,因为我想避免在postgre中进行所有计算。
伊恩

0

我知道这很旧,但只是想我会添加我的解决方案,以防万一有人觉得有用。

我想将本地原始日期时间与时间服务器的已知日期时间进行比较。我基本上使用感知的datetime对象创建了一个新的朴素的datetime对象。这有点骇人听闻,看起来并不漂亮,但是可以完成工作。

import ntplib
import datetime
from datetime import timezone

def utc_to_local(utc_dt):
    return utc_dt.replace(tzinfo=timezone.utc).astimezone(tz=None)    

try:
    ntpt = ntplib.NTPClient()
    response = ntpt.request('pool.ntp.org')
    date = utc_to_local(datetime.datetime.utcfromtimestamp(response.tx_time))
    sysdate = datetime.datetime.now()

...软糖来了...

    temp_date = datetime.datetime(int(str(date)[:4]),int(str(date)[5:7]),int(str(date)[8:10]),int(str(date)[11:13]),int(str(date)[14:16]),int(str(date)[17:19]))
    dt_delta = temp_date-sysdate
except Exception:
    print('Something went wrong :-(')

仅供参考,utc_to_local()从我的答案返回本地时间为感知 DateTime对象(这是Python的3.3+代码)
JFS

目前尚不清楚您的代码正在尝试做什么。您可以将其替换为delta = response.tx_time - time.time()
jfs
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.