Konvertieren Sie die UTC-Datetime-Zeichenfolge in die lokale Datetime

206

Ich musste nie Zeit von und nach UTC umrechnen. Vor kurzem hatte ich die Bitte, meine App zeitzonenabhängig zu machen, und ich habe mich selbst im Kreis geführt. Viele Informationen zum Konvertieren der Ortszeit in UTC, die ich als ziemlich elementar empfand (vielleicht mache ich das auch falsch), aber ich kann keine Informationen zum einfachen Konvertieren der UTC-Zeit in die Zeitzone des Endbenutzers finden.

Kurz gesagt, die Android App sendet mir Daten (Appengine App) und innerhalb dieser Daten befindet sich ein Zeitstempel. Um diesen Zeitstempel in der von mir verwendeten Zeit zu speichern:

datetime.utcfromtimestamp(timestamp)

Das scheint zu funktionieren. Wenn meine App die Daten speichert, werden sie 5 Stunden im Voraus gespeichert (ich bin EST -5).

Die Daten werden in der BigTable von Appengine gespeichert und beim Abrufen als Zeichenfolge wie folgt ausgegeben:

"2011-01-21 02:37:21"

Wie konvertiere ich diese Zeichenfolge in eine DateTime in der korrekten Zeitzone des Benutzers?

Was ist der empfohlene Speicher für Zeitzoneninformationen eines Benutzers? (Wie speichert man normalerweise tz-Informationen, dh: "-5: 00" oder "EST" usw. usw.?) Ich bin sicher, dass die Antwort auf meine erste Frage einen Parameter enthält, der die Antworten auf die zweite Frage enthält.

MattoTodd
quelle

Antworten:

402

Wenn Sie keine eigenen tzinfoObjekte bereitstellen möchten, lesen Sie die Python-Dateutil- Bibliothek. Es bietet tzinfoImplementierungen über einer Zoneinfo (Olson) -Datenbank , sodass Sie mit einem etwas kanonischen Namen auf Zeitzonenregeln verweisen können.

from datetime import datetime
from dateutil import tz

# METHOD 1: Hardcode zones:
from_zone = tz.gettz('UTC')
to_zone = tz.gettz('America/New_York')

# METHOD 2: Auto-detect zones:
from_zone = tz.tzutc()
to_zone = tz.tzlocal()

# utc = datetime.utcnow()
utc = datetime.strptime('2011-01-21 02:37:21', '%Y-%m-%d %H:%M:%S')

# Tell the datetime object that it's in UTC time zone since 
# datetime objects are 'naive' by default
utc = utc.replace(tzinfo=from_zone)

# Convert time zone
central = utc.astimezone(to_zone)

Bearbeiten Erweitertes Beispiel, um die strptimeVerwendung anzuzeigen

Bearbeiten 2 Feste API-Verwendung, um eine bessere Einstiegspunktmethode anzuzeigen

Bearbeiten 3 Enthaltene automatische Erkennungsmethoden für Zeitzonen (Yarin)

