7

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?

4

Datum in PHP vergleichen – so geht’s

php_date_compareWie vergleicht man eigentlich 2 Daten (Daten = Plural von Datum) in PHP miteinander? Die Frage klingt erst mal einfacher als es ist.
Der erste Gedanke, der einem meistens kommt, ist folgender. Wir speichern 2 Daten als String und vergleichen diese.

 

 

Vorüberlegung

<?php
$date1 = "2012-1-12";
$date2 = "2011-10-12";

if ($date1 > $date2)
 echo "$date1 ist neuer als $date2";
else
 echo "$date1 ist &auml;lter als $date2";
?>

Ausgabe:

2012-1-12 ist neuer als 2011-10-12

Auf den ersten Blick scheint das auch eine funktionierende Lösung zu sein. Was jedoch, wenn die beiden Daten in einem unterschiedlichen Format vorliegen?

<?php
$date1 = "12-1-12";
$date2 = "2011-10-12";

if ($date1 > $date2)
 echo "$date1 ist neuer als $date2";
else
 echo "$date1 ist &auml;lter als $date2";
?>

Ausgabe:

12-1-12 ist älter als 2011-10-12

Nun wird das Datum in 2012 auf einmal als kleiner als das Datum in 2011 deklariert, was natürlich falsch ist. Doch aus PHP-Sicht ist dieses Verhalten korrekt, schließlich wurden ja zwei Strings verglichen und das 2012er Datum hatte definitiv den kleineren/kürzeren String.

Datum korrekt vergleichen

Eine funktionierende Lösung wäre es, die Daten zuerst in Timestamps (dt.: Zeitstempel) umzuwandeln und diese numerischen Timestamps dann miteinander zu vergleichen. Zur Umwandlung eines Datum-Strings in einen Timestamp bietet PHP die Funktion strtotime($zeitString), die ein Datum oder eine Zeitangabe annimmt und den entsprechenden Timestamp zurückliefert.

<?php
$date1 = "12-1-12";
$date2 = "2011-10-12";
$dateTimestamp1 = strtotime($date1);
$dataTimestamp2 = strtotime($data2);

if ($dateTimestamp1 > $dataTimestamp2)
 echo "$date1 ist neuer als $date2";
else
 echo "$date1 ist &auml;lter als $date2";
?>

Ausgabe:

12-1-12 ist neuer als 2011-10-12

Jedoch führen bekanntlich viele Wege nach Rom und so gibt es auch hier noch weitere Lösungen für das Ausgangsproblem. So wäre eine weitere Lösung die Verwendung der DateTime-Klasse, die PHP ab der Version 5.2.0 zur Verfügung stellt.

<?php
$date1 = new DateTime("12-1-12");
$date2 = new DateTime("2011-10-12");

if ($date1 > $data2)
 echo $date1->format("Y-m-d")." ist neuer als ".$date2->format("Y-m-d");
else
 echo $date1->format("Y-m-d")." ist &auml;lter als ".$date2->format("Y-m-d");
?>

Ausgabe:

2012-01-12 ist neuer als 2011-10-12

Verwendet man die auf der DateTime-Klasse basierende Lösung, hat man auch gleich noch beide Daten einheitlich formatiert. Gut, man könnte in der ersten Lösung auch die date()-Funktion benutzen, um die Daten zu formatieren, aber das würde den ersten Lösungsweg noch länger machen. Am Ende bleibt es Geschmackssache, welchen Weg man für den Vergleich zweier Daten geht.

Wie vergleicht ihr zwei Daten in PHP? Nutzt ihr einen der beiden aufgezeigten Wege oder geht ihr einen anderen? Mich würde interessieren, wie ihr euch dem Problem annehmt.

3

PHP-Fehlermeldungen unter Webmatrix 2 anzeigen

WebMatrix2_logoSeit einigen Wochen steht Microsofts Entwicklungsumgebung Webmatrix nun in der Version 2 zur Verfügung. Wie auch schon in der ersten Version, ist Webmatrix 2 eine kostenlose Entwicklungsumgebung für Webseiten und Webanwendungen verschiedenster Plattformen wie z.B. ASP.NET, PHP und HTML5.

Ich nutze Webmatrix unter anderem, um mal eben das ein oder andere PHP-Script zu testen. Leider gibt der mitgelieferte/integrierte Webserver in den Standardeinstellungen, also so, wie er ausgeliefert wird, keine Fehlermeldungen aus. Schleicht sich also der Fehlerteufel im PHP-Script ein und man hat nicht von vornherein alles mittels try-catch-Block abgefangen, so sieht man eine weiße Seite.

Um die, wie ich finde, zur Entwicklung sehr hilfreichen Fehlermeldungen wieder auszugeben, muss die php.ini des Webservers angepasst werden. Sofern ihr während des Setups von Webmatrix nichts verstellt habt, läuft ein IIS Express unter der Haube von Webmatrix. Die php.ini-Datei findet ihr unter Windows 7/Windows 8 standardmäßig unter folgendem Pfad:

C:\Program Files (x86)\IIS Express\PHP\v5.3

(Eventuell kann die PHP-Version, hier 5.3, abweichen.)

Öffnet nun die php.ini und sucht die folgende Zeile:

display_errors = Off

Wenn ihr die Fehlermeldungen angezeigt bekommen wollt, dann müsst ihr nur das “Off” durch ein “On” ersetzen.

display_errors = On

Danach einmal abspeichern und von nun an sollten wieder alle PHP-Fehlermeldungen ausgegeben werden.

Sollte es Probleme beim Speichern geben, dann liegt das in den meisten Fällen an den Berechtigungen. Abhilfe schaffen könnt ihr euch zum Beispiel, in dem ihr Notepad mit einem Rechtsklick als Administrator startet und dann über die “Datei->Öffnen”-Funktion von Notepad die php.ini öffnet. So sollte es auf jeden Fall mit dem Speichern klappen.

