Неможливо відняти зсувні наївні та відомі за змістом дати


305

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

Проблема у мене полягає в тому, що і те, і інше datetime.datetime.now() і, datetime.datetime.utcnow()здається, повертають невідомі часові позначки часового поясу, внаслідок чого я отримую цю помилку:

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

Чи є спосіб цього уникнути (бажано без використання сторонніх модулів).

EDIT: Дякую за пропозиції, однак намагання налаштувати часовий пояс, схоже, дає мені помилки .. тому я просто збираюся використовувати невідомі часові позначки часового поясу в PG та завжди вставляти, використовуючи:

NOW() AT TIME ZONE 'UTC'

Таким чином, усі мої часові позначки за замовчуванням є UTC (хоча це більше дратує).

Відповіді:


316

ви намагалися зняти обізнаність про часовий пояс?

від http://pytz.sourceforge.net/

naive = dt.replace(tzinfo=None)

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

редагувати: Будь ласка, пам’ятайте про вік цієї відповіді. Відповідь Python 3 наведена нижче.


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 відповідає цьому критерію за визначенням.
Кілотан

213

Правильне рішення полягає в тому, щоб додати інформацію про часовий пояс, наприклад, щоб отримати поточний час як усвідомлений об’єкт часу в 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)

10
Краще, ніж видаляти тз, оскільки прийнята відповідь виступає за ІМХО.
Shautieh

3
Ось список з пітона часових поясів: stackoverflow.com/questions/13866926 / ...
comfytoday

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

"неправильне" посилається на останнє речення у вашій відповіді: "... треба додати ... не 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


Будь-який об'єкт tzinfo , який повертає нуль зміщення UTC робитиме, наприклад .
jfs

6

Я також зіткнувся з тією ж проблемою. Тоді я знайшов рішення після багатьох пошуків.

Проблема полягала в тому, що коли ми отримуємо об'єкт дати з моделі або форми, це компенсується, і якщо ми отримуємо час за системою, це компенсується наївно .

Тож, що я зробив, це те, що я отримав поточний час за допомогою timezone.now () та імпортував часовий пояс від django.utils імпортує часовий пояс і поставив 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()), що корисний у джанго (я на 1.9.1). На жаль, ви не можете просто зробити datetimeоб'єкт, який знає зміщення, тоді timetz()це. Ви повинні datetimeскласти порівняння та зробити порівняння на основі цього.


1

Чи є якась нагальна причина, чому ви не можете впоратися з розрахунком віку в самому PostgreSQL? Щось на зразок

select *, age(timeStampField) as timeStampAge from myTable

2
Так, є .. але я в основному запитував, тому що я хочу уникати всіх обчислень в постгре.
Ян

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

FYI, utc_to_local()з моєї відповіді повертається місцевий час як усвідомлений об’єкт datetime (Це код Python 3.3+)
jfs

Незрозуміло, що намагається зробити ваш код. Ви можете замінити його delta = response.tx_time - time.time().
jfs
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.