Joe Holloway
quelle
1
Auf meinem vorherigen Kommentar, ich war in der Lage zu importieren dateutil.tzund zu verwenden tz.tzutc()und tz.tzlocal()als Zeitzone - Objekte die ich suchte. Es sieht so aus, als ob die Zeitzonendatenbank auf meinem System gut ist (ich habe eingecheckt /usr/share/zoneinfo). Ich bin mir nicht sicher, was los war.
Ben Kreeger
1
@ Benjamin Du bist auf dem richtigen Weg. Das tzModul ist der richtige Einstiegspunkt für diese Bibliothek. Ich habe meine Antwort aktualisiert, um dies widerzuspiegeln. Das dateutil.zoneinfoModul, das ich zuvor gezeigt habe, wird vom tzModul intern als Ersatz verwendet, wenn es die Zoneinfo-Datenbank des Systems nicht finden kann. Wenn Sie in die Bibliothek schauen, werden Sie feststellen, dass das verwendete Paket einen Zoneinfo-DB-Tarball enthält, wenn die DB Ihres Systems nicht gefunden werden kann. Mein altes Beispiel war der Versuch, diese Datenbank direkt zu treffen, und ich vermute, Sie hatten Probleme beim Laden dieser privaten Datenbank (ist sie nicht irgendwie auf dem Python-Pfad?)
Joe Holloway
1
@JFSebastian dies ist in # 225
Rohmer
2
@briankip Es hängt davon ab, wie anspruchsvoll Ihre Anwendung sein muss, aber in allgemeinen Anwendungen ist dies ein Problem. Erstens kann sich die Anzahl der Stunden zum Addieren / Subtrahieren je nach Jahreszeit ändern, da einige Zeitzonen die Sommerzeit berücksichtigen und andere nicht. Zweitens werden Zeitzonenregeln im Laufe der Zeit willkürlich geändert. Wenn Sie also mit einem früheren Datum / einer vergangenen Uhrzeit arbeiten, müssen Sie die Regeln anwenden, die zu diesem Zeitpunkt gültig waren, nicht die aktuelle Zeit. Aus diesem Grund ist es am besten, eine API zu verwenden, die die Olson TZ-Datenbank hinter den Kulissen nutzt.
Joe Holloway
44

Hier ist eine ausfallsichere Methode, die nicht von externen Bibliotheken abhängt:

from datetime import datetime
import time

def datetime_from_utc_to_local(utc_datetime):
    now_timestamp = time.time()
    offset = datetime.fromtimestamp(now_timestamp) - datetime.utcfromtimestamp(now_timestamp)
    return utc_datetime + offset

Dies vermeidet die Zeitprobleme in DelboyJays Beispiel. Und die geringeren zeitlichen Probleme in Erik van Oostens Änderungsantrag.

Interessanterweise kann der oben berechnete Zeitzonenversatz von dem folgenden scheinbar äquivalenten Ausdruck abweichen, wahrscheinlich aufgrund von Änderungen der Sommerzeitregeln:

offset = datetime.fromtimestamp(0) - datetime.utcfromtimestamp(0) # NO!

Update: Dieses Snippet hat die Schwäche, den UTC-Offset der aktuellen Zeit zu verwenden, der vom UTC-Offset der eingegebenen Datumszeit abweichen kann. In den Kommentaren zu dieser Antwort finden Sie eine andere Lösung.

Um die verschiedenen Zeiten zu umgehen, nehmen Sie die Epochenzeit der verstrichenen Zeit. Hier ist, was ich tue:

def utc2local (utc):
    epoch = time.mktime(utc.timetuple())
    offset = datetime.fromtimestamp (epoch) - datetime.utcfromtimestamp (epoch)
    return utc + offset
David Foster
quelle
Dies funktioniert gut und erfordert nichts anderes als Zeit und Datum / Uhrzeit.
Mike_K
6
@ Mike_K: aber es ist falsch. DST oder Zeitzonen, die in der Vergangenheit aus anderen Gründen einen anderen UTC-Offset hatten, werden nicht unterstützt. Eine vollständige Unterstützung ohne pytzdb ist nicht möglich, siehe PEP-431. Obwohl Sie eine reine stdlib-Lösung schreiben können, die in solchen Fällen auf Systemen funktioniert, die bereits eine historische Zeitzonen-Datenbank haben, z. B. Linux, OS X, siehe meine Antwort .
JFS
@JFSebastian: Sie sagen, dieser Code funktioniert im Allgemeinen nicht, aber auch unter OS X und Linux. Meinen Sie, dass dieser Code unter Windows nicht funktioniert?
David Foster
2
@DavidFoster: Ihr Code kann auch unter Linux und OS X fehlschlagen. Der Unterschied zwischen Ihrer und meiner Nur-stdlib-Lösung besteht darin, dass Ihre den aktuellen Offset verwendet, der sich möglicherweise vom aktuellen utc-Offset unterscheidet utc_datetime.
JFS
1
Guter Anruf. Das ist ein sehr subtiler Unterschied. Der UTC-Offset kann sich auch im Laufe der Zeit ändern. Seufzer
David Foster
23

