Benchmark: strtotime() vs DateTime vs getTimestamp in PHP

Zuletzt schrieb ich ja darüber, wie man Datumsvergleiche in PHP realisieren kann. In dem Artikel stellte ich zwei Lösungsansätze vor. Zum einen mit Hilfe der strtotime()-Methode und zum anderen mit der DateTime-Klasse.

In den Kommentaren wurde ich dann von Christoph darauf hingewiesen, dass die strtotime()-Variante womöglich schneller sei. So hundertprozentig sicher war er sich jedoch auch nicht, also habe ich beschlossen mal einen kleinen Performance-Test durchzuführen, dessen Ergebnisse ich euch hier kurz präsentieren möchte.

Was habe ich gestestet?

Da die Frage auf strtotime() vs. DateTime abzielt, die DateTime-Klasse im Gegenteil zur strtotime()-Funktion jedoch mehr Funktionalität bietet, habe ich bei der DateTime-Klasse die Erstellung des DateTime-Objekts + die Ausgabe des Timestamps gemessen. Es wird also die Zeit für die identische Ausgabe gemessen.
Als Eingabe habe ich ein Datum ohne Zeit genommen – sprich eine Jahres-, Monats- und Tag-Angabe. Zudem habe ich noch eine dritte Funktion mit ins Rennen geschickt. Was genau es mit der getTimestamp()-Funktion auf sich hat, erläutere ich weiter unten im Artikel. Die drei zu vergleichenden Methodenaufrufe für meinen Test sehen wie folgt aus:

//strtotime()
$a=strtotime("2012-05-28");

//DateTime
$date=new DateTime("2012-05-28");
$a = $date->getTimestamp();

//getTimestamp()
$a = getTimestamp("2012-05-28");

Wie habe ich getestet?

Als Referenzwert wollte ich eine Zeitspanne in Sekunden angeben. Zur Zeitmessung habe ich folgende Funktionen genutzt:

//Zeitpunkt vor der Ausführung einer Variante
$startTime = microtime(true);

/***************************
  Variante/Tests ausführen
****************************/

//Zeitpunkt nach Ende des Tests
$endTime = microtime(true);
//Zeitspanne des Tests
$elapsedTime = abs($startTime - $endTime);

Jede der Funktionen wurde in einer Schleife 100.000-mal aufgerufen und die Zeit für die 100.000 Aufrufe gemessen. Um Schwankungen auszugleichen und Messungenauigkeiten aus dem Weg zu gehen, wurde die Messung für jede Variante 20-mal durchgeführt und der Mittelwert der 20 Messungen gebildet. Letztendlich sah der Code zu Messung einer Variante wie folgt aus:

$total = 0;
for ($runs = 0; $runs < 20; $runs++)
{
    $startTime = microtime(true);
    for ($i = 0; $i < 100000; $i++)
    {
        //Funktion/Variante ausführen
    }
    $endTime = microtime(true);
    $total += abs($startTime - $endTime);
}
$elapsedTime = sprintf("%.4f sec.", ($total/20));

Zudem habe ich am Anfang des Benchmark-Scripts noch folgende Zeile Code hinzugefügt um die Zeitzone für die strtotime()- sowie die DateTime-Variante zu setzen.

date_default_timezone_set("Europe/Berlin");

Geschrieben habe ich den Testcode in Webmatrix 2 und auch über dessen mitgelieferten IIS Express Webserver ausgeführt. Die Hardware der Testumgebung sah wie folgt aus:

  • Acer Aspire 5740G (Notebook)
  • Intel Core i3 M 330 @ 2.13Ghz
  • 8GB Ram
  • Windows 8 Pro (64-Bit)

Wie ist das Ergebnis ausgefallen?

Das Ergebnis fiel so aus, wie es Christoph bereits vermutet hatte. Die DateTime-Variante lag zeitlich (wenn auch recht knapp) hinter der strtotime()-Funktion. Den zweiten Platz belegte die getTimestamp()-Funktion. In ersten Tests schnitt diese sogar so gut ab, dass die es auf den ersten Platz schaffte. Jedoch fehlten hier noch einige Berechnungen, die sie schlussendlich auf den zweiten Platz katapultiert haben.

