【问题标题】:Can't subtract offset-naive and offset-aware datetimes不能减去偏移天真和偏移感知日期时间
【发布时间】:2010-10-22 05:07:29
【问题描述】:

我在 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(尽管这样做更烦人)。

【问题讨论】:

    标签: python postgresql datetime timezone


    【解决方案1】:

    您是否尝试过消除时区意识?

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

    naive = dt.replace(tzinfo=None)
    

    可能还需要添加时区转换。

    编辑:请注意此答案的年龄。下面是一个涉及添加时区信息而不是在 python 3 中删除它的答案。 https://stackoverflow.com/a/25662061/93380

    【讨论】:

    • 这似乎是唯一的方法。似乎很蹩脚,python 对时区的支持如此糟糕,以至于它需要第三方模块才能正确处理时间戳......
    • (仅作记录)实际上添加有关时区的信息可能是一个更好的主意:stackoverflow.com/a/4530166/548696
    • 天真的日期时间对象本质上是模棱两可的,因此应该避免使用它们。是easy to add tzinfo instead
    • UTC 根据定义不需要时区。如果另一个 datetime 对象是时区感知的,那么肯定会提供相对于 UTC 的偏移量。 UTC 时间可能没有时区,但它们并不“幼稚”,Python 处理错误。
    • @Kylotan:UTC 在这种情况下是一个时区(由 tzinfo 类表示)。查看datetime.timezone.utcpytz.utc。例如,1970-01-01 00:00:00 有歧义,您必须添加一个时区来消除歧义:1970-01-01 00:00:00 UTC。你看,你必须添加新信息;时间戳本身是不明确的。
    【解决方案2】:

    正确的解决方案是添加时区信息,例如,在 Python 3 中将当前时间作为可感知的日期时间对象:

    from datetime import datetime, timezone
    
    now = datetime.now(timezone.utc)
    

    在较旧的 Python 版本上,您可以自己定义 utc tzinfo 对象(日期时间文档中的示例):

    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)
    

    【讨论】:

    【解决方案3】:

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

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

    您确实需要设置一个基本的 Django 设置基础架构,即使您只是使用这种类型的界面(在设置中,您需要包含 USE_TZ=True 以获得可感知的日期时间)。

    就其本身而言,这可能还不足以激发您将 Django 用作界面,但还有许多其他好处。另一方面,如果你在这里绊倒是因为你正在破坏你的 Django 应用程序(就像我一样),那么也许这会有所帮助......

    【讨论】:

    • 您需要USE_TZ=True,才能在此处了解日期时间。
    • 是的 - 我忘了提到你需要按照 J.F.Sebastian 的描述设置你的 settings.py(我猜这是“设置并忘记”的一个实例)。
    • 而且这也可以很容易地转换成其他时区,比如+ timedelta(hours=5, minutes=30)代表IST
    【解决方案4】:

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

    # 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 项目中使用它。它非常清晰和简单
    • “不正确”是指您回答中的最后一句话:“...必须添加 ...不是 UTC” -- UTC 时区在这里有效,因此声明不正确。
    • 要清楚我的意思是:diff = datetime.now(timezone.utc) - your_timezone_aware_variable 有效(上面的(a - b) 公式解释了为什么(a - b) 可以工作,即使a.tzinfo 不是b.tzinfo)。
    【解决方案5】:

    你不需要标准库之外的任何东西

    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
    

    【讨论】:

      【解决方案6】:

      我也遇到了同样的问题。后来找了很久才找到解决办法。

      问题在于,当我们从模型或表单中获取日期时间对象时,它是偏移感知,而如果我们通过系统获取时间,它是偏移天真。。 p>

      所以我所做的是使用 timezone.now() 获取当前时间,并通过 from django.utils import timezone 导入时区并将 USE_TZ = True 在您的项目设置文件中。

      【讨论】:

        【解决方案7】:

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

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

        只要您需要当前时间与 PostgreSQL timestamptz 进行比较,只需使用 pg_utcnow

        【讨论】:

        • 任何返回零 utc 偏移量的 tzinfo 对象都可以,for example
        【解决方案8】:

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

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

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

        【讨论】:

          【解决方案9】:

          我发现 timezone.make_aware(datetime.datetime.now()) 在 django 中很有帮助(我在 1.9.1 上)。不幸的是,您不能简单地让datetime 对象偏移感知,然后timetz() 它。你必须创建一个datetime 并以此为基础进行比较。

          【讨论】:

            【解决方案10】:

            似乎缺少对标题中问题的最直接答案:

            naive_date.replace(tzinfo=aware_date.tzinfo) - aware_date
            

            【讨论】:

              【解决方案11】:

              是否有一些紧迫的原因导致您无法在 PostgreSQL 本身中处理年龄计算?类似的东西

              select *, age(timeStampField) as timeStampAge from myTable
              

              【讨论】:

              • 是的。但我主要是问,因为我想避免在 postgre 中进行所有计算。
              【解决方案12】:

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

              我想将本地原始日期时间与来自时间服务器的已知日期时间进行比较。我基本上使用有意识的日期时间对象创建了一个新的天真的日期时间对象。这有点小技巧,看起来不太漂亮,但可以完成工作。

              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() from my answeraware 日期时间对象的形式返回本地时间(它是 Python 3.3+ 代码)
              • 不清楚您的代码试图做什么。您可以将其替换为 delta = response.tx_time - time.time()
              猜你喜欢
              • 2021-07-07
              • 1970-01-01
              • 2015-06-15
              • 2015-10-17
              • 2020-12-30
              • 1970-01-01
              • 2021-01-07
              • 1970-01-01
              相关资源
              最近更新 更多