Python, Datum und Zeit

Einführung

Python, Date and Time WEBOFF

Python bietet reichlich Funktionalität, um mit Datums und Zeit-Daten umzugehen. Die Standard-Bibliotheken enthalten folgende Module:

Diese Module bieten Klassen zur Manipulation von simplen und komplexen Datums- und Zeit-Daten.

Speziell die datetime-Klasse ist sehr wichtig für die Timeseries in Pandas.

Python Standard Module für Zeit-Daten

Die wichtigsten Module in Python, um mit Zeiten zu arbeiten, sind time, calendar und datetime.

Das datetime-Modul bietet diverse Klassen, Methoden und Funktionen für die Arbeit mit Daten, Zeiten und Zeit-Intervallen. Dafür stehen folgende Klassen zur Verfügung:

Wir starten mit dem date-Objekt.

Die Date Klasse

from datetime import date
x = date(1993, 12, 14)
print(x)
1993-12-14

Wir können Datums-Objekte instanziieren zwischen dem 01. Januar 0001 und dem 31. Dezember 9999. Über die Attribute min und max kann dies ermittelt werden:

from datetime import date
print(date.min)
print(date.max)
0001-01-01
9999-12-31

Wir können diverse Methoden auf das obige Date-Objekt anwenden. Die Methode toordinal() liefert das proleptische Gregorianische Ordinal. Der proleptische gregorianische Kalender besteht aus der Erweiterung des gregorianischen Kalenders rückwärts bis zum Tag seiner Einführung am Jahr 1582. Der 1. Januar 1582 ist somit der 1. Tag.

x.toordinal()
Führt man obigen Code aus, erhält man folgende Ausgabe:
727911

Aus dem Ordinal kann das Datum wieder herausgerechnet werden mit der Klassen-Methode fromordinal():

date.fromordinal(727911)
Wir erhalten die folgende Ausgabe:
datetime.date(1993, 12, 14)

Wenn Sie den Wochentag eines bestimmten Tages wissen möchten, kann dies mit der Methode weekday() berechnet werden :

x.weekday()
Führt man obigen Code aus, erhält man folgende Ausgabe:
1
date.today()
Wir erhalten die folgende Ausgabe:
datetime.date(2019, 1, 20)

Über die Attribute können wir auf den Tag, den Monat und das Jahr eines Date-Objektes zugreifen:

print(x.day)
print(x.month)
print(x.year)
14
12
1993

Die time-Klasse

Die time-Klasse ist gleich organisiert wie die date-Klasse.

from datetime import time
t = time(15, 6, 23)
print(t)
15:06:23

Die möglichen Zeiten liegen zwischen:

print(time.min)
print(time.max)
00:00:00
23:59:59.999999

Der Zugriff auf Stunde, Minute und Sekunde:

t.hour, t.minute, t.second
Wir erhalten die folgende Ergebnisse:
(15, 6, 23)

Jede Komponente eines Zeit-Objektes kann durch replace() geändert werden:

t = t.replace(hour=11, minute=59)
t
Führt man obigen Code aus, erhält man folgendes Ergebnis:
datetime.time(11, 59, 23)

Wir können einen Datums-String in C-Style generieren, entsprechend der ctime-Funktion in C:

x.ctime()
Der obige Python-Code liefert folgendes Ergebnis:
'Tue Dec 14 00:00:00 1993'

Die datetime-Klasse

Das datetime-Modul bietet uns Funktionen und Methoden zur Manipulation von Datums- und Zeit-Objekten. Weiterhin stellt es Funktionalitäten zur Verfügung für arithmetische Operationen für Datums- und Zeit-Objekte, z.B. Addition und Subtraktion. Ein weiterer Fokus der Implementierung liegt auf der Extrahierung von Attributen.

Es gibt zwei Arten von Datums- und Zeit-Objekten:

Wenn ein Zeit-Objekt 'naive' ist, enthält es keine Informationen für den Vergleich oder Lokalisation gegenüber anderen Datums- oder Zeit-Objekten. Die Semantik, falls das 'naive'-Objekt einer bestimmten Zeitzone entspricht (wie bspw. UTC, lokale Zeit, etc.), ist in der Logik des Programs verankert.

Auf der anderen Seite hat ein 'aware'-Objekt Informationen über die Zeitzone. Somit kann es gegenüber anderen 'aware'-Objekten lokalisiert werden.

Wie können Sie herausfinden, ob ein datetime-Objekt t 'aware' ist?

t ist 'aware' wenn t.tzinfo nicht None ist und t.tzinfo.utcoffset(t) nicht None ist. Beide Bedingungen müssen erfüllt sein.

Dem gegenüber das Objekt t 'naive' wenn t.tzinfo oder t.tzinfo.utcoffset(t) None ist.

Erstellen wir ein datetime-Objekt:

from datetime import datetime
t = datetime(2017, 4, 19, 16, 31, 0)
t
Führt man obigen Code aus, erhält man folgende Ausgabe:
datetime.datetime(2017, 4, 19, 16, 31)

t ist naive, weil folgender Ausdruck True ist:

t.tzinfo == None
Der obige Code führt zu folgendem Ergebnis:
True

