Konvertieren Sie eine Python-UTC-Datumszeit in eine lokale Datumszeit, indem Sie nur die Python-Standardbibliothek verwenden.

185

Ich habe eine Python-Datetime-Instanz, die mit datetime.utcnow () erstellt und in der Datenbank beibehalten wurde.

Zur Anzeige möchte ich die aus der Datenbank abgerufene datetime-Instanz unter Verwendung der lokalen Standardzeitzone in die lokale datetime konvertieren (dh, als ob die datetime mit datetime.now () erstellt worden wäre).

Wie kann ich die UTC-Datumszeit in eine lokale Datumszeit konvertieren, indem ich nur die Python-Standardbibliothek verwende (z. B. keine Pytz-Abhängigkeit)?

Es scheint, dass eine Lösung darin besteht, datetime.astimezone (tz) zu verwenden, aber wie würden Sie die lokale Standardzeitzone erhalten?

Nitro Zark
quelle
In welchem ​​Format blieb die Zeit für die Datenbank bestehen? Wenn es sich um ein Standardformat handelt, müssen Sie möglicherweise keine Konvertierung durchführen.
Apalala
1
Diese Antwort zeigt eine einfache Verwendung von Pytz .
Juan

Antworten:

269

In Python 3.3+:

from datetime import datetime, timezone

def utc_to_local(utc_dt):
    return utc_dt.replace(tzinfo=timezone.utc).astimezone(tz=None)

In Python 2/3:

import calendar
from datetime import datetime, timedelta

def utc_to_local(utc_dt):
    # get integer timestamp to avoid precision lost
    timestamp = calendar.timegm(utc_dt.timetuple())
    local_dt = datetime.fromtimestamp(timestamp)
    assert utc_dt.resolution >= timedelta(microseconds=1)
    return local_dt.replace(microsecond=utc_dt.microsecond)

Verwenden pytz(beide Python 2/3):

import pytz

local_tz = pytz.timezone('Europe/Moscow') # use your local timezone name here
# NOTE: pytz.reference.LocalTimezone() would produce wrong result here

## You could use `tzlocal` module to get local timezone on Unix and Win32
# from tzlocal import get_localzone # $ pip install tzlocal

# # get local timezone    
# local_tz = get_localzone()

def utc_to_local(utc_dt):
    local_dt = utc_dt.replace(tzinfo=pytz.utc).astimezone(local_tz)
    return local_tz.normalize(local_dt) # .normalize might be unnecessary

Beispiel

def aslocaltimestr(utc_dt):
    return utc_to_local(utc_dt).strftime('%Y-%m-%d %H:%M:%S.%f %Z%z')

print(aslocaltimestr(datetime(2010,  6, 6, 17, 29, 7, 730000)))
print(aslocaltimestr(datetime(2010, 12, 6, 17, 29, 7, 730000)))
print(aslocaltimestr(datetime.utcnow()))

Ausgabe

Python 3.3
2010-06-06 21:29:07.730000 MSD+0400
2010-12-06 20:29:07.730000 MSK+0300
2012-11-08 14:19:50.093745 MSK+0400
Python 2
2010-06-06 21:29:07.730000 
2010-12-06 20:29:07.730000 
2012-11-08 14:19:50.093911 
Pytz
2010-06-06 21:29:07.730000 MSD+0400
2010-12-06 20:29:07.730000 MSK+0300
2012-11-08 14:19:50.146917 MSK+0400

Hinweis: Es berücksichtigt die Sommerzeit und die jüngste Änderung des UTC-Offsets für die MSK-Zeitzone.

Ich weiß nicht, ob Nicht-Pytz-Lösungen unter Windows funktionieren.

jfs
quelle
1
Was macht "normalisieren"?
avi
4
@avi: im Allgemeinen: pytz: Warum wird beim Konvertieren zwischen Zeitzonen eine Normalisierung benötigt? . Hinweis: Bei der aktuellen Implementierung von ist dies nicht erforderlich, .normalize()da die Quellzeitzone für .astimezone()UTC ist.
JFS
1
Ich bekomme TypeError: replace() takes no keyword argumentsbeim Ausprobieren die Pytz-Lösung.
Craig
@Craig Der Code funktioniert wie er ist, wie die Ausgabe in der Antwort zeigt. Erstellen Sie ein vollständiges minimales Codebeispiel, das zum TypeError führt, erwähnen Sie Ihre Python- und Pytz-Versionen, die Sie verwenden, und stellen Sie es als separate Stack Overflow-Frage.
JFS
Schneller Tipp für die Python 3.3+ -Lösung. Um in die entgegengesetzte Richtung zu gehen (Native to UTC), funktioniert dies: local_dt.replace(tzinfo=None).astimezone(tz=timezone.utc)
Jasonrhaas
50

