Offset-naive und Offset-fähige Datenzeiten können nicht subtrahiert werden

305

Ich habe ein zeitzonenbewusstes timestamptzFeld in PostgreSQL. Wenn ich Daten aus der Tabelle ziehe, möchte ich jetzt die Zeit abziehen, damit ich das Alter ermitteln kann.

Das Problem, das ich habe, ist, dass beide datetime.datetime.now()und datetime.datetime.utcnow()scheinbar Zeitzonen-unbewusste Zeitstempel zurückgeben, was dazu führt, dass ich diesen Fehler erhalte:

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

Gibt es eine Möglichkeit, dies zu vermeiden (vorzugsweise ohne Verwendung eines Drittanbieter-Moduls).

BEARBEITEN: Vielen Dank für die Vorschläge, aber der Versuch, die Zeitzone anzupassen, scheint mir Fehler zu geben. Ich werde also nur Zeitzonen-unbewusste Zeitstempel in PG verwenden und immer Folgendes einfügen:

NOW() AT TIME ZONE 'UTC'

Auf diese Weise sind alle meine Zeitstempel standardmäßig UTC (obwohl es ärgerlicher ist, dies zu tun).

Ian
quelle

Antworten:

316

Haben Sie versucht, das Zeitzonenbewusstsein zu entfernen?

von http://pytz.sourceforge.net/

naive = dt.replace(tzinfo=None)

Möglicherweise muss auch die Zeitzonenkonvertierung hinzugefügt werden.

bearbeiten: Bitte beachten Sie das Alter dieser Antwort. Eine Python 3-Antwort finden Sie unten.

