Wie analysiere ich Daten mit einer Zeitzonenzeichenfolge von -0400 in Python?

80

Ich habe eine Datumszeichenfolge in der Form '2009/05/13 19:19:30 -0400'. Es scheint, dass frühere Versionen von Python möglicherweise ein% z-Format-Tag in strptime für die nachfolgende Zeitzonenspezifikation unterstützt haben, aber 2.6.x scheint dies entfernt zu haben.

Was ist der richtige Weg, um diese Zeichenfolge in ein datetime-Objekt zu analysieren?

Felder
quelle

Antworten:

116

Sie können die Analysefunktion von dateutil verwenden:

>>> from dateutil.parser import parse
>>> d = parse('2009/05/13 19:19:30 -0400')
>>> d
datetime.datetime(2009, 5, 13, 19, 19, 30, tzinfo=tzoffset(None, -14400))

Auf diese Weise erhalten Sie ein Datum / Uhrzeit-Objekt, das Sie dann verwenden können.

Wie beantwortet , ist dateutil2.0 für Python 3.0 geschrieben und funktioniert nicht mit Python 2.x. Für Python 2.x muss dateutil1.5 verwendet werden.

txwikinger
quelle
13
Dies funktioniert gut für mich ( dateutil2.1) mit Python 2.7.2; Python 3 ist nicht erforderlich. Beachten Sie, dass bei der Installation von pip der Paketname lautet python-dateutil.
BigglesZX
44

%z wird in Python 3.2+ unterstützt:

>>> from datetime import datetime
>>> datetime.strptime('2009/05/13 19:19:30 -0400', '%Y/%m/%d %H:%M:%S %z')
datetime.datetime(2009, 5, 13, 19, 19, 30,
                  tzinfo=datetime.timezone(datetime.timedelta(-1, 72000)))

Bei früheren Versionen:

from datetime import datetime

date_str = '2009/05/13 19:19:30 -0400'
naive_date_str, _, offset_str = date_str.rpartition(' ')
naive_dt = datetime.strptime(naive_date_str, '%Y/%m/%d %H:%M:%S')
offset = int(offset_str[-4:-2])*60 + int(offset_str[-2:])
if offset_str[0] == "-":
   offset = -offset
dt = naive_dt.replace(tzinfo=FixedOffset(offset))
print(repr(dt))
# -> datetime.datetime(2009, 5, 13, 19, 19, 30, tzinfo=FixedOffset(-240))
print(dt)
# -> 2009-05-13 19:19:30-04:00

Wo FixedOffsetist eine Klasse basierend auf dem Codebeispiel aus den Dokumenten :

from datetime import timedelta, tzinfo

class FixedOffset(tzinfo):
    """Fixed offset in minutes: `time = utc_time + utc_offset`."""
    def __init__(self, offset):
        self.__offset = timedelta(minutes=offset)
        hours, minutes = divmod(offset, 60)
        #NOTE: the last part is to remind about deprecated POSIX GMT+h timezones
        #  that have the opposite sign in the name;
        #  the corresponding numeric value is not used e.g., no minutes
        self.__name = '<%+03d%02d>%+d' % (hours, minutes, -hours)
    def utcoffset(self, dt=None):
        return self.__offset
    def tzname(self, dt=None):
        return self.__name
    def dst(self, dt=None):
        return timedelta(0)
    def __repr__(self):
        return 'FixedOffset(%d)' % (self.utcoffset().total_seconds() / 60)
jfs
quelle
1
Dies führt ValueError: 'z' is a bad directive in format '%Y-%m-%d %M:%H:%S.%f %z'in meinem Fall zu einem (Python 2.7).
Jonathan H
@Sheljohn es soll nicht mit Python 2.7 funktionieren Schauen Sie sich ganz oben in der Antwort an.
JFS
Übrigens seltsam, dass dies in Python 2.7- Dokumenten überhaupt nicht erwähnt wird: docs.python.org/2.7/library/…
62mkv
22

Hier finden Sie eine Lösung für das "%z"Problem mit Python 2.7 und früheren Versionen

Anstatt zu verwenden:

datetime.strptime(t,'%Y-%m-%dT%H:%M %z')

Verwenden Sie die timedelta, um die Zeitzone wie folgt zu berücksichtigen:

from datetime import datetime,timedelta
def dt_parse(t):
    ret = datetime.strptime(t[0:16],'%Y-%m-%dT%H:%M')
    if t[18]=='+':
        ret-=timedelta(hours=int(t[19:22]),minutes=int(t[23:]))
    elif t[18]=='-':
        ret+=timedelta(hours=int(t[19:22]),minutes=int(t[23:]))
    return ret

Beachten Sie, dass die Daten in konvertiert werden GMT, wodurch Datumsarithmetik durchgeführt werden kann, ohne sich um Zeitzonen kümmern zu müssen.