Sie können dies nicht nur mit der Standardbibliothek tun, da die Standardbibliothek keine Zeitzonen hat. Sie benötigen Pytz oder Dateutil .

>>> from datetime import datetime
>>> now = datetime.utcnow()
>>> from dateutil import tz
>>> HERE = tz.tzlocal()
>>> UTC = tz.gettz('UTC')

The Conversion:
>>> gmt = now.replace(tzinfo=UTC)
>>> gmt.astimezone(HERE)
datetime.datetime(2010, 12, 30, 15, 51, 22, 114668, tzinfo=tzlocal())

Oder Sie können es ohne Pytz oder Dateutil tun, indem Sie Ihre eigenen Zeitzonen implementieren. Aber das wäre dumm.

Lennart Regebro
quelle
Danke, ich werde das zur Hand haben, wenn ich jemals eine vollständige Lösung brauche. Unterstützt dateutil Python 3.1 (sagt Python 2.3+, ist aber verdächtig)?
Nitro Zark
pytz handhabt DST-Umstellungen tatsächlich besser.
Lennart Regebro
2
Update: Pytz und Dateutil unterstützen heutzutage beide Python 3.
Lennart Regebro
1
@JFSebastian: dateutil kann nicht zwischen den beiden 1: 30-Zeiten unterscheiden, die während einer Sommerzeitumstellung auftreten. Wenn Sie in der Lage sein müssen, zwischen diesen beiden Zeiten zu unterscheiden, umgehen Sie es, um Pytz zu verwenden, das damit umgehen kann.
Lennart Regebro
8

Sie können es nicht mit Standardbibliothek tun. Mit dem Pytz- Modul können Sie jedes naive / bewusste Datum / Uhrzeit-Objekt in eine andere Zeitzone konvertieren. Sehen wir uns einige Beispiele mit Python 3 an.

Naive Objekte, die mit der Klassenmethode erstellt wurden utcnow()

Um ein naives Objekt in eine andere Zeitzone zu konvertieren, müssen Sie es zuerst in ein bewusstes Datum / Uhrzeit-Objekt konvertieren . Sie können die replaceMethode zum Konvertieren eines naiven Datetime-Objekts in ein bewusstes Datetime-Objekt verwenden. Um ein bewusstes Datum / Uhrzeit-Objekt in eine andere Zeitzone zu konvertieren , können Sie die astimezoneMethode verwenden.

Die Variable pytz.all_timezonesgibt Ihnen die Liste aller verfügbaren Zeitzonen im Pytz-Modul.

import datetime,pytz

dtobj1=datetime.datetime.utcnow()   #utcnow class method
print(dtobj1)

dtobj3=dtobj1.replace(tzinfo=pytz.UTC) #replace method

dtobj_hongkong=dtobj3.astimezone(pytz.timezone("Asia/Hong_Kong")) #astimezone method
print(dtobj_hongkong)

Naive Objekte, die mit der Klassenmethode erstellt wurden now()

Da die nowMethode das aktuelle Datum und die aktuelle Uhrzeit zurückgibt, müssen Sie zuerst die Zeitzone des Datetime-Objekts berücksichtigen. Die localize Funktion konvertiert ein naives datetime-Objekt in ein zeitzonenbewusstes datetime-Objekt. Anschließend können Sie die astimezoneMethode verwenden, um sie in eine andere Zeitzone zu konvertieren.

dtobj2=datetime.datetime.now()

mytimezone=pytz.timezone("Europe/Vienna") #my current timezone
dtobj4=mytimezone.localize(dtobj2)        #localize function

dtobj_hongkong=dtobj4.astimezone(pytz.timezone("Asia/Hong_Kong")) #astimezone method
print(dtobj_hongkong)
N Randhawa
quelle
6

