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

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 (хотя это более раздражает).

Ян
источник

Ответы:

316

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

с http://pytz.sourceforge.net/

naive = dt.replace(tzinfo=None)

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

редактировать: пожалуйста, имейте в виду возраст этого ответа. Python 3 ответ ниже.

phillc
источник
32
Кажется, это единственный способ сделать это. Кажется довольно прискорбно, что python имеет такую ​​дурацкую поддержку часовых поясов, что ему нужен сторонний модуль для правильной работы с временными метками ..
Ян
33
(Только для справки) На самом деле лучше добавить информацию о часовом поясе: stackoverflow.com/a/4530166/548696
Tadeck
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
214

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

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)
JFS
источник
10
Лучше, чем снимать тз, так как принятый ответ отстаивает ИМХО.
Shautieh
3
Вот список часовых
поясов
61

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

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

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

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

шалфей
источник
1
вам нужно USE_TZ=True, чтобы узнать дату и время здесь.
Jfs
2
Да, я забыл упомянуть, что вам нужно настроить файл settings.py, как описывает JFSebastian (я думаю, это был случай «установил и забыл»).
мудрец
И это также может быть легко преобразовано в другие часовые пояса, такие как + 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

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

ePi272314
источник
Некорректное? Код, который я написал, был протестирован, и я использую его в проекте Django. Это намного яснее и проще
ePi272314
«неверный» относится к последнему предложению в вашем ответе: «... должен добавить ... не UTC» - здесь работает часовой пояс UTC, и поэтому утверждение неверно.
Jfs
чтобы быть понятным, я имел в виду: 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всякий раз, когда вам нужно текущее время для сравнения с PostgreSQLtimestamptz

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