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?

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.


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)

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.


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)

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)

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


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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.