Siehe die datetime- Dokumentation zu tzinfo- Objekten. Sie müssen die Zeitzonen implementieren, die Sie selbst unterstützen möchten. Dies sind Beispiele am Ende der Dokumentation.

Hier ist ein einfaches Beispiel:

from datetime import datetime,tzinfo,timedelta

class Zone(tzinfo):
    def __init__(self,offset,isdst,name):
        self.offset = offset
        self.isdst = isdst
        self.name = name
    def utcoffset(self, dt):
        return timedelta(hours=self.offset) + self.dst(dt)
    def dst(self, dt):
            return timedelta(hours=1) if self.isdst else timedelta(0)
    def tzname(self,dt):
         return self.name

GMT = Zone(0,False,'GMT')
EST = Zone(-5,False,'EST')

print datetime.utcnow().strftime('%m/%d/%Y %H:%M:%S %Z')
print datetime.now(GMT).strftime('%m/%d/%Y %H:%M:%S %Z')
print datetime.now(EST).strftime('%m/%d/%Y %H:%M:%S %Z')

t = datetime.strptime('2011-01-21 02:37:21','%Y-%m-%d %H:%M:%S')
t = t.replace(tzinfo=GMT)
print t
print t.astimezone(EST)

Ausgabe

01/22/2011 21:52:09 
01/22/2011 21:52:09 GMT
01/22/2011 16:52:09 EST
2011-01-21 02:37:21+00:00
2011-01-20 21:37:21-05:00a
Mark Tolonen
quelle
Danke für's Annehmen! Ich habe es leicht aktualisiert, um Ihre spezifische Beispielzeit zu übersetzen. Durch das Parsen der Zeit wird eine von den Dokumenten als "naiv" bezeichnete Zeit erstellt. Verwenden replaceSie diese Option, um die Zeitzone auf GMT einzustellen und die Zeitzone "bewusst" zu machen. Verwenden Sie astimezonesie dann , um in eine andere Zeitzone zu konvertieren.
Mark Tolonen
Tut mir leid, dass ich gegen Joes Antwort getauscht habe. Technisch erklärt Ihre Antwort genau, wie es mit rohem Python geht (was gut zu wissen ist), aber die von ihm vorgeschlagene Bibliothek bringt mich viel schneller zu einer Lösung.
MattoTodd
4
Sieht nicht so aus, als würde die Sommerzeit automatisch verarbeitet.
Gringo Suave
@GringoSuave: Das war nicht beabsichtigt. Die Regeln dafür sind kompliziert und ändern sich oft.
Mark Tolonen
19

Wenn Sie das richtige Ergebnis auch für die Zeit erhalten möchten, die einer mehrdeutigen Ortszeit entspricht (z. B. während eines Sommerzeitübergangs) und / oder der lokale UTC-Offset zu verschiedenen Zeiten in Ihrer lokalen Zeitzone unterschiedlich ist, verwenden Sie pytzZeitzonen:

#!/usr/bin/env python
from datetime import datetime
import pytz    # $ pip install pytz
import tzlocal # $ pip install tzlocal

local_timezone = tzlocal.get_localzone() # get pytz tzinfo
utc_time = datetime.strptime("2011-01-21 02:37:21", "%Y-%m-%d %H:%M:%S")
local_time = utc_time.replace(tzinfo=pytz.utc).astimezone(local_timezone)
jfs
quelle
10

Diese Antwort sollte hilfreich sein, wenn Sie keine anderen Module verwenden möchten datetime.

datetime.utcfromtimestamp(timestamp)gibt ein naives datetimeObjekt zurück (kein bewusstes). Bewusste sind zeitzonenbewusst und naiv nicht. Sie möchten eine bewusste, wenn Sie zwischen Zeitzonen konvertieren möchten (z. B. zwischen UTC und Ortszeit).

Wenn Sie nicht derjenige sind, der das Datum zu Beginn instanziiert, aber dennoch ein naives datetimeObjekt in UTC-Zeit erstellen können, sollten Sie diesen Python 3.x-Code ausprobieren, um es zu konvertieren:

import datetime

d=datetime.datetime.strptime("2011-01-21 02:37:21", "%Y-%m-%d %H:%M:%S") #Get your naive datetime object
d=d.replace(tzinfo=datetime.timezone.utc) #Convert it to an aware datetime object in UTC time.
d=d.astimezone() #Convert it to your local timezone (still aware)
print(d.strftime("%d %b %Y (%I:%M:%S:%f %p) %Z")) #Print it with a directive of choice

Achten Sie darauf, nicht fälschlicherweise anzunehmen, dass die Sommerzeit mit dem oben genannten Code nicht funktioniert, wenn Ihre Zeitzone derzeit MDT ist, da MST gedruckt wird. Sie werden feststellen, dass MDT gedruckt wird, wenn Sie den Monat auf August ändern.

Eine andere einfache Möglichkeit, ein bewusstes datetimeObjekt (auch in Python 3.x) abzurufen, besteht darin, es mit einer Zeitzone zu erstellen, mit der zunächst begonnen wurde. Hier ist ein Beispiel mit UTC:

import datetime, sys

aware_utc_dt_obj=datetime.datetime.now(datetime.timezone.utc) #create an aware datetime object
dt_obj_local=aware_utc_dt_obj.astimezone() #convert it to local time

#The following section is just code for a directive I made that I liked.
if sys.platform=="win32":
    directive="%#d %b %Y (%#I:%M:%S:%f %p) %Z"
else:
    directive="%-d %b %Y (%-I:%M:%S:%f %p) %Z"

print(dt_obj_local.strftime(directive))

Wenn Sie Python 2.x verwenden, müssen Sie wahrscheinlich eine Unterklasse datetime.tzinfoverwenden, um ein bewusstes datetimeObjekt zu erstellen , da datetime.timezonees in Python 2.x nicht vorhanden ist.

Brōtsyorfuzthrāx
quelle
8

Wenn Sie Django verwenden, können Sie die folgende timezone.localtimeMethode verwenden:

from django.utils import timezone
date 
# datetime.datetime(2014, 8, 1, 20, 15, 0, 513000, tzinfo=<UTC>)

timezone.localtime(date)
# datetime.datetime(2014, 8, 1, 16, 15, 0, 513000, tzinfo=<DstTzInfo 'America/New_York' EDT-1 day, 20:00:00 DST>)
frmdstryr
quelle
2

Sie können Pfeil verwenden

from datetime import datetime
import arrow

now = datetime.utcnow()

print(arrow.get(now).to('local').format())
# '2018-04-04 15:59:24+02:00'

Sie können arrow.get()mit allem füttern . Zeitstempel, ISO-String usw.

d21d3q
quelle
1

Ich verschiebe dies traditionell auf das Frontend - sende Zeiten vom Backend als Zeitstempel oder ein anderes Datum / Uhrzeit-Format in UTC, lasse dann den Client den Zeitzonenversatz herausfinden und rendere diese Daten in der richtigen Zeitzone.

Für eine Webanwendung ist dies in Javascript ziemlich einfach. Sie können den Zeitzonenversatz des Browsers mithilfe integrierter Methoden ziemlich einfach ermitteln und dann die Daten aus dem Backend ordnungsgemäß rendern.

Matt Billenstein
quelle
3
Dies funktioniert in vielen Fällen gut, in denen Sie lediglich die Anzeige lokalisieren. Gelegentlich müssen Sie jedoch eine Geschäftslogik basierend auf der Zeitzone des Benutzers ausführen, und Sie möchten in der Lage sein, die Konvertierung auf der Ebene des Anwendungsservers durchzuführen.
Joe Holloway
1

Sie können verwenden calendar.timegm, um Ihre Zeit seit der Unix-Epoche in Sekunden time.localtimeumzuwandeln und zurück zu konvertieren:

import calendar
import time

time_tuple = time.strptime("2011-01-21 02:37:21", "%Y-%m-%d %H:%M:%S")
t = calendar.timegm(time_tuple)