Wir erstellen ein 'aware' datetime-Objekt vom aktuellen Datum. Dafür benötigen wir das Modul pytz. pytz ist ein Modul, welches die 'Olsen Zeitzonen Datenbank' in Python bereitstellt. Die Olsen Zeitzonen werden nahezu komplett durch dieses Modul unterstützt.

from datetime import datetime
import pytz
t = datetime.now(pytz.utc)

Wir sehen, dass sowohl t.tzinfo als auch t.tzinfo.utcoffset(t) nicht None sind und t somit ein 'aware'-Objekt ist:

t.tzinfo, t.tzinfo.utcoffset(t)
Wir erhalten die folgende Ausgabe:
(<UTC>, datetime.timedelta(0))
from datetime import datetime, timedelta as delta
ndays = 15
start = datetime(1991, 4, 30)
dates = [start - delta(days=x) for x in range(0, ndays)]
dates
Wir können die folgende Ausgabe erwarten, wenn wir den obigen Python-Code ausführen:
[datetime.datetime(1991, 4, 30, 0, 0),
 datetime.datetime(1991, 4, 29, 0, 0),
 datetime.datetime(1991, 4, 28, 0, 0),
 datetime.datetime(1991, 4, 27, 0, 0),
 datetime.datetime(1991, 4, 26, 0, 0),
 datetime.datetime(1991, 4, 25, 0, 0),
 datetime.datetime(1991, 4, 24, 0, 0),
 datetime.datetime(1991, 4, 23, 0, 0),
 datetime.datetime(1991, 4, 22, 0, 0),
 datetime.datetime(1991, 4, 21, 0, 0),
 datetime.datetime(1991, 4, 20, 0, 0),
 datetime.datetime(1991, 4, 19, 0, 0),
 datetime.datetime(1991, 4, 18, 0, 0),
 datetime.datetime(1991, 4, 17, 0, 0),
 datetime.datetime(1991, 4, 16, 0, 0)]

Unterschied zwischen Zeiten

Schauen wir was passiert, wenn wir datetime-Objekte voneinander subtrahieren:

from datetime import datetime
delta = datetime(1993, 12, 14) - datetime(1991, 4, 30)
delta, type(delta)
Wir erhalten die folgende Ergebnisse:
(datetime.timedelta(days=959), datetime.timedelta)

Das Ergebnis der Subtraktion der beiden datetime-Objekte ist ein timedelta-Objekt. Über das Attribut days können wir die Tage der Differenz auslesen:

delta.days
Der obige Python-Code führt zu folgender Ausgabe:
959
t1 = datetime(2017, 1, 31, 14, 17)
t2 = datetime(2015, 12, 15, 16, 59)
delta = t1 - t2
delta.days, delta.seconds
Der obige Python-Code führt zu folgender Ausgabe:
(412, 76680)

Es ist möglich ein timedelta-Objekt von einem anderen datetime-Objekt zu subtrahieren oder es zu addieren (in Tagen) um ein neues datetime-Objekt zu berechnen:

from datetime import datetime, timedelta
d1 = datetime(1991, 4, 30)
d2 = d1 + timedelta(10)
print(d2)
print(d2 - d1)
d3 = d1 - timedelta(100)
print(d3)
d4 = d1 - 2 * timedelta(50)
print(d4)
1991-05-10 00:00:00
10 days, 0:00:00
1991-01-20 00:00:00
1991-01-20 00:00:00

Ebenso können timedelta-Objekte auch in Tagen und Minuten zu datetime-Objekten addiert oder voneinander subtrahiert werden:

from datetime import datetime, timedelta
d1 = datetime(1991, 4, 30)
d2 = d1 + timedelta(10,100)
print(d2)
print(d2 - d1)
1991-05-10 00:01:40
10 days, 0:01:40

Wandlung von datetime-Objekten in Strings

Der einfachste Weg ein datetime-Objekt als String darzustellen ist die str()-Methode.

s = str(d1)
s
Der obige Code führt zu folgendem Ergebnis:
'1991-04-30 00:00:00'

Wandlung mit strftime

Der Methodenaufruf datetime.strftime(format) liefert einen String zurück, welcher die Zeit und das Datum repräsentiert, jedoch durch einen explizites Format bestimmt wird. Ein komplette Liste von möglichen Formatierungen kann hier (strftime) eingesehen werden:

print(d1.strftime('%Y-%m-%d'))
print("Wochentag: " + d1.strftime('%a'))
print("Wochentag ausgeschrieben: " + d1.strftime('%A'))
# Weekday as a decimal number, where 0 is Sunday 
# and 6 is Saturday
print("Wochentag als Dezimalzahl: " + d1.strftime('%w'))
1991-04-30
Wochentag: Tue
Wochentag ausgeschrieben: Tuesday
Wochentag als Dezimalzahl: 2

Formatierung von Monaten:

