Nie można odjąć czasów danych naiwnych i uwzględniających przesunięcia

305

Mam pole uwzględniające strefę czasową timestamptzw PostgreSQL. Kiedy pobieram dane z tabeli, chcę odjąć teraz czas, aby uzyskać jego wiek.

Problem mam jest to, że zarówno datetime.datetime.now()i datetime.datetime.utcnow()wydają się powrócić strefy czasowej nieświadomych znaczniki czasu, co powoduje mi się następujący błąd:

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

Czy istnieje sposób, aby tego uniknąć (najlepiej bez użycia modułu innej firmy).

EDYCJA: Dzięki za sugestie, jednak próba dostosowania strefy czasowej wydaje się dawać błędy. Więc po prostu użyję strefy czasowej nieświadomej znaczników czasowych w PG i zawsze wstawiam używając:

NOW() AT TIME ZONE 'UTC'

W ten sposób wszystkie moje znaczniki czasu są domyślnie UTC (chociaż jest to bardziej denerwujące).

Ian
źródło

Odpowiedzi:

316

próbowałeś usunąć świadomość strefy czasowej?

z http://pytz.sourceforge.net/

naive = dt.replace(tzinfo=None)

może być konieczne dodanie konwersji strefy czasowej.

edycja: Pamiętaj o wieku tej odpowiedzi. Odpowiedź na Python 3 jest poniżej.