Ich wünsche viel Spaß beim entwickeln mit PHP!

2

TagKeywordFinder – ein kostenloses WordPress SEO Plugin

Letztens beim stöbern stieß ich auf ein WordPress Plugin namens wpSuggest. Dieses verknüpft Googles Autocomplete-Funktion (das Teil nennt sich Google Suggest und sorgt dafür, dass ihr schon beim tippen der Suchanfrage Vorschläge bekommt) mit dem Titelfeld im WordPress Editor. Wenn ihr also einen Artikel im WordPress Backend verfasst und den Titel eintippt, macht euch wpSuggest auomatisch Vorschläge.

Die Art und Weise wie Google Suggest hier verwendet wurde gefiel mir wirklich gut. Jedoch fand ich, dass die Kombination aus Google Suggest und dem Eingabefeld für die Schlagwörter/Tags viel mehr Sinn machen würde.
So habe ich kurzerhand auch ein kleines WordPress Plugin entwickelt, den TagKeywordFinder. Dieses Plugin arbeitet ähnlich wie das oben genannte wpSuggest, mit dem kleinen Unterschied, dass es beim Taggen der Artikel zum Einsatz kommt. Und so sieht’s aus:


(Wem die Installation egal ist, der sollte zu 1:00 skippen.)
Wenn ihr anfangt einen Tag für euren Artikel zu vergeben, schlägt mein Plugin mögliche Tags vor, die ihr mit einem Klick übernehmen könnt. Da dies mein erstes WordPress Plugin ist, bitte ich bei eventuelle Bugs zu entschuldigen. (Solltet ihr welche finden, schreibt mir einfach einen Kommentar.)

tagkeywordfinder_aktion_1 tagkeywordfinder_aktion_2 tagkeywordfinder_aktion_4
Den Download gibt es diesmal in zwei Varianten. Einmal als Direktlink – sprich anklicken und los geht’s. Und einmal als Link mit Werbung. (Es wird euch 5 Sekunden lang eine Werbung angezeigt, bevor der Download startet.)
Da ich niemanden zwingen möchte sich Werbung anzugucken, gibt es den Direktdownload. Solltet ihr mir aber einen Gefallen tun wollen, so solltet ihr euch für den werbebasierten Link entscheiden. Es liegt in eurer Hand. ;)

TagKeywordFinder – Direktdownload

TagKeywordFinder – Sag-dem-Entwickler-Danke-Download (mit Werbeeinblendung)

 
So, und da ich letztens schon ein, zwei mal gefragt wurde, gibt’s hier für die ganz großen Samariter noch den Paypal Spenden Button.





 
Das war’s dann auch erst mal wieder. Ich hoffe euch gefällt das Plugin. Wenn ihr noch Fehler findet, oder Verbesserungsvorschläge habt, dann schreibt mir doch einfach einen Kommentar.

Viele Grüße,
Raffi 

2

PHP Workaround – file_get_contents() ohne allow_url_fopen nutzen

php_artikel_logoIch gebe zu, der Titel ist etwas irreführend. Die PHP-Funktion file_get_contents(), die dazu genutzt werden kann Dateien aus dem Internet in einen String zu lesen, um sie weiterzuverarbeiten, funktioniert bei deaktiviertem allow_url_fopen einfach nicht. Daran wird auch dieser Artikel nichts ändern.

Entwickelt man jedoch eine Anwendung/ein Script, das auf möglichst vielen Serverumgebungen funktionieren soll, so wie zum Beispiel ein WordPress-Plugin, so gibt es einen guten Workaround, um Nutzer, auf dessen Webservern allow_url_fopen deaktiviert ist, nicht auszuschließen.
Und genau dieses kleine Snippet möchte ich euch heute zeigen und erklären.

$datei = "http://www.beispiel.de/meine_seite.php";
if (function_exists('curl_version'))
{
	$curl = curl_init();
	curl_setopt($curl, CURLOPT_URL, $datei);
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
	$content = curl_exec($curl);
	curl_close($curl);
}
else if (file_get_contents(__FILE__) && ini_get('allow_url_fopen'))
{
	$content = file_get_contents($datei);
}
else
{
	echo 'Sie haben weder cURL installiert, noch allow_url_fopen aktiviert. Bitte aktivieren/installieren allow_url_fopen oder Curl!';
}

Zuerst wird überprüft, ob auf dem Server die cURL Erweiterung zur Verfügung steht. Wenn dies der Fall ist, wird die Datei per cURL geladen.
Steht cURL nicht zur Verfügung, so wird überprüft, ob allow_url_fopen aktiviert ist. Ist dies der Fall, wird die Datei per file_get_contents() geladen.
Stehen beide Möglichkeiten nicht zur Verfügung, wird eine Fehlermeldung ausgegeben, was den Endanwender vor etwaigen, für ihn “kryptischen”, Fehlermeldungen bewahren soll.

Falls ihr euch fragt, warum ich cURL den Vorzug gegenüber file_get_contents() gebe, so liegt das daran, dass ich die Erfahrung gemacht habe, dass cURL, bezogen auf diesen Anwendungsfall, etwas performanter ist.

Abschließend lässt sich sagen, dass ihr bei Verwendung der hier aufgezeigten Methode, die Chance darauf, dass euer Script bei möglichst vielen Usern läuft, verdoppeln könnt. Das ist doch schon mal was, oder?

Was haltet ihr von meinem Snippet? Würdet ihr es genauso umsetzen oder haltet ihr davon eher nichts? Wie geht ihr mit der “Problematik” allow_url_fopen um?

Viele Grüße,
Raffi