# Day of the month as a zero-padded decimal number. 
# 01, 02, ..., 31
print(d1.strftime('%d'))
# Month as locale’s abbreviated name. 
# Jan, Feb, ..., Dec (en_US); 
# Jan, Feb, ..., Dez (de_DE)
print(d1.strftime('%b'))
# Month as locale’s full name. 	
# January, February, ..., December (en_US);
# Januar, Februar, ..., Dezember (de_DE)
print(d1.strftime('%B'))
# Month as a zero-padded decimal number. 
# 01, 02, ..., 12
print(d1.strftime('%m'))
30
Apr
April
04

Ausgabe in Landessprache

Wir haben bereits gesehen, dass die Datumsausgaben in Englisch erfolgt sind. Im folgenden geben wir ein Datum auf die im Vereinigten Königreich gebräuchlichste Art aus:

from datetime import datetime, timedelta
d1 = datetime(1993, 12, 14)
print(d1.strftime('%d %B %Y'))
print("Nur in Zahlen:")
print(d1.strftime('%d/%m/%Y'))
print("Auf US Art:")
print(d1.strftime('%m/%d/%Y'))
print(f"It was a {d1.strftime('%A'):s}!")
14 December 1993
Nur in Zahlen:
14/12/1993
Auf US Art:
12/14/1993
It was a Tuesday!

Eine häufig gestellte Frage lautet, wie man diese Ausgaben in Landessprache ausgeben kann. Zunächst ist es wichtig das locale-Modul zu importieren:

from datetime import datetime, timedelta
import locale
# Umstellung auf Deutsch:
locale.setlocale(locale.LC_ALL, 'de_DE.utf8')
d1 = datetime(1993, 12, 14)
print(d1.strftime('%d. %B %Y'))
print("Nur in Zahlen:")
print(d1.strftime('%d.%m.%Y'))
print(f"Der {d1.strftime('%d.%m.%Y'):s} war ein {d1.strftime('%A'):s}!")
# und nun in Französisch:
      
locale.setlocale(locale.LC_ALL, 'fr_FR.utf8')
d1 = datetime(1993, 12, 14)
print(d1.strftime('%d %B %Y'))
print("Seulement en nombre:")
print(d1.strftime('%d.%m.%Y'))
print(f"Le {d1.strftime('%d %m %Y'):s} était un {d1.strftime('%A'):s}!")
14. Dezember 1993
Nur in Zahlen:
14.12.1993
Der 14.12.1993 war ein Dienstag!
14 décembre 1993
Seulement en nombre:
14.12.1993
Le 14 12 1993 était un mardi!

Anmerkung:

Die länderspezifischen Ausgaben der obigen Beispiele funktionieren nur, falls 'de_DE.utf8' und 'fr_FR.utf8' im Betriebssystem installiert sind. Unter Ubuntu kann man diese wie folgt installieren:

sudo locale-gen fr_FR.UTF-8

und

sudo locale-gen de_DE.UTF-8

datetime-Objekte aus Strings erstellen

Wir können strptime() nutzen, um neue datetime-Objekte aus Strings zu erstellen, welche Datum und Zeit beinhalten. Die Argumente von strptime() sind der String, welcher geparsed werden soll, und die Format-Spezifikation.

from datetime import datetime
t = datetime.strptime("30 Nov 00", "%d %b %y")
print(t)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-29-dff4e9a226bd> in <module>()
      1 from datetime import datetime
----> 2 t = datetime.strptime("30 Nov 00", "%d %b %y")
      3 print(t)
~/anaconda3/lib/python3.7/_strptime.py in _strptime_datetime(cls, data_string, format)
    575     """Return a class cls instance based on the input string and the
    576     format string."""
--> 577     tt, fraction, gmtoff_fraction = _strptime(data_string, format)
    578     tzname, gmtoff = tt[-2:]
    579     args = tt[:6] + (fraction,)
~/anaconda3/lib/python3.7/_strptime.py in _strptime(data_string, format)
    357     if not found:
    358         raise ValueError("time data %r does not match format %r" %
--> 359                          (data_string, format))
    360     if len(data_string) != found.end():
    361         raise ValueError("unconverted data remains: %s" %
ValueError: time data '30 Nov 00' does not match format '%d %b %y'
In [ ]:
dt = "2007-03-04T21:08:12"
datetime.strptime( dt, "%Y-%m-%dT%H:%M:%S" )
In [ ]:
dt = '12/24/1957 4:03:29 AM'
dt = datetime.strptime(dt, '%m/%d/%Y %I:%M:%S %p')
dt

Auf einer Linux-Maschine können wir einen englischen Datumsstring generieren mit dem Kommando LC_ALL=en_EN.utf8 date.

In [ ]:
dt = 'Wed Apr 12 20:29:53 CEST 2017'
dt = datetime.strptime(dt, '%a %b %d %H:%M:%S %Z %Y')
print(dt)

Obwohl datetime.strptime() eine einfache Möglichkeit ist, ein Datum mit einem bekannten Format zu parsen, kann es doch kompliziert sein, jedes Mal eine neue Spezifikation zu erstellen für neue Datumsformate.

Für das Parsen ist die Nutzung der Methode dateutil.parser besser:

In [ ]:
from dateutil.parser import parse
parse('2011-01-03')
In [ ]:
parse('Wed Apr 12 20:29:53 CEST 2017')