So machen Sie eine nicht bekannte Datums- / Uhrzeitzone in Python bekannt

507

Was ich tun muss

Ich habe ein zeitzonenunabhängiges datetime-Objekt, zu dem ich eine Zeitzone hinzufügen muss, um es mit anderen zeitzonenbewussten datetime-Objekten vergleichen zu können. Ich möchte nicht meine gesamte Anwendung in eine Zeitzone konvertieren, ohne dass dies für diesen einen Legacy-Fall bekannt ist.

Was ich versucht habe

Um das Problem zu demonstrieren:

Python 2.6.1 (r261:67515, Jun 24 2010, 21:47:49) 
[GCC 4.2.1 (Apple Inc. build 5646)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import datetime
>>> import pytz
>>> unaware = datetime.datetime(2011,8,15,8,15,12,0)
>>> unaware
datetime.datetime(2011, 8, 15, 8, 15, 12)
>>> aware = datetime.datetime(2011,8,15,8,15,12,0,pytz.UTC)
>>> aware
datetime.datetime(2011, 8, 15, 8, 15, 12, tzinfo=<UTC>)
>>> aware == unaware
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't compare offset-naive and offset-aware datetimes

Zuerst habe ich Astimezone ausprobiert:

>>> unaware.astimezone(pytz.UTC)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: astimezone() cannot be applied to a naive datetime
>>>

Es ist nicht sonderlich überraschend, dass dies fehlgeschlagen ist, da tatsächlich versucht wird, eine Konvertierung durchzuführen. Ersetzen schien eine bessere Wahl zu sein (gemäß Python: Wie erhalte ich einen Wert von datetime.today (), der "zeitzonenbewusst" ist? ):

>>> unaware.replace(tzinfo=pytz.UTC)
datetime.datetime(2011, 8, 15, 8, 15, 12, tzinfo=<UTC>)
>>> unaware == aware
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't compare offset-naive and offset-aware datetimes
>>> 

Aber wie Sie sehen können, scheint Ersetzen das tzinfo zu setzen, aber das Objekt nicht bewusst zu machen. Ich bereite mich darauf vor, die Eingabezeichenfolge zu bearbeiten, um eine Zeitzone zu haben, bevor ich sie analysiere (ich verwende Dateutil zum Parsen, wenn das wichtig ist), aber das scheint unglaublich klobig.

Außerdem habe ich dies sowohl in Python 2.6 als auch in Python 2.7 mit den gleichen Ergebnissen versucht.

Kontext

Ich schreibe einen Parser für einige Datendateien. Es gibt ein altes Format, das ich unterstützen muss, wenn die Datumszeichenfolge keine Zeitzonenanzeige hat. Ich habe die Datenquelle bereits repariert, muss aber noch das ältere Datenformat unterstützen. Eine einmalige Konvertierung der Altdaten ist aus verschiedenen geschäftlichen BS-Gründen keine Option. Während mir die Idee, eine Standardzeitzone fest zu codieren, im Allgemeinen nicht gefällt, scheint sie in diesem Fall die beste Option zu sein. Ich weiß mit hinreichender Sicherheit, dass alle fraglichen Altdaten in UTC vorliegen, daher bin ich bereit, das Risiko zu akzeptieren, dass in diesem Fall dies nicht der Fall ist.

Mark Tozzi
quelle
1
unaware.replace()würde zurückkehren, Nonewenn das unawareObjekt an Ort und Stelle geändert würde. Die REPL zeigt, dass hier .replace()ein neues datetimeObjekt zurückgegeben wird.
JFS
3
Was ich brauchte, als ich hierher kam:import datetime; datetime.datetime.now(datetime.timezone.utc)
Martin Thoma
1
@ MartinThoma Ich würde das benannte Argument verwenden tz, um besser lesbar zu sein:datetime.datetime.now(tz=datetime.timezone.utc)
Acumenus

Antworten:

594

Verwenden Sie im Allgemeinen die Lokalisierungsmethode , um eine naive Datums- / Uhrzeitzone zeitbewusst zu machen :

import datetime
import pytz

unaware = datetime.datetime(2011, 8, 15, 8, 15, 12, 0)
aware = datetime.datetime(2011, 8, 15, 8, 15, 12, 0, pytz.UTC)

now_aware = pytz.utc.localize(unaware)
assert aware == now_aware

Für die UTC-Zeitzone ist die Verwendung nicht unbedingt erforderlich, localizeda keine Berechnung der Sommerzeit erforderlich ist:

now_aware = unaware.replace(tzinfo=pytz.UTC)

funktioniert. ( .replaceGibt eine neue Datums- und Uhrzeitangabe zurück. Sie wird nicht geändert unaware.)

unutbu
quelle
10
Nun, ich fühle mich albern. Ersetzen gibt eine neue Datums- / Uhrzeitangabe zurück. Das steht genau dort in den Dokumenten, und das habe ich völlig verpasst. Danke, genau das habe ich gesucht.
Mark Tozzi
2
"Ersetzen gibt eine neue Datums- / Uhrzeitangabe zurück." Ja. Der Hinweis, den die REPL Ihnen gibt, ist, dass sie Ihnen den zurückgegebenen Wert anzeigt. :)
Karl Knechtel - von zu Hause weg
danke, ich hatte Verwendung von dt_aware zu Unware dt_unware = datetime.datetime (* (dt_aware.timetuple () [: 6])),
Sérgio
4
Wenn die Zeitzone nicht UTC ist, verwenden Sie den Konstruktor nicht direkt: aware = datetime(..., tz)Verwenden Sie .localize()stattdessen.
JFS
1
Es ist erwähnenswert, dass die Ortszeit möglicherweise nicht eindeutig ist. tz.localize(..., is_dst=None)behauptet, dass es nicht ist.
JFS
166