Ich glaube, ich habe es herausgefunden: Berechnet die Anzahl der Sekunden seit der Epoche, konvertiert dann mit time.localtime in einen lokalen Timzeone und konvertiert dann die Zeitstruktur zurück in eine datetime ...

EPOCH_DATETIME = datetime.datetime(1970,1,1)
SECONDS_PER_DAY = 24*60*60

def utc_to_local_datetime( utc_datetime ):
    delta = utc_datetime - EPOCH_DATETIME
    utc_epoch = SECONDS_PER_DAY * delta.days + delta.seconds
    time_struct = time.localtime( utc_epoch )
    dt_args = time_struct[:6] + (delta.microseconds,)
    return datetime.datetime( *dt_args )

Die Sommer- / Winter-Sommerzeit wird korrekt angewendet:

>>> utc_to_local_datetime( datetime.datetime(2010, 6, 6, 17, 29, 7, 730000) )
datetime.datetime(2010, 6, 6, 19, 29, 7, 730000)
>>> utc_to_local_datetime( datetime.datetime(2010, 12, 6, 17, 29, 7, 730000) )
datetime.datetime(2010, 12, 6, 18, 29, 7, 730000)
Nitro Zark
quelle
1
Pfui. Aber das sollte zumindest unter Unix funktionieren. Unter Windows kann es falsch sein, wenn sich Ihre Sommerzeitregeln seit der Konvertierung geändert haben. Warum möchten Sie keine Bibliothek verwenden?
Lennart Regebro
Ich brauche nur eine utc / local-Konvertierung, daher scheint das Ziehen einer solchen Bibliothek übertrieben zu sein. Da ich der einzige Benutzer der Anwendung bin, kann ich mit einigen Einschränkungen leben. Außerdem wird vermieden, dass Probleme im Zusammenhang mit Abhängigkeiten verwaltet werden (Unterstützung von Python 3?, Windows? ...), die teurer wären als die Arbeit an meinem kleinen Skript.
Nitro Zark
1
Gut. Wenn Sie Python3 benötigen, haben Sie kein Glück. Ansonsten ist das Recherchieren und Erstellen einer eigenen Lösung im Vergleich zur Verwendung einer Bibliothek übertrieben.
Lennart Regebro
1
+1 (um die Abwertung auszugleichen: Es kann eine gültige Anforderung sein, nur nach stdlib-Lösungen zu suchen) mit der genannten Einschränkung @Lennart. Angesichts dessen kann dateutil sowohl unter Unix als auch unter Windows fehlschlagen . Übrigens können Sie utctotimestamp(utc_dt) -> (seconds, microseconds)aus Gründen der Klarheit aus Ihrem Code extrahieren , siehe Python 2.6-3.x-Implementierung
jfs
1
Ein Pytz (und ein Dateutil) funktionieren seit einiger Zeit unter Python 3, sodass die Probleme mit Python3 / Windows / Qualität kein Problem mehr darstellen.
Lennart Regebro
6

Aufbauend auf Alexeis Kommentar. Dies sollte auch für die Sommerzeit funktionieren.

import time
import datetime

def utc_to_local(dt):
    if time.localtime().tm_isdst:
        return dt - datetime.timedelta(seconds = time.altzone)
    else:
        return dt - datetime.timedelta(seconds = time.timezone)
John Kerns
quelle
4

Die Standard-Python-Bibliothek enthält überhaupt keine tzinfoImplementierungen. Ich habe dies immer als überraschendes Manko des datetime-Moduls angesehen.

Die Dokumentation für die tzinfo-Klasse enthält einige nützliche Beispiele. Suchen Sie nach dem großen Codeblock am Ende des Abschnitts.