Ich habe das Ganze mal in zwei kleinen Grafiken zusammengefasst.

php_date_to_timestamp_benchmark1

php_date_to_timestamp_benchmark_percent

Was hat es mit der getTimestamp()-Funktion auf sich?

Die getTimestamp()-Funktion ist keine offizielle PHP-Funktion, sondern eine kleine Eigenentwicklung von mir. Nachdem ich ein paar Tests nur mit der strtotime()- und der DateTime-Variante durchgeführt hatte, wollte ich sehen, ob ich selber nicht noch eine schnellere Variante schaffen kann. Wie den obigen Grafiken zu entnehmen kann, ist mir das leider nicht ganz gelungen. In ersten Tests hatte dies zwar geklappt, jedoch wurde ich in den Kommentaren darauf aufmerksam gemacht, dass die getTimestamp()-Funktion nicht immer korrekte Werte zurückliefern würde.
Nachdem ich noch die Umschaltung der Sommer- sowie Winterzeit hinzugefügt habe (für unseren Raum – in Amerika wird an anderen Daten umgestellt!), ist meine Funktion nun doch langsamer als die strtotime()-Funktion.

Meine getTimestamp-Funktion sieht wie folgt aus:

function getTimestamp($timeStr)
{
    $daysInMonth = array(0,31,59,90,120,151,181,212,243,273,304,334);
    $dateArr = explode("-", $timeStr);

    //Tag der Jahre bis eingegeben Jahres hinzufügen
    $days = ($dateArr[0]-1970) * 365;

    //Schaltjahrestage hinzufügen
    for ($i = 1972; $i <= $dateArr[0]; $i+=4)
    {
        if (($i%4==0 && $i%100 != 0) || $i%400==0)
            $days++;
    }

    //Tage bis zum 31.03. des Jahres
    $daysTimeswitch = $days + 90;
    //Per MOD + 3 Wochentag bestimmen. (+3 da 01.01.1970 = Donnerstag)
    //Wochentag vom 31. abziehen = letzter Sonntag im Monat => Sommerzeit
    $daySummertime = 31 - ((($daysTimeswitch%7) + 3)%7);

    //Tage bis zum 31.03. des Jahres
    $daysTimeswitch = $days + 304;
    //Per MOD + 3 Wochentag bestimmen. (+3 da 01.01.1970 = Donnerstag)
    //Wochentag vom 31. abziehen = letzter Sonntag im Monat => Sommerzeit
    $dayWintertime = 31 - ((($daysTimeswitch%7) + 3)%7);

    if (($dateArr[1] > 3 && $dateArr[1] < 10)
        || ($dateArr[1] == 3 && $dateArr[2] > $daySummertime)
        || ($dateArr[1] == 10 && $dateArr[2] <= $dayWintertime))
    {
         $summerWinterTime = 3600;
    }
    else{
         $summerWinterTime = 0;
    }

    //Schaltjahr-Tag abziehen, wenn Monat Februar des laufenden Jahr nicht inkludiert
    $days -= (($dateArr[0]%4==0 &&  $dateArr[0]%100 != 0) ||  $dateArr[0]%400==0) ? ($dateArr[1] > 2) ? 0 : 1 : 0;

    //Tage für Monate des laufenden Jahrs hinzufügen
    $days += $daysInMonth[$dateArr[1]-1];

    //Tage des laufenden Jahrs hinzufügen
    $days += $dateArr[2] - 1;

    //3600 Sekunden für Zeitverschiebung abziehen. (Unix-Timestamp / UTC)
    return $days * 86400 - 3600 - $summerWinterTime;
}

Wer den Quelltext der Funktion aufmerksam gelesen hat, wird jetzt jedoch vielleicht “Halt mal…” schreien wollen. Ja, korrekterweise muss ich eingestehen, dass meine Funktion nur unter gewissen Umständen korrekte Werte liefert. Folge Voraussetzungen müssen erfüllt sein:

  • Das Datum muss in der Form YYYY-MM-DD übergeben werden
  • Das Datum darf nicht älter als der 01.01.1970 sein