Alle diese Beispiele verwenden ein externes Modul, aber Sie können das gleiche Ergebnis nur mit dem datetime-Modul erzielen, wie auch in dieser SO-Antwort dargestellt :

from datetime import datetime
from datetime import timezone

dt = datetime.now()
dt.replace(tzinfo=timezone.utc)

print(dt.replace(tzinfo=timezone.utc).isoformat())
'2017-01-12T22:11:31+00:00'

Weniger Abhängigkeiten und keine Pytz-Probleme.

HINWEIS: Wenn Sie dies mit Python3 und Python2 verwenden möchten, können Sie dies auch für den Zeitzonenimport verwenden (fest codiert für UTC):

try:
    from datetime import timezone
    utc = timezone.utc
except ImportError:
    #Hi there python2 user
    class UTC(tzinfo):
        def utcoffset(self, dt):
            return timedelta(0)
        def tzname(self, dt):
            return "UTC"
        def dst(self, dt):
            return timedelta(0)
    utc = UTC()
kang
quelle
10
Sehr gute Antwort, um die pytzProbleme zu vermeiden. Ich bin froh, dass ich ein bisschen nach unten gescrollt habe! Wollte pytzauf meinen Remote-Servern in der Tat nicht damit
anfangen
7
Beachten Sie, dass dies from datetime import timezonein py3 funktioniert, jedoch nicht in py2.7.
7yl4r
11
Sie sollten beachten, dass dt.replace(tzinfo=timezone.utc)eine neue Datums- / Uhrzeitangabe zurückgegeben wird und nicht dtan Ort und Stelle geändert wird . (Ich werde bearbeiten, um dies zu zeigen).
Blairg23
2
Wie können Sie anstelle von timezone.utc eine andere Zeitzone als Zeichenfolge angeben (z. B. "America / Chicago")?
Bumpkin
2
@ Buckkin besser spät als nie, denke ich:tz = pytz.timezone('America/Chicago')
Florian
82

Ich hatte Verwendung von dt_aware zu dt_unaware

dt_unaware = dt_aware.replace(tzinfo=None)

und dt_unware zu dt_aware

from pytz import timezone
localtz = timezone('Europe/Lisbon')
dt_aware = localtz.localize(dt_unware)

Aber vorher antworten ist auch eine gute Lösung.