phillc
źródło
32
To chyba jedyny sposób, aby to zrobić. Wydaje się dość kiepskie, że Python ma tak kiepską obsługę stref czasowych, że potrzebuje modułu innej firmy do poprawnej pracy ze znacznikami czasu.
Ian
33
(Dla
przypomnienia
7
naiwne obiekty daty i godziny są z natury niejednoznaczne i dlatego należy ich unikać. Jest łatwo dodać tzinfo zamiast
JFS
1
@Kylotan: UTC jest strefą czasową w tym kontekście (reprezentowaną przez klasę tzinfo). Spójrz na datetime.timezone.utclub pytz.utc. Na przykład, 1970-01-01 00:00:00jest niejednoznaczne i trzeba dodać strefę czasową disambiguate: 1970-01-01 00:00:00 UTC. Widzisz, musisz dodać nowe informacje; znacznik czasu sam w sobie jest niejednoznaczny.
jfs
1
@JFSebastian: Problem polega na tym, że utcnownie powinno zwracać naiwnego obiektu lub znacznika czasu bez strefy czasowej. Z dokumentów „Świadomy obiekt służy do przedstawienia określonego momentu w czasie, który nie jest otwarty na interpretację”. Każda godzina w UTC spełnia z definicji to kryterium.
Kylotan
213

Prawidłowym rozwiązaniem jest dodanie informacji o strefie czasowej, np. Aby uzyskać bieżący czas jako świadomy obiekt daty i godziny w Pythonie 3:

from datetime import datetime, timezone

now = datetime.now(timezone.utc)

W starszych wersjach Pythona możesz sam zdefiniować utcobiekt tzinfo (przykład z dokumentów 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()

następnie:

now = datetime.now(utc)
jfs
źródło
10
Lepsze niż usunięcie tz, ponieważ przyjęta odpowiedź opowiada się za IMHO.
Shautieh
3
Oto lista stref czasowych Pythona: stackoverflow.com/questions/13866926/…
comfytoday
61

Wiem, że niektórzy ludzie używają Django specjalnie jako interfejsu do abstrakcji tego typu interakcji z bazą danych. Django zapewnia narzędzia, których można użyć do tego:

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

Musisz skonfigurować podstawową infrastrukturę ustawień Django, nawet jeśli tylko używasz tego typu interfejsu (w ustawieniach musisz uwzględnić, USE_TZ=Trueaby uzyskać świadomą datę i godzinę).

Samo to prawdopodobnie nie jest wystarczająco blisko, aby zmotywować cię do używania Django jako interfejsu, ale istnieje wiele innych korzyści. Z drugiej strony, jeśli natknąłeś się tutaj, ponieważ manipulowałeś aplikacją Django (tak jak ja), to może to pomaga ...

szałwia
źródło
1
potrzebujesz tutaj USE_TZ=True, aby uzyskać świadomą datę i godzinę.
jfs
2
Tak - zapomniałem wspomnieć, że musisz skonfigurować plik settings.py, jak opisuje JFSebastian (myślę, że była to instancja „ustaw i zapomnij”).
mędrzec
Można to również łatwo przekonwertować na inne strefy czasowe, takie jak + timedelta(hours=5, minutes=30)IST
ABcDexter
25

Jest to bardzo proste i przejrzyste rozwiązanie
Dwie linie kodu

# 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

Wniosek: musisz zarządzać zmiennymi datetime przy użyciu tych samych informacji o czasie

ePi272314
źródło
Błędny? Kod, który napisałem, został przetestowany i używam go w projekcie django. Jest to o wiele jasne i proste
ePi272314
„niepoprawny” odnosi się do ostatniego zdania w odpowiedzi: „... należy dodać ... nie UTC” - Strefa czasowa UTC działa tutaj i dlatego stwierdzenie jest niepoprawne.
jfs
dla jasności miałem na myśli: diff = datetime.now(timezone.utc) - your_timezone_aware_variabledziała (a (a - b)powyższy wzór wyjaśnia, dlaczego (a - b)może działać, nawet jeśli a.tzinfonie jest b.tzinfo).
jfs
6

Moduł psycopg2 ma własne definicje stref czasowych, więc skończyłem pisać własne opakowanie wokół utcnow:

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

i używaj pg_utcnowtylko wtedy, gdy potrzebujesz aktualnego czasu, aby porównać z PostgreSQLtimestamptz

erjiang
źródło
Zrobi to na przykład dowolny obiekt tzinfo, który zwróci zerowe przesunięcie utc .
jfs
6

Też napotkałem ten sam problem. Potem znalazłem rozwiązanie po wielu poszukiwaniach.

Problem polegał na tym, że kiedy pobieramy obiekt datetime z modelu lub formy, jest on rozpoznawany z przesunięciem, a jeśli otrzymujemy czas przez system, jest naiwny .

Więc zrobiłem to, że mam aktualny czas za pomocą timezone.now () i zaimportuję strefę czasową z django.utils zaimportuj strefę czasową i umieść USE_TZ = True w pliku ustawień projektu.

Ashok Joshi
źródło
2

Wymyśliłem bardzo proste rozwiązanie:

import datetime

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

Działa zarówno z wartościami daty i godziny uwzględniającymi strefę czasową, jak i naiwnymi dla strefy czasowej. I nie są wymagane żadne dodatkowe biblioteki ani obejścia bazy danych.

John L. Stanley
źródło
1

Znalazłem, że timezone.make_aware(datetime.datetime.now())jest pomocny w django (korzystam z wersji 1.9.1). Niestety, nie można po prostu ustawić datetimeobiektu na rozpoznawanie przesunięcia, to jest timetz()to. Musisz dokonać datetimei dokonać porównań na tej podstawie.

Joseph Coco
źródło
1

Czy istnieje jakiś naglący powód, dla którego nie można obsłużyć obliczeń wieku w samym PostgreSQL? Coś jak

select *, age(timeStampField) as timeStampAge from myTable
Nic Gibson
źródło
2
Tak, jest… ale głównie pytałem, ponieważ chcę uniknąć wykonywania wszystkich obliczeń w postgre.
Ian
0

Wiem, że to stare, ale pomyślałem, że dodam moje rozwiązanie na wypadek, gdyby ktoś uznał je za przydatne.

Chciałem porównać lokalną naiwną datę i godzinę ze świadomą datą z serwera czasu. Zasadniczo utworzyłem nowy naiwny obiekt datetime przy użyciu świadomego obiektu datetime. To trochę hack i nie wygląda bardzo ładnie, ale wykonuje pracę.

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

... nadchodzi krówka ...

    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
źródło
FYI, utc_to_local()z mojej odpowiedzi zwraca czas lokalny jako świadomy obiekt datetime (jest to kod Python 3.3+)
jfs
Nie jest jasne, co próbuje zrobić Twój kod. Możesz go zastąpićdelta = response.tx_time - time.time() .
jfs