Невозможно вычесть смещенные наивные и смещенные даты

163

В поле PostgreSQL есть поле timestamptz, посвященное часовому поясу. Когда я извлекаю данные из таблицы, я тогда хочу вычесть время прямо сейчас, чтобы получить возраст.

Проблема, с которой я сталкиваюсь, заключается в том, что как datetime.datetime.now(), так и datetime.datetime.utcnow(), похоже, возвращают временные метки, не знающие временных меток, что приводит к получению этой ошибки:

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

Есть ли способ избежать этого (желательно, если не используется сторонний модуль).

EDIT: Спасибо за предложения, однако попытка настроить часовой пояс, кажется, дает мне ошибки.. поэтому я просто собираюсь использовать часовой пояс, не знающий временных меток в PG и всегда вставляя с помощью:

NOW() AT TIME ZONE 'UTC'

Таким образом, все мои временные метки по умолчанию являются UTC (хотя это более раздражает, чтобы сделать это).

Теги:
datetime
timezone

9 ответов

175
Лучший ответ

Вы пытались удалить информацию о часовом поясе?

из http://pytz.sourceforge.net/

naive = dt.replace(tzinfo=None)

возможно, придется добавить изменение часового пояса.

edit: Имейте в виду возраст этого ответа. Ответ на Python 3 ниже.

  • 25
    Кажется, это единственный способ сделать это. Кажется довольно странным, что python имеет такую дрянную поддержку часовых поясов, что ему нужен сторонний модуль для правильной работы с временными метками.
  • 18
    Python 3000 должен быть в порядке.
Показать ещё 8 комментариев
65

Правильное решение состоит в том, чтобы добавить информацию о часовом поясе, например, чтобы получить текущее время как известный объект datetime в Python 3:

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)
  • 4
    Лучше, чем снимать тз, так как принятый ответ отстаивает ИМХО.
  • 1
    Вот список часовых поясов Python: stackoverflow.com/questions/13866926/…
30

Я знаю, что некоторые люди используют Django специально как интерфейс для абстрактного взаимодействия с этим типом базы данных. Django предоставляет утилиты, которые можно использовать для этого:

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

Вам нужно настроить базовую инфраструктуру настроек Django, даже если вы просто используете этот тип интерфейса (в настройках вам нужно включить USE_TZ=True, чтобы получить информацию о дате времени).

Сам по себе это, вероятно, нигде не достаточно, чтобы мотивировать вас использовать Django в качестве интерфейса, но есть много других преимуществ. С другой стороны, если вы споткнулись здесь, потому что вы манипулировали своим приложением Django (как и я), возможно, это помогает...

  • 0
    вам нужно USE_TZ=True , чтобы узнать USE_TZ=True время здесь.
  • 1
    Да, я забыл упомянуть, что вам нужно настроить файл settings.py, как описывает JFSebastian (я думаю, это был случай «установил и забыл»).
8

Это очень простое и понятное решение
Две строки кода

# 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

Вы должны управлять своими переменными datetime с той же информацией времени

  • 0
    Некорректное? Код, который я написал, был протестирован, и я использую его в проекте Django. Это намного яснее и проще
  • 0
    «неверный» относится к последнему предложению в вашем ответе: «... должен добавить ... не UTC» - здесь работает часовой пояс UTC, и поэтому утверждение неверно.
Показать ещё 1 комментарий
5

Модуль psycopg2 имеет свои собственные определения часового пояса, поэтому я закончил писать свою собственную обертку вокруг utcnow:

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

и просто используйте pg_utcnow, когда вам нужно текущее время для сравнения с PostgreSQL timestamptz

  • 0
    Любой объект tzinfo , который возвращает нулевое смещение будет UTC сделать, например .
4

Я также столкнулся с той же проблемой. Затем я нашел решение после многого поиска.

Проблема заключалась в том, что когда мы получаем объект datetime из модели или формы, он распознает смещение, и если мы получим время по системе, оно смещено наивное.

Итак, я сделал текущее время с помощью timezone.now() и импортировал часовой пояс из django.utils time time и поместил USE_TZ = True в файле настроек проекта.

1

Есть ли какая-то насущная причина, почему вы не можете обрабатывать расчет возраста в PostgreSQL? Что-то вроде

select *, age(timeStampField) as timeStampAge from myTable
  • 1
    Да, есть .. но я в основном спрашивал, потому что я хочу избежать всех вычислений в postgre.
0

Я нашел, что timezone.make_aware(datetime.datetime.now()) полезен в django (я на 1.9.1). К сожалению, вы не можете просто сделать объект datetime объектно-ориентированным, а затем timetz() его. Вы должны сделать datetime и сделать сравнения на основе этого.

0

Я знаю, что это старо, но просто подумал, что добавлю свое решение на случай, если кто-то найдет его полезным.

Я хотел сравнить локальное наивное datetime с известным временем datetime от временного сервера. В основном я создал новый объект наивного 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 :-(')
  • 0
    К вашему сведению, utc_to_local() из моего ответа возвращает местное время как осведомленный объект datetime (это код Python 3.3+)
  • 0
    Не ясно, что пытается сделать ваш код. Вы можете заменить его на delta = response.tx_time - time.time() .

Ещё вопросы

Сообщество Overcoder
Наверх
Меню