Sérgio
quelle
2
Sie können localtz.localize(dt_unware, is_dst=None)eine Ausnahme auslösen, wenn dt_unwaresie eine nicht vorhandene oder mehrdeutige Ortszeit darstellt (Hinweis: In der vorherigen Überarbeitung Ihrer Antwort gab es kein solches Problemlocaltz es sich um UTC handelte, da UTC keine Sommerzeitübergänge aufweist
jfs
@JF Sebastian, erster Kommentar angewendet
Sérgio
1
Ich weiß es zu schätzen, dass Sie beide Richtungen der Konvertierung zeigen.
Christian Long
42

Ich benutze diese Aussage in Django, um eine unbewusste Zeit in eine bewusste umzuwandeln:

from django.utils import timezone

dt_aware = timezone.make_aware(dt_unaware, timezone.get_current_timezone())
Googol
quelle
2
Ich mag diese Lösung (+1), aber sie hängt von Django ab, was nicht das ist, wonach sie gesucht haben (-1). =)
mkoistinen
3
Sie sind eigentlich nicht das zweite Argument. Das Standardargument (Keine) bedeutet, dass die lokale Zeitzone implizit verwendet wird. Gleiches gilt für die Sommerzeit (das ist das dritte Argument)
Oli
14

Ich stimme den vorherigen Antworten zu und bin in Ordnung, wenn Sie in UTC starten können. Ich denke jedoch, dass es auch ein häufiges Szenario für Menschen ist, mit einem tz-bewussten Wert zu arbeiten, dessen Datums- und Uhrzeitangabe eine lokale Zeitzone hat, die nicht UTC ist.

Wenn Sie nur nach Namen suchen würden, würde man wahrscheinlich schließen, dass replace () anwendbar ist und das richtige datetime-fähige Objekt erzeugt. Das ist nicht der Fall.

Das Ersetzen (tzinfo = ...) scheint in seinem Verhalten zufällig zu sein . Es ist daher nutzlos. Verwenden Sie dies nicht!

Lokalisieren ist die richtige Funktion. Beispiel:

localdatetime_aware = tz.localize(datetime_nonaware)

Oder ein vollständigeres Beispiel:

import pytz
from datetime import datetime
pytz.timezone('Australia/Melbourne').localize(datetime.now())

gibt mir einen zeitzonenabhängigen datetime-Wert der aktuellen Ortszeit:

datetime.datetime(2017, 11, 3, 7, 44, 51, 908574, tzinfo=<DstTzInfo 'Australia/Melbourne' AEDT+11:00:00 DST>)
paolov
quelle
3
Dies erfordert mehr Upvotes. Wenn Sie versuchen, dies replace(tzinfo=...)in einer anderen Zeitzone als UTC zu tun, wird Ihre Datums- und Uhrzeitzeit verschmutzt. Ich habe -07:53statt -08:00zum Beispiel. Siehe stackoverflow.com/a/13994611/1224827
Blairg23
Können Sie ein reproduzierbares Beispiel für replace(tzinfo=...)unerwartetes Verhalten geben?
xjcl
11

Verwenden Sie dateutil.tz.tzlocal()diese Option, um die Zeitzone für Ihre Verwendung von datetime.datetime.now()und abzurufen datetime.datetime.astimezone():

from datetime import datetime
from dateutil import tz

unlocalisedDatetime = datetime.now()

localisedDatetime1 = datetime.now(tz = tz.tzlocal())
localisedDatetime2 = datetime(2017, 6, 24, 12, 24, 36, tz.tzlocal())
localisedDatetime3 = unlocalisedDatetime.astimezone(tz = tz.tzlocal())
localisedDatetime4 = unlocalisedDatetime.replace(tzinfo = tz.tzlocal())

Beachten Sie, dass datetime.astimezoneIhr datetimeObjekt zuerst in UTC und dann in die Zeitzone konvertiert wird. Dies entspricht dem Aufruf datetime.replacemit den ursprünglichen Zeitzoneninformationen None.

Ahmet
quelle
1
Wenn Sie es UTC machen wollen:.replace(tzinfo=dateutil.tz.UTC)
Martin Thoma
2
Ein Import weniger und nur:.replace(tzinfo=datetime.timezone.utc)
Kubanczyk
9

Dies kodifiziert die Antworten von @ Sérgio und @ unutbu . Es funktioniert "nur" mit einem pytz.timezoneObjekt oder einer IANA-Zeitzonenzeichenfolge .

def make_tz_aware(dt, tz='UTC', is_dst=None):
    """Add timezone information to a datetime object, only if it is naive."""
    tz = dt.tzinfo or tz
    try:
        tz = pytz.timezone(tz)
    except AttributeError:
        pass
    return tz.localize(dt, is_dst=is_dst) 