Uri Goren
quelle
Ich mag das, obwohl Sie 'Sekunden =' in 'Minuten =' ändern müssen.
Dave
1
Wenn Sie eine Zeitzone in einer Zeichenfolge verwenden und die Datums- und Uhrzeitangabe in UTC konvertieren möchten, verwenden Sie die hier aufgeführte entgegengesetzte Logik. Wenn die Zeitzone ein + hat, subtrahieren Sie das Zeitdelta und umgekehrt.
Sektor 95
Die Umwandlung in UTC war falsch. Wenn ein +Zeichen vorhanden ist, sollte das Zeitdelta subtrahiert werden und umgekehrt. Ich habe den Code bearbeitet und korrigiert.
Tomtastico
7

Das Problem bei der Verwendung von dateutil besteht darin, dass Sie nicht sowohl für die Serialisierung als auch für die Deserialisierung dieselbe Formatzeichenfolge verwenden können, da dateutil nur über begrenzte Formatierungsoptionen verfügt (nur dayfirstund yearfirst).

In meiner Anwendung speichere ich die Formatzeichenfolge in einer INI-Datei, und jede Bereitstellung kann ein eigenes Format haben. Daher mag ich den Dateutil-Ansatz wirklich nicht.

Hier ist eine alternative Methode, die stattdessen Pytz verwendet:

from datetime import datetime, timedelta

from pytz import timezone, utc
from pytz.tzinfo import StaticTzInfo

class OffsetTime(StaticTzInfo):
    def __init__(self, offset):
        """A dumb timezone based on offset such as +0530, -0600, etc.
        """
        hours = int(offset[:3])
        minutes = int(offset[0] + offset[3:])
        self._utcoffset = timedelta(hours=hours, minutes=minutes)

def load_datetime(value, format):
    if format.endswith('%z'):
        format = format[:-2]
        offset = value[-5:]
        value = value[:-5]
        return OffsetTime(offset).localize(datetime.strptime(value, format))

    return datetime.strptime(value, format)

def dump_datetime(value, format):
    return value.strftime(format)

value = '2009/05/13 19:19:30 -0400'
format = '%Y/%m/%d %H:%M:%S %z'

assert dump_datetime(load_datetime(value, format), format) == value
assert datetime(2009, 5, 13, 23, 19, 30, tzinfo=utc) \
    .astimezone(timezone('US/Eastern')) == load_datetime(value, format)
sayap
quelle
2

Ein Liner für alte Pythons da draußen. Sie können ein Zeitdelta je nach +/- Vorzeichen mit 1 / -1 multiplizieren, wie in:

datetime.strptime(s[:19], '%Y-%m-%dT%H:%M:%S') + timedelta(hours=int(s[20:22]), minutes=int(s[23:])) * (-1 if s[19] == '+' else 1)
Eric Sellin
quelle
-10

Wenn Sie unter Linux arbeiten, können Sie mit dem externen dateBefehl dwim:

import commands, datetime

def parsedate(text):
  output=commands.getoutput('date -d "%s" +%%s' % text )
  try:
      stamp=eval(output)
  except:
      print output
      raise
  return datetime.datetime.frometimestamp(stamp)

Dies ist natürlich weniger portabel als dateutil, aber etwas flexibler, da dateauch Eingaben wie "gestern" oder "letztes Jahr" akzeptiert werden :-)

Gyom
quelle
3
Ich denke nicht, dass es gut ist, dafür ein externes Programm aufzurufen. Und die nächste Schwachstelle: eval (): Wenn Sie jetzt, da ein Webserver diesen Code ausführt, können Sie eine beliebige Codeausführung auf dem Server durchführen!
Guettli
5
Es hängt alles vom Kontext ab: Wenn wir nur ein Skript zum
Wegwerfen und Wegwerfen suchen
10
Dies herunterzustimmen, weil: 1) ein Systemaufruf für etwas Triviales ausgeführt wird, 2) Zeichenfolgen direkt in einen Shell-Aufruf eingefügt werden, 3) eval () aufgerufen wird und 4) eine Ausnahme-Catch-All vorliegt. Grundsätzlich ist dies ein Beispiel dafür, wie man Dinge nicht tut.
benjaoming
In diesem Fall ist eval zwar böse und sollte nicht verwendet werden. Ein externer Aufruf scheint der einfachste und praktischste Weg zu sein, um einen Unix-Zeitstempel von einem zeitzonensensitiven Datenring abzurufen, bei dem die Zeitzone kein numerischer Offset ist.
Leliel
1
Nun, auch dieses Motto "Eval is Evil" hängt wirklich von Ihrem Kontext ab (der vom OP nicht angegeben wurde). Wenn ich Skripte für meinen eigenen Gebrauch schreibe, verwende ich eval großzügig und es ist großartig. Python ist eine großartige Sprache für Klebeskripte! Natürlich können Sie komplizierte, überentwickelte Lösungen für allgemeine Fälle wie in den obigen Antworten einführen und dann behaupten, dies sei der einzige richtige Weg, dies zu tun, ala Java. Aber für viele Anwendungsfälle ist eine schnelle und schmutzige Lösung genauso gut.
Gyom