Die strtotime()-Funktion nimmt zum Beispiel auch Daten in anderen Formaten an und kann in einem gewissen Rahmen auch Timestamps für Daten vor dem 01.01.1970 berechnen. Hier liegt meine Funktion klar im Nachteil und man könnte sich Fragen, ob der Vergleich dann nicht ungerechtfertigt ist. Jedoch muss man auch anmerken, dass die DateTime-Klasse wiederum eine größere Funktionalität als die strtotime()-Funktion bietet.
Und da es in dem Benchmark lediglich um die Generierung eines Timestamps aus einem gegebenen Datum ging, denke ich, dass meine Funktion ihre Daseinsberechtigung hat. Denn korrekterweise beginnt die Unix-Timestamp-Rechnung am 01.01.1970 und somit erfüllt meine Funktion die Grundvoraussetzungen zur Berechnung eine Unix-Timestamps.

Fazit

Die Behauptung, strtotime() sei schneller als DateTime, konnte mit dem Test belegt werden. Zudem konnte ich feststellen, dass es, zumindest mir, nicht möglich ist, eine schnellere Funktion als die strtotime() zu schreiben. Zu Beginn der Tests, dachte ich, dass wenn ich mich auf das Minimum beschränken würde, ich eine schnellere Funktion schreiben könnte. Schließlich kann die strtotime()-Funktion noch ein paar Sachen mehr als meine getTimestamp(). Dennoch ist es mir nicht gelungen.

Letztendlich sollte die Wahl der Methode auch immer im Kontext des Projekts getroffen werden. In welchen Format(en) liegen die Daten vor? Möchte ich vielleicht noch mehr als nur den Timestamp berechnen?

Zum Schluss noch eine Frage in die Runde: Hat jemand von euch es schon mal probiert eine schnellere Funktion als die strtotime()-Funktion zu schreiben? Und wenn ja, welchen Weg seid ihr gegangen und auf welche Probleme seid ihr gestoßen?

