Datum kürzen
Bernd
- php
Moin,
ist es so der richtige Weg ein Datum von 2019 auf 19 zu kürzen?
function date2german($date) {
$d = explode("-", $date);
return sprintf("%02d.%02d.%02d", $d[2], $d[1], substr($d[0], 2, 4));
}
Tach!
ist es so der richtige Weg ein Datum von 2019 auf 19 zu kürzen?
Ein Weg ist richtig, wenn er in allen Fällen zur gewünschten Lösung führt.
function date2german($date) { $d = explode("-", $date); return sprintf("%02d.%02d.%02d", $d[2], $d[1], substr($d[0], 2, 4)); }
Ob das der richtige Weg für deinen Anwendungsfall ist, kann ich dir nicht mit Gewissheit sagen. Dazu weiß ich zu wenig über das Datenformat. Anhand des Codes könnte man annehmen, dass es ein bereits formatierter String ist. In dem Fall sieht es so aus, dass es bis auf die 4 eine passende Lösung sein könnte. substr() möchte eine Länge als dritten Parameter, keine Positionsangabe. Andererseits stört es auch nicht, wenn von zwei verbleibenden Zeichen vier oder noch mehr gewählt werden, es werden in jedem Fall nur zwei im Ergebnis stehen.
Ich würde das nicht auf diese Weise zu lösen versuchen, wenn ich stattdessen mit Zeitangaben im Rohformat arbeiten kann. Dann würde ich das Datum gleich wie gewünscht formatieren und nicht hinterher per Stringfunktionen Korrekturen nachreichen.
dedlfix.
Hallo,
ich habe immer ein Datum in diesem Format: 2019-05-07. Mit dieser Funktion möchte ich das Datum in der Ausgabe gerne auf Deutsch haben. Zusätzlich benötige ich an manchen Stellen das 20 nicht sonder nur 18 oder 19.
Tach!
ich habe immer ein Datum in diesem Format: 2019-05-07.
Warum hast du es als String vorliegen und nicht als DateTime-Objekt, mit dem man noch eine Menge mehr machen könnte als Stringfunktionen darauf anzuwenden?
Der eigentliche Zweck wird doch wohl nicht sein, ein String vorliegen zu haben, an dem man Bearbeitungen vornehmen kann, sondern ein Datum, mit dem man irgendwas tun möchte, beispielsweise es für die Ausgabe in ein bestimmtes Format zu bringen.
Wenn es so aus dem DBMS geliefert wird, dann würde ich gleich beim Übernehmen ein DateTime-Objekt draus machen, wozu man nur diesen String dem Constructor von DateTime übergeben muss.
dedlfix.
MySQL jedenfalls interpretiert 19---5-6
einwandfrei als das heutige Datum.
Naja. Du machst zu "viel" - jedenfalls wenn Du ein ISO-Datum erwartest, da haben Monate und Tage führende Nullen. Eigentlich kannst Du das Datum gleich als String hernehmen und mit substr (nötiger Link zum Handbuch) die Items rauspolken. Das ist nämlich "irre schnell". Aber weil der Name, naja, irritierend ist, könnte man auch gleich einen Zeitstempel übergeben. Oder nichts und dann das Standardverhalten (Hergeben des aktuellen Datums) erwarten:
<?php
function date2german( $date = false ) {
if ( false === $date ) {
$date = time();
}
if ( 'string' == gettype( $date ) ) {
return (
substr( $date, 8, 2) . '.'
. substr( $date, 5, 2 ) . '.'
. substr( $date, 2, 2 )
);
} elseif ( 'integer' == gettype( $date ) ) {
return date( 'd.m.y', $date );
} else {
trigger_error(
"function date2german: Typ für '$date' nicht implementiert.",
E_USER_NOTICE
);
return false;
}
}
echo date2german( '2019-12-31' ) . "\n";
echo date2german() . "\n";
php test.php
31.12.19
06.05.19
Hallo ursus,
nicht mein Minus, aber ich denke, im Falle des string-Zweigs bist Du tatsächlich zu leichtsinnig. Man sollte ein paar Datumsformate verstehen, oder zumindest sicher sein, dass der String ein ISO-Datum ist bevor man ihn als solches interpretiert.
D.h. man sollte die Formate "yyyy-mm-dd", "yy-mm-dd", "dd.mm.yy und "dd.mm.yyyy" prüfen, und vielleicht noch "mm/dd/yyyy" und "mm/dd/yy". Ich weiß, dass das nicht sehr zuverlässig ist, aber besser als blindlings GIGO anzuwenden und yyyy-mm-dd zu unterstellen.
D.h.
function date2german($date = false)
{
switch (gettype($date))
{
case 'boolean':
return date('d.m.y');
case 'integer':
return date('d.m.y', $date);
case 'string':
$yearOffs = strlen($date) == 8 ? 0 : 2;
$fmtDate = function($s, $d, $m, $y) {
return substr($s, $d, 2).".".substr($s, $m, 2).".".substr($s, $y, 2);
};
// yy-mm-dd oder yyyy-mm-dd
if ($date[$yearOffs+2] == "-" && $date[$yearOffs+5] == "-")
return $fmtDate($date, $yearOffs+6, $yearOffs+3, $yearOffs);
// dd.mm.yy oder dd.mm.yyyy
if ($date[2] == "." && $date[5] == ".")
return $fmtDate($date, 0, 3, $yearOffs+6);
// mm/dd/yy oder mm/dd/yyyy
if ($date[2] == "/" && $date[5] == "/")
return $fmtDate($date, 3, 0, $yearOffs+6);
}
trigger_error("function date2german: Typ für '$date' nicht implementiert.",
E_USER_NOTICE);
return false;
}
function test($input)
{
echo "$input -> <" . date2german($input) . ">\n";
}
test(false);
test(time());
test("1.4.17");
test("01.04.17");
test("01.04.2017");
test("01/04/17");
test("01/04/2017");
test("17-04-01");
test("2017-04-01");
Wobei man das noch mit Regexen ausfeilen kann…
function date2german($date = false)
{
switch (gettype($date))
{
case 'boolean':
return date('d.m.y');
case 'integer':
return date('d.m.y', $date);
case 'string':
// yy-mm-dd oder yyyy-mm-dd
// dd.mm.yy oder dd.mm.yyyy
// mm/dd/yy oder mm/dd/yyyy
if (preg_match("/^(?<year>\d\d?|\d{4})-(?<month>\d\d?)-(?<day>\d\d?)$/", $date, $matches) ||
preg_match("/^(?<day>\d\d?)\.(?<month>\d\d?)\.(?<year>\d\d?|\d{4})$/", $date, $matches) ||
preg_match("/^(?<month>\d\d?)\/(?<day>\d\d?)\/(?<year>\d\d?|\d{4})$/", $date, $matches)
)
return str_pad($matches['day'], 2, STR_PAD_LEFT)
. "." . str_pad($matches['month'],2,STR_PAD_LEFT)
. "." . str_pad($matches['year'],2,STR_PAD_LEFT);
trigger_error("function date2german: '$date' hat unbekanntes Format.",
E_USER_NOTICE);
return false;
}
trigger_error("function date2german: Typ für '$date' nicht implementiert.",
E_USER_NOTICE);
return false;
}
function test($input)
{
echo "$input -> <" . date2german($input) . ">\n";
}
test(false);
test(time());
test("1.4.7");
test("1.4.17");
test("01.04.17");
test("01.04.217");
test("01.04.2017");
test("1/4/7");
test("01/04/17");
test("01/04/217");
test("01/04/2017");
test("7-04-1");
test("17-4-1");
test("2017-4-01");
test("217-04-01");
test("2017-04-01");
Rolf
Tach!
Man sollte ein paar Datumsformate verstehen, oder zumindest sicher sein, dass der String ein ISO-Datum ist bevor man ihn als solches interpretiert.
Bist du sicher, dass es hier das bessere Vorgehen ist, einen Datumsparser selbst zu schreiben, wenn ein solcher bereits im Constructor von DateTime beziehungsweise strtotime() enthalten ist? Da wir außerdem den eigentlichen Anwendungsfall nicht kennen, könnne wir auch nicht einschätzen, was konkret gebraucht wird. Anscheinend ist ja nur der Fall "yyyy-mm-dd" von Interesse. Aber es könnte ja auch ein AB-Problem sein, bei dem eigentlich keine Lösung für B gesucht werden müsste, sondern eine bessere Vorgehensweise für A, damit B gar nicht erst auftritt.
dedlfix.
Gegenvorschlag:
<?php
function gerShortDate( $s = false ) {
if ( false === $s ) {
return date( 'd.m.y', time() );
}
if ( 'integer' == gettype( $s ) ) {
return date( 'd.m.y', $s );
} else {
if ( strpos( $s, '.' ) ) {
$a = explode ('.', $s );
$a[2] = $a[2] % 100;
$s = implode('/', [$a[1], $a[0], $a[2] ] );
}
return date( 'd.m.y', strtotime( $s ) );
}
}
function test($input=false)
{
echo sprintf("% 12s", $input),"\t", gerShortDate($input) , "\n";
}
test();
test(time());
# Deutsch
test("7.5.9");
test("7.5.19");
test("07.05.19");
test("07.05.219");
test("07.05.2019");
# Englisch:
test("5/7/9");
test("05/07/19");
test("05/07/219");
test("5/7/2019");
test("05/07/2019");
# ISO:
test("19-05-7");
test("19-5-7");
test("2019-5-07");
test("9-05-07");
test("19-05-07");
test("219-05-07");
test("2019-05-07");
Resultate:
07.05.19
1557221414 07.05.19
7.5.9 07.05.09
7.5.19 07.05.19
07.05.19 07.05.19
07.05.219 07.05.19
07.05.2019 07.05.19
5/7/9 07.05.09
05/07/19 07.05.19
05/07/219 07.05.19
5/7/2019 07.05.19
05/07/2019 07.05.19
19-05-7 07.05.19
19-5-7 07.05.19
2019-5-07 07.05.19
9-05-07 07.05.09
19-05-07 07.05.19
219-05-07 07.05.19
2019-05-07 07.05.19
Kleine Verbesserung bzgl. "Fehlerfestigkeit".
<?php
function gerShortDate( $s = false ) {
if ( preg_match('/[^0-9\/.-]/', $s ) ) {
trigger_error(
"function date2german: Typ für '$s' nicht implementiert.",
E_USER_NOTICE
);
return false;
}
if ( false === $s ) {
return date( 'd.m.y' );
}
if ( 'integer' == gettype( $s ) ) {
return date( 'd.m.y', $s );
} else {
if ( strpos( $s, '.' ) ) {
$a = explode ('.', $s );
$a[2] = $a[2] % 100;
$s = implode('/', [ $a[1], $a[0], $a[2] ] );
}
return date( 'd.m.y', strtotime( $s ) );
}
}
function test($input=false)
{
echo sprintf("% 12s", $input),"\t", gerShortDate($input) , "\n";
}
test();
test(time());
test('7. Mai 2019');
# Deutsch
test("7.5.9");
test("7.5.19");
test("07.05.19");
test("07.05.219");
test("07.05.2019");
# Englisch:
test("5/7/9");
test("05/07/19");
test("05/07/219");
test("5/7/2019");
test("05/07/2019");
# ISO:
test("19-05-7");
test("19-5-7");
test("2019-5-07");
test("9-05-07");
test("19-05-07");
test("219-05-07");
test("2019-05-07");
Resultate:
07.05.19
1557222454 07.05.19
7. Mai 2019 PHP Notice: function date2german: Typ für '7. Mai 2019' nicht implementiert. in /tmp/test.php on line 6
7.5.9 07.05.09
7.5.19 07.05.19
07.05.19 07.05.19
07.05.219 07.05.19
07.05.2019 07.05.19
5/7/9 07.05.09
05/07/19 07.05.19
05/07/219 07.05.19
5/7/2019 07.05.19
05/07/2019 07.05.19
19-05-7 07.05.19
19-5-7 07.05.19
2019-5-07 07.05.19
9-05-07 07.05.09
19-05-07 07.05.19
219-05-07 07.05.19
2019-05-07 07.05.19
Tach!
Gegenvorschlag:
Einfach strtotime() nehmen.
function test($input)
{
echo str_pad($input, 12), date('Y-m-d H:i:s', strtotime($input)), "\n";
}
1969-12-31 16:00:00
1557223130 1969-12-31 16:00:00
7. Mai 2019 1969-12-31 16:00:00
7.5.9 2019-05-07 07:05:09
7.5.19 2019-05-07 07:05:19
07.05.19 2019-05-07 07:05:19
07.05.219 1969-12-31 16:00:00
07.05.2019 2019-05-07 00:00:00
5/7/9 2009-05-07 00:00:00
05/07/19 2019-05-07 00:00:00
05/07/219 0219-05-07 00:00:00
5/7/2019 2019-05-07 00:00:00
05/07/2019 2019-05-07 00:00:00
19-05-7 2019-05-07 00:00:00
19-5-7 2019-05-07 00:00:00
2019-5-07 2019-05-07 00:00:00
9-05-07 2009-05-07 00:00:00
19-05-07 2019-05-07 00:00:00
219-05-07 0219-05-07 00:00:00
2019-05-07 2019-05-07 00:00:00
Wenn man keinen Wert vorliegen hat, dann time() und wenn man bereits einen Timestamp von time() vorliegen hat, braucht man den ja auch nicht mehr zu parsen. Deswegen sind die ersten beiden Tests nicht viel wert. Um einen Wert mit Monatsnamen zu parsen, muss man vermutlich locale richtig einstellen.
dedlfix.
Tach!
Gegenvorschlag:
Einfach strtotime() nehmen.
Womöglich. Deine Ergebnisse sagen aber: "Tu das nicht!"
Tach!
Einfach strtotime() nehmen.
Womöglich. Deine Ergebnisse sagen aber: "Tu das nicht!"
Ich finde, sie sind generell ausreichend. Nunja, wenn das Jahr nicht vierstellig ist, werden die Punkt-Varianten als Uhrzeit gewertet. Das Datum ist zufälligerweise richtig, weil es das von heute ist. Mit Nicht-Heute-Testwerten sieht man das deutlicher. Zweistellige Jahreszahlen sind für strtotime() nur für Werte zwischen 61 und 99 definiert.
Dass es bei nicht standardisierten Angaben (sprich Datenmüll) keinen gescheiten Wert ergibt, ist auch ein generelles Problem. Wenn man dazu keine eindeutige Regel definieren kann, ist das auch unlösbar.
dedlfix.
Dass es bei nicht standardisierten Angaben (sprich Datenmüll) keinen gescheiten Wert ergibt, ist auch ein generelles Problem. Wenn man dazu keine eindeutige Regel definieren kann, ist das auch unlösbar.
Ja richtig, ich halte es aus diesem Grund für sinnvoller defensiv vorzugehen und eine Exception zu schmeißen, wenn die Eingabe nicht auf das erwartete Format passt, anstatt zu versuchen den Eingabestring willkürlich zu (re)interpretieren.
function format(string $input) : string {
$dateTime = DateTime::createFromFormat('Y-m-d', $input);
if ($dateTime instanceof DateTime) {
return $dateTime->format('d.m.y');
} else {
throw new DomainException("$input does not match the expected format yyyy-mm-dd.");
}
}
Dass es bei nicht standardisierten Angaben (sprich Datenmüll)
Eigentlich hat Bernd schon gechrieben, was er braucht:
ich habe immer ein Datum in diesem Format: 2019-05-07.
Insoweit erweist sich mein erster Ansatz als schnell und zielführend.
Tach!
Eigentlich hat Bernd schon gechrieben, was er braucht:
ich habe immer ein Datum in diesem Format: 2019-05-07.
Insoweit erweist sich mein erster Ansatz als schnell und zielführend.
Gerade mit diesem Format hat strtotime() kein Problem. Wenn man das nicht als Stringverarbeitung lösen möchte, ist man mit
date('d.m.y', strtotime($date))
einfacher am Ziel. Einfacher auch, weil man diesem Ausdruck direkt ansieht, was er macht und man nicht erst noch analysieren muss, was da wie auseinandergenommen und wieder zusammengesetzt wird.
dedlfix.
Gerade mit diesem Format hat strtotime() kein Problem.
Ich habe geschrieben "schnell und zielführend".
Dein:
date('d.m.y', strtotime($date))
... ist zwar auch zielführend, auch "einfach zu notieren" - aber vermutlich langsamer. Einfach weil es mehr unnötiges macht, darunter "teures" Parsen des Strings.
Tach!
Dein:
date('d.m.y', strtotime($date))
... ist zwar auch zielführend - aber vermutlich langsamer. Einfach weil es mehr unnötiges macht, darunter "teures" Parsen des Strings.
Verständlichkeit ist mir wichtiger als vermutete Performance. Wenn Performance eine Rolle spielt, würde ich lieber sehen, dass ich das Problem gar nicht erst bekomme, einen Datumsstring umformatieren zu wollen.
dedlfix.
Wenn Performance eine Rolle spielt, würde ich lieber sehen, dass ich das Problem gar nicht erst bekomme, einen Datumsstring umformatieren zu wollen.
Den Hinweis hatte ich gelesen und das mögliche Upgrade gleich so verbaut, dass Bernd nichts umbauen muss. (Deshalb habe ich die Funktion ja so gebaut, dass die auch die Unix-Time in Sekunden schluckt.)
Insoweit erweist sich mein erster Ansatz als schnell und zielführend.
"Schnell" ist eine hohle Phrase, wenn du nicht gemessen hast und ergibt ohne Bezugspunkte ohnehin nicht viel Sinn. Außerdem ist das Umformatieren eines zehn Zeichen langen Strings wohl kaum ein Performance-Engpass im Gesamtkontext der Anwendung. Für Performance-Optimierung gilt die Faustregel: Machen, wenn es zu spät ist. Zuerst sollte man den Code möglichst lesbar und wartungsfreundlich notieren. Anschließend kann man mit einem Profiler nach Optimierungspotenzialen suchen und dort optimieren, wo sich wirklich etwas einsparen lässt.
Außerdem stecken in deinem Code zwei Feature-Creeps: Die Behandlung von Zeitstempeln und der Fallback auf das aktuelle Jahr. Dafür fehlt mir eine Fehlerbehandlung für ungültige Eingaben. Dafür habe ich im Üübrigen die Negativ-Bewertung verliehen.
"Schnell" ist eine hohle Phrase, wenn du nicht gemessen hast
Soso. Dann messen wir doch einfach mal:
<?php
function dedlfix ( $input ) {
return date( 'd.m.y', strtotime( $input ) );
}
function ursus( $date ) {
return (
substr( $date, 8, 2) . '.'
. substr( $date, 5, 2 ) . '.'
. substr( $date, 2, 2 )
);
}
$Imax = 1000000;
$string = '2019-07-05';
$Tstart = microtime(true);
for ( $i=0; $i < 10000; $i++ ) {
$dummy = dedlfix( $string );
}
echo "Methode dedlfix: " . ( microtime(true) - $Tstart ) , "\n";
$Tstart = microtime(true);
for ( $i=0; $i < 10000; $i++ ) {
$dummy = ursus( $string );
}
echo "Methode ursrus : " . ( microtime(true) - $Tstart ) , "\n";
Resultate:
Methode dedlfix: 0.085968971252441
Methode ursrus : 0.018429040908813
Die Vorhersage war nicht schwierig weil strtotime ziemlich viel macht.
Für Performance-Optimierung gilt die Faustregel: Machen, wenn es zu spät ist. Zuerst sollte man den Code möglichst lesbar und wartungsfreundlich notieren.
Jetzt ist mir wenigstens klar, dass da jemand mein Zeug negativ bewertet (und dann schweigt) mit dessen Vorgehensweisen und Erkenntnissen ich wohl kaum einverstanden sein kann. Was Du da ansagst läuft nämlich auf "Schnell zusammenschießen und falls der Kunde sich beklagt erst mal auf ihn und dessen Hardware zeigen und erst nach einem Nachweis durch einen vom Gericht bestellten Sachverständigen nach den - hier offensichtlichen und ohne Profiler erkennbaren - Performancebremsen schauen" hinaus.
Tach!
Dann messen wir doch einfach mal:
$Imax = 1000000; // ... for ( $i=0; $i < 10000; $i++ ) {
Resultate:
Methode dedlfix: 0.050378084182739 Methode ursrus : 0.018560886383057
Toll, aber zum einen ist das anderer Code und zum anderen finden in echten Anwendungen wohl eher keine $Imax oder 10000 derartige Aufrufe in einer Schleife statt. Der Unterschied in realistischen Anwendungen geht irgendwo im Grundrauschen unter.
Für Performance-Optimierung gilt die Faustregel: Machen, wenn es zu spät ist. Zuerst sollte man den Code möglichst lesbar und wartungsfreundlich notieren.
Was Du da ansagst läuft auf: "Schnell zusammenschießen und falls der Kunde sich beklagt erstmal auf ihn und dessen Hardware zeigen und erst dann mal nach den - hier offensichtlichen und ohne Profiler erkennbaren - Performancebremsen schauen" hinaus.
Nicht übertreiben, wenn du ernst genommen werden möchtest. Mit solcher Microptimierung gewinnst du keinen Blumentopf. Such lieber nach echten Laufzeitfressern. Aufwendig zu lesender Code ist auch nicht unbedingt kostensparender.
dedlfix.
Nicht übertreiben, wenn du ernst genommen werden möchtest.
Du hast zwar jetzt gesehen, dass ich 1 Mio mal das Datum umformatiert habe, aber dass insgesamt 1 Mio irgendwelcher vergleichbarer Operationen stattfinden bis eine Webseite fertig ist, ist gar nicht so abnorm.
Auch nicht abnorm: Eine Webseite mit nur 1000 solcher solcher Operationen wird 1000 mal abgerufen. Schon haben wir 1 Mio mal die selbe Operation. Es gibt eine faktisch unbegrenzte Anzahl an Möglichkeiten um die Million oder weit mehr zu erreichen: 10 verschiedene Webseiten mit 100 solchen Operationen werden je tausend mal abgerufen …
Methode dedlfix: 0.050378084182739 Methode ursrus : 0.018560886383057
Das war ja noch die "unfaire Messung" weil die Methode dedlfix da den Integer zurückgab. Mit der Rückumwandlung des Integers in das Datumsformat durch date() war es eine ganze Ecke drastischer:
Methode dedlfix: 0.085968971252441 Methode ursrus : 0.018429040908813
Mit solcher Microptimierung gewinnst du keinen Blumentopf.
Kommt das nicht auf den Wettbewerb an?
Such lieber nach echten Laufzeitfressern.
Die findet man oft in der oft sinnlosen sinnlosen Benutzung dieser megabyte-schweren, "gut getesteten", "Eier, Milch und Wolle gebenden" Frameworks statt eines "Zehnzeilers" in "Vanilla" (der einfachen Ptogrammiersprache)…
Aufwendig zu lesender Code ist auch nicht unbedingt kostensparender.
Also wenn Dir die paar Zeilen von mir als "aufwendig zu lesender Code" erscheinen … naja (Mir fehlen grad die Worte.)
Hallo,
Du hast zwar jetzt gesehen, dass ich 1 Mio mal das Datum umformatiert habe,
hat er das? Ich konnte das nicht sehen. Ich hab nur 10000 gesehen.
Methode dedlfix: 0.050378084182739 Methode ursrus : 0.018560886383057
Vorallem sieht man hier nicht die Einheit. Es handelt sich laut php.net um 0.05 Mikrosekunden bzw. 0.01 µs…
Gruß
Kalk
hat er das? Ich konnte das nicht sehen. Ich hab nur 10000 gesehen.
Ja Richtig. Da habe ich Mist gebaut.
Weil zwei mal for ( $i=0; $i < 10000; $i++ ) {
da stand wollte ich eigentlich die 10000 durch $Imax ( = 1 Mio) ersetzen und habs dann doch nicht getan... Kommt in der Eile vor.
Und ändert am faktischen Inhalt des Ergebnisses, also am Geschwindigkeitsunterschied nichts.
Vorallem sieht man hier nicht die Einheit. Es handelt sich laut php.net um 0.05 Mikrosekunden bzw. 0.01 µs…
Nein. Die Angabe auf PHP.net …
microtime() gibt den aktuellen Unix-Timestamp mit Mikrosekunden zurück.
ist stark verwirrend.
Blick in die Wirklichkeit:
<?php
$Tstart = microtime( true );
sleep( 1 ); # Wartet 1 Sekunde
echo ( microtime(true) - $Tstart ) , "\n";
Resultat:
1.0001120567322
Die Angabe ist also, wenn diese als Dezimalzahl microtime(true)
erfolgt, in Sekunden.
Tach!
Vorallem sieht man hier nicht die Einheit. Es handelt sich laut php.net um 0.05 Mikrosekunden bzw. 0.01 µs…
Nein. Die Angabe auf PHP.net …
microtime() gibt den aktuellen Unix-Timestamp mit Mikrosekunden zurück.
ist stark verwirrend.
Das Handbuch schreibt es schon richtig, man muss es nur komplett lesen.
Microsekunden sind es nur, wenn man die Funktion ohne true aufruft. Da kommt ein Ergebnis aus zwei Teilen, dem Microsekunden-Anteil, einem Leerzeichen und den Sekunden seit Epoch. Mit true gibt es stattdessen einen Float-Wert, der die Sekunden seit Epoch angibt und die Bruchteile im Nachkomma-Anteil hat, was dann also Zehntel- und so weiter Sekunden sind.
dedlfix.
Vorallem sieht man hier nicht die Einheit. Es handelt sich laut php.net um 0.05 Mikrosekunden bzw. 0.01 µs…
Nein. Die Angabe auf PHP.net …
microtime() gibt den aktuellen Unix-Timestamp mit Mikrosekunden zurück.
ist stark verwirrend.
Das Handbuch schreibt es schon richtig, man muss es nur komplett lesen.
Eigentlich muss man das gar nicht:
microtime() gibt den aktuellen Unix-Timestamp mit Mikrosekunden zurück.
"MIT Mikrosekunden" heißt nicht "IN Mikrosekunden" -also ist au den Unix-Timestamp abzustellen, der die Anzahl der Sekunden seit dem 1.1.1970 in ganzen Sekunden wieder gibt.
Aber da sich sogar intelligente und hoch angesehene Menschen wie germanoteutonische RichterInnen erweislich von weit weniger verwirren lassen ist mein "stark verwirrend" wohl die richtige Wortwahl.
Hallo ursus contionabundo,
Aber da sich sogar intelligente und hoch angesehene Menschen wie germanoteutonische RichterInnen
Was haben die denn nun wieder damit zu tun?
Bis demnächst
Matthias
Hi,
Was haben die denn nun wieder damit zu tun?
Was dem einen sein Perl, ist dem anderen sein Richter …
cu,
Andreas a/k/a MudGuard
Nein. Die Angabe auf PHP.net …
microtime() gibt den aktuellen Unix-Timestamp mit Mikrosekunden zurück. ist stark verwirrend.
Hab grad sehr gründlich nachgesehen:
Wenn get_as_float TRUE ist, gibt microtime() stattdessen einen float zurück, welcher die aktuelle Zeit in Sekunden seit Beginn der Unix Epoche angibt (die Nachkommastellen geben die Mikrosekunden an).
steht viel weiter unten.
Die haben jetzt auch die Rückgabebeschreibung neu angeordnet. statt
(mixed) foo(bar)
(wie früher) steht da seit dem Update:
foo(bar) :mixed
Hallo,
Nein. Die Angabe auf PHP.net … ist stark verwirrend.
Ups, ja, also verwirrend genug, dass ich, äh…
sorry
Gruß
Kalk
Ups, ja, also verwirrend genug, dass ich, äh…
… im positiven Gegensatz zu so manchen, angeblich intelligenten und hoch angesehenen, tatsächlich aber in ihrer Arroganz abgesoffenen teutogermanischen(n) RichterInnen kannst Du das wenigstens einräumen.
Tach!
Nicht übertreiben, wenn du ernst genommen werden möchtest.
Du hast zwar jetzt gesehen, dass ich 1 Mio mal das Datum umformatiert habe, aber dass insgesamt 1 Mio irgendwelcher vergleichbarer Operationen stattfinden bis eine Webseite fertig ist, ist gar nicht so abnorm.
10000 waren es nur.
Wenn ich Daten für 1 Mio solcher Operationen hätte, würde ich die wohl eher seitenweise anzeigen lassen wollen, so dass einerseits pro Durchlauf nur wenige entstehen, andererseits das Gros des Rechenaufwands dadurch entsteht, dass der Anwender durch diese Datenmenge navigiert. Wenn die Daten dafür aus einem DBMS kommen, wäre es je nach Anwendungsfall sinnvoll, sie gleich dort richtig formatieren zu lassen, statt erstmal falsch und dann zu korrigieren. Aber das macht das Kraut in dem Fall auch nicht fetter.
Such lieber nach echten Laufzeitfressern.
Die findet man oft in der oft sinnlosen sinnlosen Benutzung dieser megabyte-schweren, "gut getesteten, "Eier, Milch und Wolle gebenden" Frameworks statt eines Zehnzeilers in "Vanilla" (der einfachen Ptogrammiersprache)…
Zum Beispiel. Die eigentlichen Einsatzgebiete solcher Frameworks liegen aber eher bei Anwendungen mit etwas mehr als 10 Zeilen (grobe Schätzung 😉).
Aufwendig zu lesender Code ist auch nicht unbedingt kostensparender.
Also wenn Dir die paar Zeilen von mir als "aufwendig zu lesender Code" erscheinen … naja (Mir fehlen grad die Worte.)
25 Zeilen zu einer finde schon aufwendiger. Da sollte noch ein "er" am "Aufwendig" hängen. Aber es geht mir nicht um die Menge des Codes, sondern um das, was er macht. Bei der gezeigten Funktion muss man erstmal nachvollziehen, welche Teile des Strings betroffen sind und was am Ende daraus gemacht wird. Das 'd.m.y' zeigt mir hingegen auf einen Blick, was entstehen soll.
dedlfix.
Wenn die Daten dafür aus einem DBMS kommen, wäre es je nach Anwendungsfall sinnvoll, sie gleich dort richtig formatieren zu lassen, statt erstmal falsch und dann zu korrigieren. Aber das macht das Kraut in dem Fall auch nicht fetter.
Wenn die Daten aus einem DBMS kommen und ich brauche die für Ausgaben unterschiedlich formatiert, muss womöglich weitere Berechnungen und Vergleiche damit machen, dann kippe ich Zeitangaben vom DBMS bevorzugt als Unix-Timestamp in die Rückgabe und formatiere die in der Anwendung, konkret dem Moment der Ausgabe.
Wir sind uns also nur in einem Punkt nicht einig:
Ich meine nämlich, das macht das Kraut sehr viel fetter. Bei Webseiten hat man ja keinen unmittelbaren Einfluss auf die Abrufhäufigkeit.
Hallo,
Resultate:
Methode dedlfix: 0.050378084182739 Methode ursrus : 0.018560886383057
Wow, 0,018 µs statt 0,05 und das für 10000 Daten.
Jetzt überlege ich grad, wie oft Bernd diese Funktion wohl einsetzen muss.
Täglich einmal für eine Handvoll Daten oder gar ein einziges Mal für einen großen Haufen, der vielleicht sogar die 10000 überschreitet?
Vermutlich gibts lohnendere Ziele für Performanceüberlegungen…
Gruß
Kalk
Jetzt überlege ich grad, wie oft Bernd diese Funktion wohl einsetzen muss.
Dann messen wir doch einfach mal:
Die Vorhersage war nicht schwierig, weil strtotime ziemlich viel macht.
Deine ursprüngliche Funktion macht aber auch viel mehr, als das was du gebenchmarkt hast. Aber vor allem kann man dem Benchmark doch ablesen, dass hier kein großes Optimierungspotenzial besteht. 10.000 Iterationen brauchen ein paar hunderstel Sekunden. Da zu optimieren, ist weder effizient noch effektiv.
Für Performance-Optimierung gilt die Faustregel: Machen, wenn es zu spät ist. Zuerst sollte man den Code möglichst lesbar und wartungsfreundlich notieren.
Jetzt ist mir wenigstens klar, dass da jemand mein Zeug negativ bewertet (und dann schweigt) mit dessen Vorgehensweisen und Erkenntnissen ich wohl kaum einverstanden sein kann.
Ich habe nicht geschwiegen, ich habe dir doch erklärt, wofür die Negativ-Bewertung war.
Was Du da ansagst läuft auf: "Schnell zusammenschießen und falls der Kunde sich beklagt erstmal auf ihn und dessen Hardware zeigen und erst dann mal nach den - hier offensichtlichen und ohne Profiler erkennbaren - Performancebremsen schauen" hinaus.
Nein, ich sage nicht, man soll keine Perfromance-Optimierung betreiben, ich sage man soll sie effektiv und effizient betrieben. Effektiv, indem man die größten Optimierungspotenziale zuerst verbessert und effizient, indem man die Entwicklungszeit sinnvoll nutzt und nicht mit Mikro-Optimierung verschwendet. Das ist das kleine Performance-Einmaleins.
Ich habe nicht geschwiegen, ich habe dir doch erklärt, wofür die Negativ-Bewertung war.
Stellt sich die Frage "wann" und welche Provokation dafür notwendig war.
Deine ursprüngliche Funktion macht aber auch viel mehr, als das was du gebenchmarkt hast.
Dann messen wir die eben mal, das Ergebnis wird Dir nicht gefallen:
<?php
function dedlfix ( $input ) {
return date( 'd.m.y', strtotime( $input ) );
}
function ursus( $date = false ) {
if ( false === $date ) {
$date = time();
}
if ( 'string' == gettype( $date ) ) {
return (
substr( $date, 8, 2) . '.'
. substr( $date, 5, 2 ) . '.'
. substr( $date, 2, 2 )
);
} elseif ( 'integer' == gettype( $date ) ) {
return date( 'd.m.y', $date );
} else {
trigger_error(
"function date2german: Typ für '$date' nicht implementiert.",
E_USER_NOTICE
);
return false;
}
}
$Imax = 1000000;
$string = '2019-07-05';
$Tstart = microtime(true);
for ( $i=0; $i < 10000; $i++ ) {
$dummy = dedlfix( $string );
}
echo "Methode dedlfix: " . ( microtime(true) - $Tstart ) , "\n";
$Tstart = microtime(true);
for ( $i=0; $i < 10000; $i++ ) {
$dummy = ursus( $string );
}
echo "Methode ursrus : " . ( microtime(true) - $Tstart ) , "\n";
Ausgaben:
Methode dedlfix: 0.087255001068115
Methode ursrus : 0.02013111114502
Deine ursprüngliche Funktion macht aber auch viel mehr, als das was du gebenchmarkt hast.
Dann messen wir die eben mal, das Ergebnis wird Dir nicht gefallen:
Doch, es gefällt mir, dass du dir die Mühe machst und belastbare Fakten schaffst. Ich hab meine negative Bewertung deshalb auch zurückgezogen.
An meiner persönlichen Bewertung der Resultate ändert das aber nichts. Der Messfehler hat sich signifikant auf das Ergebnis ausgewirkt, aber nach wie vor liegt das Optimierungspotenzial im zu vernachlässigbaren Mikrosekunden-Bereich. Da zu investieren lohnt sich nicht. Ich habe auch mal mit phpbench einen Schnelltest gemacht und alle bisher vorgeschlagenen Lösungen einbezogen, das Ergebnis deckt sich im Wesentlichen mit deinem:
Warm-Up-Revisionen: 5
Iterationen: 5
Revisionen: 10000
Standardabweichung: max. 5%
System: Windows 10, PHP 7.3 mit Opcache
+-------------------+---------+---------+---------+---------+---------+--------+-------+
| subject | best | mean | mode | worst | stdev | rstdev | diff |
+-------------------+---------+---------+---------+---------+---------+--------+-------+
| benchBernd | 1.776μs | 1.812μs | 1.791μs | 1.899μs | 0.044μs | 2.43% | 3.37x |
| benchUrsus | 0.532μs | 0.538μs | 0.534μs | 0.546μs | 0.006μs | 1.05% | 1.00x |
| benchDedlfix | 2.945μs | 2.972μs | 2.954μs | 3.024μs | 0.030μs | 1.02% | 5.53x |
| benchRolf | 1.025μs | 1.051μs | 1.040μs | 1.085μs | 0.021μs | 2.04% | 1.96x |
| bench1UnitedPower | 3.124μs | 3.144μs | 3.138μs | 3.174μs | 0.017μs | 0.55% | 5.85x |
+-------------------+---------+---------+---------+---------+---------+--------+-------+
Dafür dass meine Methode außerdem eine Plausibilitäts-Prüfung der Eingabe-Daten beinhaltet performt sie gar nicht so schlecht. Ich gewehre hier der Robustheit und Lesbarkeit vorzug vor Performanz.
Verzeichnisstruktur:
composer.json
phpbench.json
src/
FormatDateBench.php
Zum Ausführen:
composer update
./vendor/bin/phpbench run src/FormatDateBench.php --report=aggregate --retry-threshold=5
composer.json
{
"name": "selfhtml/benchmark-format-date",
"description": "Benchmarks some methods to format date-strings.",
"type": "project",
"require": {
"phpbench/phpbench": "^0.16.9"
},
"autoload": {
"psr-4": {
"SelfHtml\\": "src"
}
}
}
phpbench.json
{
"bootstrap": "vendor/autoload.php"
}
src/FormatDateBench.php
<?php
namespace SelfHtml;
use \DateTime;
use \DomainException;
class FormatDateBench
{
public function provideDateStrings()
{
yield ['date' => '2019-05-07'];
}
/**
* @ParamProviders({"provideDateStrings"})
* @Warmup(5)
* @Revs({10000})
* @Iterations(5)
*/
public function benchBernd($params)
{
$date = $params['date'];
$d = explode("-", $date);
return sprintf("%02d.%02d.%02d", $d[2], $d[1], substr($d[0], 2, 4));
}
/**
* @ParamProviders({"provideDateStrings"})
* @Warmup(5)
* @Revs({10000})
* @Iterations(5)
*/
public function benchUrsus($params)
{
$date = $params['date'];
if (false === $date) {
$date = time();
}
if ('string' == gettype($date)) {
return (
substr($date, 8, 2) . '.' .
substr($date, 5, 2) . '.' .
substr($date, 2, 2)
);
} elseif ('integer' == gettype($date)) {
return date('d.m.y', $date);
} else {
trigger_error(
"function date2german: Typ für '$date' nicht implementiert.",
E_USER_NOTICE
);
return false;
}
}
/**
* @ParamProviders({"provideDateStrings"})
* @Warmup(5)
* @Revs({10000})
* @Iterations(5)
*/
public function benchDedlfix($params)
{
$input = $params['date'];
return date('d.m.y', strtotime($input));
}
/**
* @ParamProviders({"provideDateStrings"})
* @Warmup(5)
* @Revs({10000})
* @Iterations(5)
*/
public function benchRolf($params)
{
$date = $params['date'];
switch (gettype($date)) {
case 'boolean':
return date('d.m.y');
case 'integer':
return date('d.m.y', $date);
case 'string':
$yearOffs = strlen($date) == 8 ? 0 : 2;
$fmtDate = function ($s, $d, $m, $y) {
return substr($s, $d, 2).".".substr($s, $m, 2).".".substr($s, $y, 2);
};
// yy-mm-dd oder yyyy-mm-dd
if ($date[$yearOffs+2] == "-" && $date[$yearOffs+5] == "-")
return $fmtDate($date, $yearOffs+6, $yearOffs+3, $yearOffs);
// dd.mm.yy oder dd.mm.yyyy
if ($date[2] == "." && $date[5] == ".")
return $fmtDate($date, 0, 3, $yearOffs+6);
// mm/dd/yy oder mm/dd/yyyy
if ($date[2] == "/" && $date[5] == "/")
return $fmtDate($date, 3, 0, $yearOffs+6);
}
trigger_error(
"function date2german: Typ für '$date' nicht implementiert.",
E_USER_NOTICE
);
return false;
}
/**
* @ParamProviders({"provideDateStrings"})
* @Warmup(5)
* @Revs({10000})
* @Iterations(5)
*/
public function bench1UnitedPower($params)
{
$input = $params['date'];
$dateTime = DateTime::createFromFormat('Y-m-d', $input);
if ($dateTime instanceof DateTime) {
return $dateTime->format('d.m.y');
} else {
throw new DomainException("$input does not match the expected format yyyy-mm-dd.");
}
}
}
Hallo 1unitedpower,
ich habe noch einen Testlauf mit meiner preg_match Methode gemacht, allerdings stumpf mit handgeschnitztem microtime in einem Commandline-PHP.
Ergebnis: Die benchRolfPreg Methode braucht ca doppelt so lange wie benchRolf und schiebt sich damit hinter benchBernd auf den 4. Platz. Wenn ich von y-m-d auf d.m.y oder m/d/y wechsele, erhöht sich die Laufzeit pro Stufe um ca 8%, aber da ist viel Varianz drin, der Test läuft auf einer VM die nicht allein auf ihrem Host ist.
PHP cached dem Vernehmen nach Regexe, d.h. er erste Aufruf dauert länger als die Folgeaufrufe, das kann ich aber nicht messen. Die Laufzeit ist zu kurz, für einen Call werden mir immer 0 Mikrosekunden gemeldet. Die kleinste microtime-Differenz, die ich hier sehe, scheint 1ms zu sein. Sehr ärgerlich, Windows kann das eigentlich genauer, aber PHP scheint es nicht zu nutzen.
Rolf
ich habe noch einen Testlauf mit meiner preg_match Methode gemacht, allerdings stumpf mit handgeschnitztem microtime in einem Commandline-PHP.
Ups, hab die Methode glatt vergessen. Hab sie nun auch mal in meinen Benchmark gepackt.
PHP cached dem Vernehmen nach Regexe, d.h. er erste Aufruf dauert länger als die Folgeaufrufe, das kann ich aber nicht messen. Die Laufzeit ist zu kurz, für einen Call werden mir immer 0 Mikrosekunden gemeldet.
Das kann gut sein, dafür gibt es in phpbench die Warmup-Phase. Da werden die Benchmarks ausgeführt ohne dass sie gemessen werden.
Die kleinste microtime-Differenz, die ich hier sehe, scheint 1ms zu sein. Sehr ärgerlich, Windows kann das eigentlich genauer, aber PHP scheint es nicht zu nutzen.
Hmm, ich hab auch PHP unter Windows benutzt, bei mir scheint das kein Problem zu sein.
Hab in einem neuen Lauf alle Algorithmen auf ihren Kern reduziert und noch einen einen sehr banalen Algorithmus benchStringAccess
hinzugenommen. Parameter wie zuvor.
+-------------------+---------+---------+---------+---------+------- -+--------+-------+
| subject | best | mean | mode | worst | stdev | rstdev | diff |
+-------------------+---------+---------+---------+---------+------- -+--------+-------+
| benchBernd | 1.851μs | 1.890μs | 1.865μs | 1.951μs | 0.038μs | 2.03% | 4.73x |
| benchUrsus | 0.507μs | 0.517μs | 0.520μs | 0.530μs | 0.009μs | 1.67% | 1.30x |
| benchDedlfix | 3.213μs | 3.260μs | 3.240μs | 3.342μs | 0.045μs | 1.39% | 8.16x |
| benchRolfPreg | 1.015μs | 1.036μs | 1.037μs | 1.053μs | 0.014μs | 1.33% | 2.59x |
| benchStringAccess | 0.390μs | 0.399μs | 0.393μs | 0.414μs | 0.010μs | 2.43% | 1.00x |
| bench1UnitedPower | 3.323μs | 3.373μs | 3.342μs | 3.438μs | 0.046μs | 1.36% | 8.45x |
+-------------------+---------+---------+---------+---------+------- -+--------+-------+
<?php
namespace SelfHtml;
use \DateTimeImmutable;
class FormatDateBench
{
public function provideDateStrings()
{
yield ['date' => '2019-05-07'];
}
/**
* @ParamProviders({"provideDateStrings"})
* @Warmup(5)
* @Revs({10000})
* @Iterations(5)
*/
public function benchBernd($params)
{
$date = $params['date'];
$d = explode("-", $date);
return sprintf("%02d.%02d.%02d", $d[2], $d[1], substr($d[0], 2, 4));
}
/**
* @ParamProviders({"provideDateStrings"})
* @Warmup(5)
* @Revs({10000})
* @Iterations(5)
*/
public function benchUrsus($params)
{
$date = $params['date'];
return (
substr($date, 8, 2) . '.' .
substr($date, 5, 2) . '.' .
substr($date, 2, 2)
);
}
/**
* @ParamProviders({"provideDateStrings"})
* @Warmup(5)
* @Revs({10000})
* @Iterations(5)
*/
public function benchDedlfix($params)
{
$input = $params['date'];
return date('d.m.y', strtotime($input));
}
/**
* @ParamProviders({"provideDateStrings"})
* @Warmup(5)
* @Revs({10000})
* @Iterations(5)
*/
public function benchRolfPreg($params)
{
$date = $params['date'];
preg_match("/^..(?<year>..).(?<month>..).(?<day>..)$/", $date, $matches);
return (
$matches['day'] . "." .
$matches['month'] . "." .
$matches['year']
);
}
/**
* @ParamProviders({"provideDateStrings"})
* @Warmup(5)
* @Revs({10000})
* @Iterations(5)
*/
public function benchStringAccess($params)
{
$date = $params['date'];
return $date[8] . $date[9] . '.' . $date[5] . $date[6] . '.' . $date[2] . $date[3];
}
/**
* @ParamProviders({"provideDateStrings"})
* @Warmup(5)
* @Revs({10000})
* @Iterations(5)
*/
public function bench1UnitedPower($params)
{
$input = $params['date'];
return DateTimeImmutable::createFromFormat('Y-m-d', $input)->format('d.m.y');
}
}
D.h. man sollte die Formate "yyyy-mm-dd", "yy-mm-dd", "dd.mm.yy und "dd.mm.yyyy" prüfen, und vielleicht noch "mm/dd/yyyy" und "mm/dd/yy". Ich weiß, dass das nicht sehr zuverlässig ist, aber besser als blindlings GIGO anzuwenden und yyyy-mm-dd zu unterstellen.
Nun ja. Da das ISO-Datum "eher selten" als Eingabe erfolgt haben die berühmte Glaskugel und mein Bauch unisono behauptet, dass das Resultat eines "maschinellen Bemühens" (automatische Ermittlung, Rückgabe aus Datenbank, Datepicker, …) verarbeitet werden soll. Aufklären kann das nur der Bernd.
Und dann kann man wohl aus Performancegründen so unvorsichtig sein. Das ich mit dem Funktionsname nicht ganz einverstanden bin habe ich geschrieben.