Mark Ransom
quelle
1
Meine Vermutung bezüglich der Kernimplementierung ist, dass die Zeitzonendatenbank regelmäßig aktualisiert werden muss, da einige Länder keine festen Regeln zur Bestimmung der Sommerzeitperioden haben. Wenn ich mich nicht irre, muss zum Beispiel die JVM aktualisiert werden, um die letzte Zeitzonendatenbank zu erhalten ...
Nitro Zark
@ Nitro Zark, zumindest hätten sie einen für UTC haben sollen. Das osModul hätte basierend auf den Betriebssystemfunktionen eine für die Ortszeit bereitstellen können.
Mark Ransom
1
Ich habe die Liste der neuen Funktionen in 3.2 überprüft und sie haben die UTC-Zeitzone in 3.2 hinzugefügt ( docs.python.org/dev/whatsnew/3.2.html#datetime ). Es scheint jedoch keine lokale Zeitzone zu geben ...
Nitro Zark
1

Python 3.9 fügt das zoneinfoModul hinzu, sodass es jetzt wie folgt ausgeführt werden kann (nur stdlib):

from zoneinfo import ZoneInfo
from datetime import datetime

utc_unaware = datetime(2020, 10, 31, 12)  # loaded from database
utc_aware = utc_unaware.replace(tzinfo=ZoneInfo('UTC'))  # make aware
local_aware = utc_aware.astimezone(ZoneInfo('localtime'))  # convert

Mitteleuropa ist 1 oder 2 Stunden vor UTC, also local_aware:

datetime.datetime(2020, 10, 31, 13, 0, tzinfo=backports.zoneinfo.ZoneInfo(key='localtime'))

als str:

2020-10-31 13:00:00+01:00

Windows hat keine Systemzeitzonendatenbank, daher wird hier ein zusätzliches Paket benötigt:

pip install tzdata  

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

Eine einfache (aber möglicherweise fehlerhafte) Methode, die in Python 2 und 3 funktioniert:

import time
import datetime

def utc_to_local(dt):
    return dt - datetime.timedelta(seconds = time.timezone)

Sein Vorteil ist, dass es trivial ist, eine inverse Funktion zu schreiben

Alexei Averchenko
quelle
1
Dies führt zu einem falschen Ergebnis, da time.timezonedie Sommerzeit nicht berücksichtigt wird.
Guyarad
0

Der einfachste Weg, den ich gefunden habe, besteht darin, den Zeitversatz von Ihrem Standort zu ermitteln und diesen dann von der Stunde abzuziehen.

def format_time(ts,offset):
    if not ts.hour >= offset:
        ts = ts.replace(day=ts.day-1)
        ts = ts.replace(hour=ts.hour-offset)
    else:
        ts = ts.replace(hour=ts.hour-offset)
    return ts

Dies funktioniert bei mir in Python 3.5.2.

Zld Productions
quelle
0

Hier ist eine andere Möglichkeit, die Zeitzone im Datum / Uhrzeit-Format zu ändern (ich weiß, dass ich meine Energie dafür verschwendet habe, aber ich habe diese Seite nicht gesehen, also weiß ich nicht wie), ohne min. und sek. weil ich es für mein Projekt nicht brauche:

def change_time_zone(year, month, day, hour):
      hour = hour + 7 #<-- difference
      if hour >= 24:
        difference = hour - 24
        hour = difference
        day += 1
        long_months = [1, 3, 5, 7, 8, 10, 12]
        short_months = [4, 6, 9, 11]
        if month in short_months:
          if day >= 30:
            day = 1
            month += 1
            if month > 12:
              year += 1
        elif month in long_months:
          if day >= 31:
            day = 1
            month += 1
            if month > 12:
              year += 1
        elif month == 2:
          if not year%4==0:
            if day >= 29:
              day = 1
              month += 1
              if month > 12:
                year += 1
          else:
            if day >= 28:
              day = 1
              month += 1
              if month > 12:
                year += 1
      return datetime(int(year), int(month), int(day), int(hour), 00)
Gast
quelle
Verwenden Sie timedelta, um zwischen Zeitzonen zu wechseln. Sie benötigen lediglich den Versatz in Stunden zwischen den Zeitzonen. Sie müssen nicht mit den Grenzen für alle 6 Elemente eines datetime-Objekts herumspielen. timedelta bewältigt auch Schaltjahre, Schaltjahrhundert usw. mühelos. Sie müssen 'ab datetime import timedelta'. Wenn der Versatz eine Variable in Stunden ist: timeout = timein + timedelta (Stunden = Offset), wobei timein und timeout datetime-Objekte sind. zB timein + timedelta (Stunden = -8) konvertiert von GMT zu PST.
Karl Rudnick
0

Dies ist ein schrecklicher Weg, aber es wird vermieden, eine Definition zu erstellen. Es erfüllt die Anforderung, sich an die grundlegende Python3-Bibliothek zu halten.

# Adjust from UST to Eastern Standard Time (dynamic)
# df.my_localtime should already be in datetime format, so just in case
df['my_localtime'] = pd.to_datetime.df['my_localtime']

df['my_localtime'] = df['my_localtime'].dt.tz_localize('UTC').dt.tz_convert('America/New_York').astype(str)
df['my_localtime'] = pd.to_datetime(df.my_localtime.str[:-6])
Arthur D. Howland
quelle
Obwohl dies interessant ist und ich diese Methode verwenden werde, wird nicht nur die stdlib verwendet. Es verwendet Pandas, um die Konvertierungen durchzuführen pandas.pydata.org/pandas-docs/version/0.25/reference/api/…
Tim Hughes
-3

Verwenden Sie timedelta, um zwischen Zeitzonen zu wechseln. Sie benötigen lediglich den Versatz in Stunden zwischen den Zeitzonen. Sie müssen nicht mit den Grenzen für alle 6 Elemente eines datetime-Objekts herumspielen. timedelta bewältigt auch Schaltjahre, Schaltjahrhundert usw. mühelos. Sie müssen zuerst

from datetime import datetime, timedelta

Wenn offsetdann das Zeitzonendelta in Stunden ist:

timeout = timein + timedelta(hours = offset)

Dabei sind timein und timeout datetime-Objekte. z.B

timein + timedelta(hours = -8)

konvertiert von GMT zu PST.

Also, wie zu bestimmen offset? Hier ist eine einfache Funktion, vorausgesetzt, Sie haben nur wenige Möglichkeiten zur Konvertierung, ohne datetime-Objekte zu verwenden, die zeitzonenbewusst sind, was einige andere Antworten gut tun. Ein bisschen manuell, aber manchmal ist Klarheit am besten.

def change_timezone(timein, timezone, timezone_out):
    '''
    changes timezone between predefined timezone offsets to GMT
    timein - datetime object
    timezone - 'PST', 'PDT', 'GMT' (can add more as needed)
    timezone_out - 'PST', 'PDT', 'GMT' (can add more as needed)
    ''' 
    # simple table lookup        
    tz_offset =  {'PST': {'GMT': 8, 'PDT': 1, 'PST': 0}, \
                  'GMT': {'PST': -8, 'PDT': -7, 'GMT': 0}, \
                  'PDT': {'GMT': 7, 'PST': -1, 'PDT': 0}}
    try:
        offset = tz_offset[timezone][timezone_out]
    except:
        msg = 'Input timezone=' + timezone + ' OR output time zone=' + \
            timezone_out + ' not recognized'
        raise DateTimeError(msg)

    return timein + timedelta(hours = offset)

Nachdem ich mir die zahlreichen Antworten angesehen und mit dem engsten Code herumgespielt habe, den ich mir (vorerst) vorstellen kann, scheint es am besten, dass alle Anwendungen, bei denen Zeit wichtig ist und gemischte Zeitzonen berücksichtigt werden müssen, echte Anstrengungen unternehmen sollten, um alle Datums- / Uhrzeitobjekte zu erstellen "bewusst". Dann scheint die einfachste Antwort zu sein:

timeout = timein.astimezone(pytz.timezone("GMT"))

zum Beispiel auf GMT umstellen. Um in eine andere Zeitzone zu konvertieren, die lokal oder anderweitig gewünscht wird, verwenden Sie einfach die entsprechende Zeitzonenzeichenfolge, die pytz versteht (aus pytz.all_timezones). Die Sommerzeit wird dann ebenfalls berücksichtigt.

Karl Rudnick
quelle
1
Das ist / nicht / eine gute Idee. Die Sommerzeit verschiebt sich nicht zum gleichen Zeitpunkt für die ganze Welt. Verwenden Sie dazu datetime-Funktionen.
Gabe
@gabe - Verstehe, dass die Tabelle keine gute Idee ist, obwohl sie möglicherweise für jemanden geeignet ist, der nur US-Zeitzonen abdeckt. Wie ich am Ende sagte, ist der beste Weg, IMMER Datetime-Objekte "bewusst" zu machen, damit Sie die Datetime-Funktionen problemlos verwenden können.
Karl Rudnick