Dies scheint wie datetime.localize()(oder .inform()oder .awarify()) zu tun, sowohl Zeichenfolgen als auch Zeitzonenobjekte für das tz-Argument zu akzeptieren und standardmäßig UTC zu verwenden, wenn keine Zeitzone angegeben ist.

Kochfelder
quelle
1
Vielen Dank, dies hat mir geholfen, ein rohes Datum / Uhrzeit-Objekt als "UTC" zu "brandmarken", ohne dass das System zuerst davon ausgeht, dass es Ortszeit ist, und dann die Werte neu berechnet!
Nikhil VJ
3

Python 3.9 fügt das zoneinfoModul hinzu, sodass jetzt nur noch die Standardbibliothek benötigt wird!

from zoneinfo import ZoneInfo
from datetime import datetime
unaware = datetime(2020, 10, 31, 12)

Fügen Sie eine Zeitzone hinzu:

>>> unaware.replace(tzinfo=ZoneInfo('Asia/Tokyo'))
datetime.datetime(2020, 10, 31, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='Asia/Tokyo'))
>>> str(_)
'2020-10-31 12:00:00+09:00'

Fügen Sie die lokale Zeitzone des Systems hinzu:

>>> unaware.replace(tzinfo=ZoneInfo('localtime'))
datetime.datetime(2020, 10, 31, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='localtime'))
>>> str(_)
'2020-10-31 12:00:00+01:00'

Anschließend wird es ordnungsgemäß in andere Zeitzonen konvertiert:

>>> unaware.replace(tzinfo=ZoneInfo('localtime')).astimezone(ZoneInfo('Asia/Tokyo'))
datetime.datetime(2020, 10, 31, 20, 0, tzinfo=backports.zoneinfo.ZoneInfo(key='Asia/Tokyo'))
>>> str(_)
'2020-10-31 20:00:00+09:00'

Wikipedia-Liste der verfügbaren Zeitzonen


Es gibt einen Backport, der die Verwendung in Python 3.6 bis 3.8 ermöglicht :

sudo pip install backports.zoneinfo

Dann:

from backports.zoneinfo import ZoneInfo
xjcl
quelle
0

Im Format der Antwort von unutbu; Ich habe ein Dienstprogrammmodul erstellt, das solche Dinge mit einer intuitiveren Syntax behandelt. Kann mit pip installiert werden.

import datetime
import saturn

unaware = datetime.datetime(2011, 8, 15, 8, 15, 12, 0)
now_aware = saturn.fix_naive(unaware)

now_aware_madrid = saturn.fix_naive(unaware, 'Europe/Madrid')
Schildkröten sind süß
quelle
0

für diejenigen, die nur eine zeitzonenbewusste datetime machen möchten

import datetime
import pytz

datetime.datetime(2019, 12, 7, tzinfo=pytz.UTC)
Harry Moreno
quelle
0

Python ist ziemlich neu und ich bin auf dasselbe Problem gestoßen. Ich finde diese Lösung recht einfach und für mich funktioniert sie gut (Python 3.6):

unaware=parser.parse("2020-05-01 0:00:00")
aware=unaware.replace(tzinfo=tz.tzlocal()).astimezone(tz.tzlocal())
ilmatte
quelle
0

Wechsel zwischen Zeitzonen

import pytz
from datetime import datetime

other_tz = pytz.timezone('Europe/Madrid')

# From random aware datetime...
aware_datetime = datetime.utcnow().astimezone(other_tz)
>> 2020-05-21 08:28:26.984948+02:00

# 1. Change aware datetime to UTC and remove tzinfo to obtain an unaware datetime
unaware_datetime = aware_datetime.astimezone(pytz.UTC).replace(tzinfo=None)
>> 2020-05-21 06:28:26.984948

# 2. Set tzinfo to UTC directly on an unaware datetime to obtain an utc aware datetime
aware_datetime_utc = unaware_datetime.replace(tzinfo=pytz.UTC)
>> 2020-05-21 06:28:26.984948+00:00

# 3. Convert the aware utc datetime into another timezone
reconverted_aware_datetime = aware_datetime_utc.astimezone(other_tz)
>> 2020-05-21 08:28:26.984948+02:00

# Initial Aware Datetime and Reconverted Aware Datetime are equal
print(aware_datetime1 == aware_datetime2)
>> True
Shide
quelle