7 Kommentare

  1. P.S: Deine Vorgaben wurden natürlich eingehalten:

    * Datum größer 01. Januar 1970
    * Im Format YYYY-MM-DD

    Bin mal auf die Auflösung gespannt.

    Gruß

    Christoph

  2. Hmm…. bist du sicher, dass deine selbst geschriebene Funktion stimmt?

    In meinem kleinen Testskript, kommt deine Funktion nicht so gut weg :)


    $aDates = array("1970-01-01", "1971-01-01", "2012-10-22");
    foreach($aDates as $date)
    {
    echo $date."";
    echo "getTimestamp: ".getTimestamp($date)."";
    $a = new DateTime($date);
    echo "DateTime: ".$a->getTimestamp()."";
    echo "strtotime: ".strtotime($date)."";
    echo "";
    }

    Ich rede jetzt nicht von der Performance, sondern davon, ob die Funktion überhaupt das korrekte Ergebnis liefert. Ich bekomme die folgende Ausgabe:

    1970-01-01
    getTimestamp: -97200
    DateTime: -3600
    strtotime: -3600

    1971-01-01
    getTimestamp: 31438800
    DateTime: 31532400
    strtotime: 31532400

    2012-10-22
    getTimestamp: 1350853200
    DateTime: 1350856800
    strtotime: 1350856800

    Das bedeutet strtotime und DateTime liefern denselben Timestamp (vermutlich korrekt), aber die getTimestamp liefert einen abweichenden Wert. Dann ist es ja kein Wunder, dass es schneller ist *g*

    • Raffisays:

      Hallo Christoph,

      da hatte sich wirklich ein klitze kleiner Fehler eingeschlichen. Ich habe die Zeile oben im Post abgeändert und den Test nochmal laufen lassen. Das Ergebnis ist das selbe. ;)

      Ersetze also bitte mal die Zeile:
      $days -= ((($dateArr[0]%4==0 && $dateArr[0]%100 != 0) || $dateArr[0]%400==0) && $dateArr[1] > 2) ? 0 : 1;

      Durch folgende Zeile:
      $days -= (($dateArr[0]%4==0 && $dateArr[0]%100 != 0) || $dateArr[0]%400==0) ? ($dateArr[1] > 2) ? 0 : 1 : 0;

      Nun solltest du mit deinem Test korrekte Ergebnisse erhalten. Sollten die Ergebnisse noch abweichen, so liegt das an der gesetzten Zeitzone deines Server. Zur Anpassung an die Zeitzone ziehe ich in der letzten Zeile der getTimestamp()-Funktion 10800 Sekunden ab. Entweder du passt nun noch diese 10800 Sekunden an deine Zeitzone an. Oder du setzt die Zeitzone deines Scripts auf die selbe, die ich genutzt habe.
      Dies kannst du wie folgt machen:
      date_default_timezone_set(“Europe/Moscow”);
      Das entspricht GMT+3 bzw. den 10800 Sekunden Offset in meiner getTimestamp()-Methode. Warum mein IIS Express und dessen PHP-Erweiterung jedoch denken, dass ich ein paar Zeitzonen weiter sitze kann ich dir auch nicht sagen…

      Ich würde mich freuen, wenn du die 2 Kleinigkeiten nochmal änderst und mir berichtest, wie es gelaufen ist.

      Viele Grüße,
      Raffi

      • So ganz scheint es immer noch nicht zu passen: Das modifizierte Skript:


        getTimestamp()."\n";
        echo "strtotime: ".strtotime($date)."\n";
        echo "\n";
        }

        liefert nun meist dasselbe Ergebnis, aber nicht immer, beispielsweise am “2000-05-04” oder auch am “2012-10-22” :)

        1970-01-01
        getTimestamp: -10800
        DateTime: -10800
        strtotime: -10800

        1982-01-09
        getTimestamp: 379371600
        DateTime: 379371600
        strtotime: 379371600

        1971-01-01
        getTimestamp: 31525200
        DateTime: 31525200
        strtotime: 31525200

        1999-12-31
        getTimestamp: 946587600
        DateTime: 946587600
        strtotime: 946587600

        2000-05-04
        getTimestamp: 957387600
        DateTime: 957384000
        strtotime: 957384000

        2012-10-22
        getTimestamp: 1350853200
        DateTime: 1350849600
        strtotime: 1350849600

        2022-10-22
        getTimestamp: 1666386000
        DateTime: 1666382400
        strtotime: 1666382400

        Hätte ich Zeit, dann würde ich mal einen Unittest schreiben. Irgendwo muss noch ein kleines Problemchen stecken.

        Gruß

        Christoph

        • Raffisays:

          Die 3600 Sekunden dürften aus der Sommer- bzw. Winterzeitumstellung herrühren. Das werde ich wohl noch implementieren und den Test dann noch mal laufen lassen und die Grafiken im Text dann ggf. aktualisieren.

        • Raffisays:

          Ok, es war die Sommer- und Winterzeit. Da denkt man auch echt immer nur dran, wenn man gerade mal eine Stunde “geschenkt” bzw. “geklaut” bekommt. Ist jetzt auch noch implementiert.
          Somit sollte meine Funktion nun endgültig korrekte Werte liefern. Leider hat die Implementierung der Zeitumstellung auch einiges an Performance gekostet, sodass ich nebst der Grafiken auch gleich den Artikel noch ein wenig umschreiben musste. Denn nun ist strtotime() definitiv die schnellere Funktion.
          Schade, aber ist ja schon richtig und wichtig, dass die Funktion auch den korrekten Wert liefert. Trotzdem hatte ich mich schon ein wenig gefreut, eine schnellere Lösung kreirt zu haben. Vielleicht klappts ja beim nächsten Mal.

          Viele Grüße,
          Raffi

          • Hab ich doch gleich gesagt, dass “strtotime” am schnellsten ist :)

            Finde aber gut, dass du mal diesen Test gemacht hast und es somit auch bewiesen hast.

            Das wichtigste ist definitiv, das sich die Ersatzfunktion exakt wie die Originalfunktion verhält. Bringt ja nichts, wenn es schneller ist, aber dafür andere Ergebnisse liefert.

            Drück dir die Daumen für das nächste Mal. Hätte ich etwas mehr Zeit momentan, dann würde ich es auch mal probieren. Hätte nämlich noch eine Idee, wie es schneller gehen könnte unter Verwendung einer Hashtabelle.

            Gruß

            Christoph

Schreibe einen Kommentar zu Raffi Antworten abbrechen

Deine E-Mail-Adresse wird nicht veröffentlicht. Sie dient nur dem Spamschutz.