phillc
quelle
32
Dies scheint der einzige Weg zu sein, dies zu tun. Scheint ziemlich lahm, dass Python eine so beschissene Unterstützung für Zeitzonen hat, dass es ein Modul eines Drittanbieters benötigt, um richtig mit Zeitstempeln zu arbeiten.
Ian
33
(Nur zur Veranschaulichung
Tadeck
7
naive datetime-Objekte sind von Natur aus mehrdeutig und sollten daher vermieden werden. Es ist einfach, stattdessen tzinfo hinzuzufügen
jfs
1
@Kylotan: UTC ist in diesem Zusammenhang eine Zeitzone (dargestellt durch die Klasse tzinfo). Schau dir an datetime.timezone.utcoder pytz.utc. Zum Beispiel 1970-01-01 00:00:00ist mehrdeutig und Sie müssen eine Zeitzone hinzufügen, um zu unterscheiden : 1970-01-01 00:00:00 UTC. Sie sehen, Sie müssen neue Informationen hinzufügen ; Der Zeitstempel an sich ist nicht eindeutig.
JFS
1
@JFSebastian: Das Problem ist, dass utcnowkein naives Objekt oder ein Zeitstempel ohne Zeitzone zurückgegeben werden sollte. Aus den Dokumenten geht hervor, dass "ein bewusstes Objekt verwendet wird, um einen bestimmten Zeitpunkt darzustellen, der nicht interpretiert werden kann". Jeder Zeitpunkt in UTC erfüllt dieses Kriterium per Definition.
Kylotan
214

Die richtige Lösung besteht darin , die Zeitzoneninformationen hinzuzufügen, z. B. um die aktuelle Zeit als bewusstes Datum / Uhrzeit-Objekt in Python 3 abzurufen:

from datetime import datetime, timezone

now = datetime.now(timezone.utc)

In älteren Python-Versionen können Sie das utctzinfo-Objekt selbst definieren (Beispiel aus datetime-Dokumenten):

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

dann:

now = datetime.now(utc)
jfs
quelle
10
Besser als das tz zu entfernen, da die akzeptierte Antwort IMHO befürwortet.
Shautieh
3
Hier ist eine Liste von Python-Zeitzonen: stackoverflow.com/questions/13866926/…
comfytoday
61

Ich weiß, dass einige Leute Django speziell als Schnittstelle verwenden, um diese Art der Datenbankinteraktion zu abstrahieren. Django bietet Dienstprogramme, die dafür verwendet werden können:

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

Sie müssen eine grundlegende Django-Einstellungsinfrastruktur einrichten, auch wenn Sie nur diese Art von Schnittstelle verwenden (in den Einstellungen müssen Sie diese USE_TZ=Trueangeben, um eine genaue Datums- / Uhrzeitangabe zu erhalten).

An sich ist dies wahrscheinlich bei weitem nicht genug, um Sie zu motivieren, Django als Schnittstelle zu verwenden, aber es gibt viele andere Vorteile. Auf der anderen Seite, wenn Sie hier gestolpert sind, weil Sie Ihre Django-App (wie ich) entstellt haben, dann hilft das vielleicht ...

Salbei
quelle
1
Sie müssen USE_TZ=True, um eine bewusste Datums- / Uhrzeitzeit hier zu erhalten.
JFS
2
Ja - Ich habe vergessen zu erwähnen, dass Sie Ihre settings.py so einrichten müssen, wie es JFSebastian beschreibt (ich denke, dies war eine Instanz von 'setzen und vergessen').
Salbei
Dies kann auch problemlos in andere Zeitzonen konvertiert werden, z. B. + timedelta(hours=5, minutes=30)für IST
ABcDexter
25

Dies ist eine sehr einfache und klare Lösung.
Zwei Codezeilen

# 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

Fazit: Sie müssen Ihre datetime-Variablen mit denselben Zeitinformationen verwalten

ePi272314
quelle
Falsch? Der Code, den ich geschrieben habe, wurde getestet und ich verwende ihn in einem Django-Projekt. Es ist sehr viel klarer und einfacher
ePi272314
Das "Falsche" bezieht sich auf den letzten Satz in Ihrer Antwort: "... muss hinzufügen ... nicht UTC" - Die UTC-Zeitzone funktioniert hier und daher ist die Aussage falsch.
JFS
um klar zu sein, meinte ich: diff = datetime.now(timezone.utc) - your_timezone_aware_variablefunktioniert (und die (a - b)obige Formel ist die Erklärung, warum (a - b)funktionieren kann, auch wenn dies a.tzinfonicht der Fall ist b.tzinfo).
JFS
6

Das psycopg2-Modul hat seine eigenen Zeitzonendefinitionen, daher habe ich meinen eigenen Wrapper um utcnow geschrieben:

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

und verwenden pg_utcnowSie es immer dann, wenn Sie die aktuelle Zeit zum Vergleichen mit einem PostgreSQL benötigentimestamptz

Erjiang
quelle
Jedes tzinfo-Objekt, das einen utc-Offset von Null zurückgibt, reicht beispielsweise aus .
JFS
6

Ich hatte auch das gleiche Problem. Dann habe ich nach langem Suchen eine Lösung gefunden.

Das Problem war, dass wenn wir das datetime-Objekt vom Modell oder Formular erhalten, es Offset- fähig ist und wenn wir die Zeit vom System erhalten, es naiv versetzt ist .

Ich habe also die aktuelle Zeit mit timezone.now () abgerufen und die Zeitzone von django.utils importiert. Importieren Sie die Zeitzone und fügen Sie USE_TZ = True in Ihre Projekteinstellungsdatei ein.

Ashok Joshi
quelle
2

Ich habe eine ultra-einfache Lösung gefunden:

import datetime

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

Es funktioniert sowohl mit zeitzonensensitiven als auch mit zeitzonennahen datetime-Werten. Es sind keine zusätzlichen Bibliotheken oder Datenbankumgehungen erforderlich.

John L. Stanley
quelle
1

Ich habe festgestellt, dass timezone.make_aware(datetime.datetime.now())es in Django hilfreich ist (ich bin auf 1.9.1). Leider kann man ein datetimeObjekt dann nicht einfach versatzbewusst machen timetz(). Darauf müssen Sie einen datetimeVergleich anstellen.

Joseph Coco
quelle
1

Gibt es einen dringenden Grund, warum Sie die Altersberechnung in PostgreSQL selbst nicht durchführen können? Etwas wie

select *, age(timeStampField) as timeStampAge from myTable
Nic Gibson
quelle
2
Ja, das gibt es ... aber ich habe hauptsächlich gefragt, weil ich vermeiden möchte, alle Berechnungen in Postgre durchzuführen.
Ian
0

Ich weiß, dass dies alt ist, dachte aber nur, ich würde meine Lösung hinzufügen, falls jemand sie nützlich findet.

Ich wollte die lokale naive Datumszeit mit einer bewussten Datumszeit von einem Zeitserver vergleichen. Ich habe im Grunde ein neues naives datetime-Objekt mit dem bewussten datetime-Objekt erstellt. Es ist ein bisschen wie ein Hack und sieht nicht sehr hübsch aus, erledigt aber den Job.

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

... hier kommt der Fudge ...

    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
quelle
Zu Ihrer Information , utc_to_local()aus meiner Antwort wird die Ortszeit als bewusstes Datum / Uhrzeit-Objekt zurückgegeben (es handelt sich um Python 3.3+ Code).
jfs
Es ist nicht klar, was Ihr Code versucht zu tun. Sie könnten es durch ersetzendelta = response.tx_time - time.time() .
JFS