Donnerstag, 1. April 2010

Spielereien mit Zeit und Datum

Das Rechnen mit Datum und Zeit ist nur selten unproblematisch, PHP stellt uns dafür zahlreiche Funktionen zur Verfügung. Wir wollen uns im folgenden ein paar davon ansehen und an Beispielen erproben.
Dieser Beitrag ist auf PHP-resource.de erschienen, hier ist eine aktuellere Version.

Die Unix-Zeit mit time() und date()

Die formatierte Ausgabe von Zeit und der Vergleich verschiedener Zeitangaben ist immer wieder ein beliebtes Thema welches für viel "Freude" sorgt, besonders wenn Zeitangaben aus verschiedenen Quellen stammen und unterschiedlich formatiert sind.

Eine unverzichtbare, wenn auch nicht ganz unproblematische Hilfe bei Berechnungen oder Vergleichen ist die Unix-Zeit (siehe http://de.wikipedia.org/wiki/Unixzeit), eine absolute Angabe der vergangenen Sekunden seit dem 1.1.1970.
Während Zeitangaben aus Datenbanken, Formularergebnissen oder XML-Dateien sich oft vom Aufbau und Syntax her unterscheiden, bietet das Unix-Zeitformat eine absolute Angabe, mit der leichter gerechnet, verglichen und sortiert werden kann. PHP bietet mehrere einfache Funktionen um mit der Unix-Zeit zu arbeiten.

echo time();
echo date('d.m.Y', 1269351978);
"time()" liefert die aktuelle Unix-Zeit im Moment des Aufrufs zurück.
Mit "date()" kann eine Unix-Zeit fast beliebig in eine normal formatierte Zeit umgewandelt werden.
"date" erhält als ersten Parameter einen String mit dem die Formatierung der Ausgabe festgelegt wird. Als optionalen, zweiten Parameter kann eine Unix-Zeitangabe in der Form "date('String', optionale Zeitangabe als Unix-Zeit);" übergeben werden. Auf der Basis dieser optionalen Angabe wird das formatierte Datum generiert. Ist der zweite Parameter nicht gesetzt wird die aktuelle Zeit verwendet.

Einige wichtige Parameter für eine formatierte Zeitangabe:

d, Tag mit führender Null, sonst j
m, Monat mit führender Null, sonst n
y, Jahr, zweistellig
Y, Jahr, vierstellig

H, Stunden mit führender Null, sonst G
i, Minuten mit führender Null
s, Sekunden mit führender Null

t, Anzahl der Tage des Monats
z, Tag des Jahres
w, Wochentag als 0-6 (Wochenbeginn ist mit 0 Sonntag)

W, die Kalenderwoche
U, Unix-Zeit

echo date('d.m.Y H:i:s').' entspricht '.date('U').' oder '.time();

date_default_timezone_set ('Europe/Berlin'); //oertliche Zeitzone
Es kann notwendig sein die gültige Zeitzone festzulegen, dies sollte vor dem ersten Aufruf von "date()" mit der wohlklingenden Funktion "date_default_timezone_set()" erfolgen.

Zeichen wie Punkt, Bindestrich, Leerzeichen können beliebig gesetzt werden. Um Wortzeichen als Literale zu verwenden, müssen diese in der bekannten Form escaped werden: "\H\a\l\l\o \W\e\l\t". Sinnvoll ist dies natürlich nur bei kurzen Texten.

echo date('\U\n\i\x-\Z\e\i\t: U');
Es gibt noch weitere Parameter für eine formatierte Ausgabe, die teilweise aber Ausgaben in englischer Sprache veranlassen. Wollen wir einen Wochentag oder einen Monat lesbar ausgeben, müssen wir dies bei der Verwendung von date() selbst berechnen.

$Wochentag = array('Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag');
echo $Wochentag[date('w')];

$Monat = array('0', 'Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember');
echo $Monat[date('n')];

Der große Bruder von date(): getdate()

Werden mehrere Aspekte einer Zeitangabe verwendet, kann es sinnvoll sein die Funktion "getdate()" zu verwenden, die ausgehend von einer Unix-Zeit eine Vielzahl von Zeitwerten als ein Array zurückgibt.

$Zeit = getdate();
print_r( $Zeit );

Array
(
    [seconds] => 45
    [minutes] => 45
    [hours] => 20
    [mday] => 19
    [wday] => 1
    [mon] => 4
    [year] => 2010
    [yday] => 108
    [weekday] => Monday
    [month] => April
    [0] => 1271702745
)
"getdate" kann optional eine Unix-Zeit übergeben werden auf deren Basis die Werte berechnet werden, ohne übergebene Unix-Zeit wird die aktuelle System-Zeit verwendet. Die berechneten Werte werden immer ohne führende Null dargestellt. Weitere Informationen zu getdate, welches an dieser Stelle der Einfachheit halber nicht verwendet wird, finden sie unter http://php.net/manual/de/function.getdate.php.

Die Parameter von getdate:

'seconds', Sekunden
'minutes', Minuten
'hours', Stunden
'mday', fortlaufender Tag des Monats
'wday', Tag der Woche beginnend mit 0 für Sonntag
'mon', Monat
'year', Jahr, vierstellig
'yday', fortlaufender Tag des Jahres
0 oder '0', die Unix-Zeit

$Zeit = getdate();
echo $Zeit['mday'].'.'.$Zeit['mon'].'.'.$Zeit['year'];

Der große Bruder von time(): mktime()

Bisher haben wir lediglich die aktuelle Unix-Zeit mit time() erhalten. Um aus einer beliebigen Zeitangabe eine Unix-Zeit zu erhalten, benötigen wir die Funktion "mktime()", der wir beliebige Aspekte einer Zeitangabe von Sekunden bis zum Jahr übergeben.

$Jahr = 2010; $Monat = 04; $Tag = 01; $Stunde = 12; $Minute = 0; $Sekunde = 0;
echo (  mktime($Stunde,$Minute,$Sekunde,$Monat,$Tag,$Jahr) );

Auf den Tag gebracht!

In vielen Fällen hat man eine vollständige Zeitangabe mit Datum, Stunden und Minuten, interessiert sich aber nur für Teile der Zeit, beispielsweise für den Tag an sich. In der Praxis möchte man oft wissen vor wie vielen Tagen eine Datei verändert oder erzeugt wurde.

Um die Minuten oder auch die Stunden verschwinden zu lassen benutzen wir unsere Funktion "mktime", der wir wir nur die Aspekte Monat, Tag und Jahr zur Berechnung der Unix-Zeit übergeben. Alle anderen Angaben können lassen wir außer acht, bzw. setzen sie auf Null. Mit dem Ergebnis in Form der Unix-Zeit können wir komfortabel weitere Berechnungen durchführen. Beispielsweise ziehen wir von dem aktuellen Tag ein anderes Datum ab. Das Ergebnis sind die vergangenen Tage in Sekunden, die wir in Stunden (3600 Sekunden) oder in Tage (3600*24 bzw. 86400 Sekunden) umrechnen. Die Funktion "strtotime" wird dabei am Ende dieser Ausführungen behandelt.

$Jahr = 2010; $Monat = 04; $Tag = 01;
echo ( strtotime('today') - mktime(0,0,0,$Monat,$Tag,$Jahr) )/ (3600*24) ;

Achtung!

Die Parameter der jeweiligen Zeiteinheiten müssen tatsächlich getrennt innerhalb von "mktime" angegeben werden. Eine Kurzform in der Art "mktime(0,0,0,date('m,d,Y'))" gibt keinen Fehler aus, liefert aber falsche und unerwartete Ergebnisse. "mktime" erkennt die Trennung der von date übergebenen Werte nicht, versucht aber die jetzt fehlenden oder falschen Angaben ohne weitere Meldung zu kompensieren.

Das Geheimnis der fehlenden Stunde

Wie vieles andere, so hat auch die einfache Berechnung der Tage mit der Unix-Zeit ein dunkles Geheimnis. An manchen Tagen fehlt uns eine ganze Stunde. So beträgt der Abstand vom 28. zum 29.03.2010 oder vom 29. zum 30.03.2009 nicht ein Tag, also 24 Stunden, sondern lediglich 23 Stunden.

echo (  mktime(0,0,0,03,29,2010) - mktime(0,0,0,03,28,2010) )/ 3600 ;
echo (  mktime( 0,0,0,03,30,2009) - mktime( 0,0,0,03,29,2009 ) )/ 3600 ;
Die grausame Realität, dass der Tag tatsächlich keine vollen 24 Stunden hat, holt uns hier vermutlich ein und verdirbt uns unsere Berechnungen. Man muss also berücksichtigen, dass das Ergebnis nicht immer einen Integer-Wert liefert. Sobald ich die tatsälichen Hintergründe kenne, werde ich dies hier genauer ausführen.
Bis dahin kann man das Ergebnis mit der Funktion "ceil()" etwas "schönen", indem das Ergebnis zu ganzen Tagen aufgerundet wird.

Vor wie vielen Tagen wurde eine Datei zuletzt bearbeitet?

Nehmen wir einen konkreten Fall bei dem wir wissen möchten, vor wie vielen Tagen eine Datei zuletzt bearbeitet wurde. Wir erfragen mit "filemtime()" die Unix-Zeit der letzten Änderung und schicken von diesem Ergebnis mit Hilfe von "date" den Tag, Monat und das Jahr in die Funktion "mktime". Wir fragen das aktuellen Datum als absolute Unix-Zeit ab, ziehen davon das Ergebnis der Datei ab und rechnen es in Tage um.

$t = filemtime('c:/ordner/datei.txt');
$datei = mktime(0,0,0,date('m',$t),date('d',$t),date('Y',$t));
$heute = strtotime('today');
echo ($heute - $datei) / 86400;

Das Alter aus Dateinamen bestimmen

Oft interessiert uns nicht der Zeitpunkt der letzten Bearbeitung, sondern eine Datumsangabe, die beispielsweite im Dateinamen vorliegt. Auch hier lesen wir die Datumsangaben aus, rechnen sie wie beschrieben um und vergleichen das Ergebnis mit dem aktuellen Datum.
Dazu empfiehlt es sich reguläre Ausdrücke zu verwenden. Natürlich kann man auch mit gängigen Befehlen wie "substr" versuchen die Datumsangaben auszulesen, schließlich kann man auch mit verbundenen Augen über eine Straße gehen oder eine Metall-Gabel in die Mikrowelle legen.
Da wir aber alle wissen, dass sich Dateinamen gerade von der Länge her nie wirklich dauerhaft an festgelegte Konventionen halten, verwenden wir reguläre Ausdrücke.

$Dateiname = 'dateiname24122009-xy-001.jpg';
preg_match('/(\d\d)(\d\d)(\d\d\d\d)/',$Dateiname,$Ergebnis);
$tag = $Ergebnis[1];
$monat = $Ergebnis[2];
$jahr = $Ergebnis[3];
$datei = mktime( 0,0,0,$monat,$tag,$jahr );
$heute = mktime(0,0,0,date('m'),date('d'),date('Y'));
echo ($heute - $datei) / 86400;

Natürlich kann man den regulären Ausdruck und auch den Programmcode kürzer gestalten, für die Lesbarkeit ist dies aber nicht hilfreich:

preg_match('/(\d\d)(\d\d)(\d{4})/','dateiname01042010-xy-001.jpg',$Erg);
echo (mktime(0,0,0,date('m'),date('d'),date('Y'))-mktime(0,0,0,$Erg[2],$Erg[1],$Erg[3]))/86400;

Gestern, heute, übermorgen oder "the icing on the cake"

Manchmal lohnt es sich doch bis zum Ende weiterzulesen, wir kommen jetzt zu einer der interessantesten Funktionen.

Wir erhalten mit Hilfe von "time()" das Jetzt, mit "date()" und "mktime()" definierte Zeitpunkte und können Zeitangaben fast beliebig formatieren und umwandeln.
Was uns noch fehlt ist ein definiertes Gestern, Heute, Morgen und ein Irgendwann. Natürlich können wir auf der Basis der bekannten Funktionen entsprechende Berechnungen durchführen, aber PHP macht es uns leichter mit der Funktion strtotime() (siehe http://php.net/manual/de/function.strtotime.php).

Eigentlich erzeugt strtotime die Unix-Zeit auf der Basis einer englischen Zeitangabe, also nicht viel neues. Wenig augenscheinlich ist aber die Möglichkeit mit strtotime aus einer Unix-Zeit relative Zeitangabe in der Form heute, morgen oder gestern ableiten zu lassen.

Als erstes sehen wir uns absolute Angaben an, die ausgehend vom Jetzt den jeweiligen Tag an sich liefern, also ohne anhängende Minuten oder Sekunden:

strtotime('today');
strtotime('tomorrow');
strtotime('yesterday');

strtotime("next Monday");
strtotime('Next Sunday');

echo date('d.m.y H.i.s',strtotime('tomorrow')),' ist Morgen ';
Noch interessanter sind die Angaben für "noon", die uns die Mittagszeit, 12 Uhr und "midnight", die uns den Tag um Mitternacht, also auch wieder ohne anhängende Minuten oder Sekunden liefern.
Diesen Angaben können wir auch eine beliebige Unix-Zeit als zweiten Parameter mitgeben. Von diesem Tag erhalten wir ohne Aufwand die für Datenbanken oftmals relevante Zeit 12:00:00 des Tages und bei "midnight" den Tag an sich.

strtotime('noon');
strtotime('midnight');
strtotime('noon',1270113217);
strtotime('midnight',1270113217);

echo date('d.m.y H.i.s',strtotime('noon',1270113217));
echo date('d.m.y H.i.s',strtotime('midnight',1270113217));

next, last, + und -

Aber das ist noch nicht alles. "strtotime" kennt als Paramter noch weitere Angaben: Sekunden bis zum Jahr können mit einer Angabe wie "next", "last" oder +/-1 bis +/-n verknüpft werden und liefern ausgehend vom Jetzt oder einer angegebenen Unix-Zeit berechnete Zeitangaben in der Vergangenheit oder der Zukunft. Bei der Berechnung von +/-1 bis +/-n können auch mehrere Zeitangaben kombiniert werden.

Spielen sie einfach ein wenig mit den hier angegebenen Beispielen:
echo date('d.m.y H.i.s',strtotime('next day'));
echo date('d.m.y H.i.s',strtotime('last year'));

echo date('d.m.y H.i.s',strtotime('next day', 1270116000));
echo date('d.m.y H.i.s',strtotime('last year', 1270116000));

echo date('d.m.y H.i.s',strtotime('+1 minute', 1270116000));
echo date('d.m.y H.i.s',strtotime('-2 hour', 1270116000));
echo date('d.m.y H.i.s',strtotime('-1 day'));
echo date('d.m.y H.i.s',strtotime('-2 days'));
echo date('d.m.y H.i.s',strtotime('+4 week'));
echo date('d.m.y H.i.s',strtotime('+4 weeks'));
echo date('d.m.y H.i.s',strtotime('+1 year'));
Und weiter?
Dies war nur eine kleine Einführung, die hoffentlich etwas hilfreich ist. Wer weiter gehen möchte findet unter http://php.net/manual/de/ mit "strftime" und der DateTime-Klasse weitere interessante Aspekte der Arbeit mit der Zeit unter PHP. Oder er fordert einfach einen zweiten Teil als Fortsetzung zu dieser Einführung.

Keine Kommentare:

Kommentar veröffentlichen