print time.ctime(t)

Gibt Fri Jan 21 05:37:21 2011(weil ich in UTC + 03: 00 Zeitzone bin).

myaut
quelle
1
import datetime

def utc_str_to_local_str(utc_str: str, utc_format: str, local_format: str):
    """
    :param utc_str: UTC time string
    :param utc_format: format of UTC time string
    :param local_format: format of local time string
    :return: local time string
    """
    temp1 = datetime.datetime.strptime(utc_str, utc_format)
    temp2 = temp1.replace(tzinfo=datetime.timezone.utc)
    local_time = temp2.astimezone()
    return local_time.strftime(local_format)

utc = '2018-10-17T00:00:00.111Z'
utc_fmt = '%Y-%m-%dT%H:%M:%S.%fZ'
local_fmt = '%Y-%m-%dT%H:%M:%S+08:00'
local_string = utc_str_to_local_str(utc, utc_fmt, local_fmt)
print(local_string)   # 2018-10-17T08:00:00+08:00

Zum Beispiel ist meine Zeitzone ' +08: 00 '. Eingabe utc = 2018-10-17T00: 00: 00.111Z , dann erhalte ich Ausgabe = 2018-10-17T08: 00: 00 + 08: 00

Eureka Bing
quelle
1
Sie sollten eine bessere Beschreibung Ihres Problems im Klartext geben, wo Sie dem Rest der Benutzer erklären, was Sie erreichen
möchten
1

Aus der Antwort hier können Sie das Zeitmodul verwenden, um von utc in die auf Ihrem Computer eingestellte Ortszeit zu konvertieren:

utc_time = time.strptime("2018-12-13T10:32:00.000", "%Y-%m-%dT%H:%M:%S.%f")
utc_seconds = calendar.timegm(utc_time)
local_time = time.localtime(utc_seconds)
Franksands
quelle
0

Hier ist eine schnelle und schmutzige Version, die die lokalen Systemeinstellungen verwendet, um den Zeitunterschied zu ermitteln. HINWEIS: Dies funktioniert nicht, wenn Sie in eine Zeitzone konvertieren müssen, in der Ihr aktuelles System nicht ausgeführt wird. Ich habe dies mit UK-Einstellungen unter BST-Zeitzone getestet

from datetime import datetime
def ConvertP4DateTimeToLocal(timestampValue):
   assert isinstance(timestampValue, int)

   # get the UTC time from the timestamp integer value.
   d = datetime.utcfromtimestamp( timestampValue )

   # calculate time difference from utcnow and the local system time reported by OS
   offset = datetime.now() - datetime.utcnow()

   # Add offset to UTC time and return it
   return d + offset
DelboyJay
quelle
Es sollte sein datetime.fromtimestamp(ts): Posix-Zeitstempel (Float-Sekunden) -> Datum / Uhrzeit-Objekt in lokaler Zeit (es funktioniert gut, wenn sich das Betriebssystem vergangene UTC-Offsets für die lokale Zeitzone merkt, dh unter Unix, aber nicht unter Windows für Daten in der Vergangenheit)). Andernfalls könnte Pytz verwendet werden.
JFS
Darf ich vorschlagen, Folgendes zu tun: offset = datetime.utcnow().replace(minute=0, second=0, microsecond=0) - datetime.now().replace(minute=0, second=0, microsecond=0) Ich habe seltsame Mikrosekundenunterschiede ohne sie.
Erik van Oosten
@ErikvanOosten: hast du es datetime.fromtimestamp(ts)statt der antwort versucht ?
JFS
0

Konsolidierung der Antwort von Franksand zu einer bequemen Methode.

import calendar
import datetime

def to_local_datetime(utc_dt):
    """
    convert from utc datetime to a locally aware datetime according to the host timezone

    :param utc_dt: utc datetime
    :return: local timezone datetime
    """
    return datetime.datetime.fromtimestamp(calendar.timegm(utc_dt.timetuple()))
Martlark
quelle