Ich stimme der Meinung des OP zu, dass dies kontraintuitiv und frustrierend ist, aber auch die Bestimmung, was +1 month
in den Szenarien bedeutet, in denen dies geschieht. Betrachten Sie diese Beispiele:
Sie beginnen mit dem 31.01.2015 und möchten sechsmal einen Monat hinzufügen, um einen Planungszyklus für den Versand eines E-Mail-Newsletters zu erhalten. In Anbetracht der anfänglichen Erwartungen des OP würde dies zurückkehren:
- 2015-01-31
- 28.02.2015
- 2015-03-31
- 30.04.2015
- 2015-05-31
- 30.06.2015
Beachten Sie sofort, dass wir erwarten +1 month
, last day of month
1 Monat pro Iteration zu bedeuten oder alternativ hinzuzufügen, jedoch immer in Bezug auf den Startpunkt. Anstatt dies als "letzter Tag des Monats" zu interpretieren, könnten wir es als "31. Tag des nächsten Monats oder zuletzt innerhalb dieses Monats verfügbar" lesen. Dies bedeutet, dass wir vom 30. April bis zum 31. Mai anstatt bis zum 30. Mai springen. Beachten Sie, dass dies nicht darauf zurückzuführen ist, dass es sich um den "letzten Tag des Monats" handelt, sondern darauf, dass "das Datum des Startmonats am nächsten liegt".
Angenommen, einer unserer Benutzer abonniert einen anderen Newsletter, der am 30.01.2015 beginnt. Wofür ist das intuitive Datum +1 month
? Eine Interpretation wäre "30. Tag des nächsten Monats oder am nächsten verfügbar", die zurückkehren würde:
- 30.01.2015
- 28.02.2015
- 30.03.2015
- 30.04.2015
- 30.05.2015
- 30.06.2015
Dies wäre in Ordnung, es sei denn, unser Benutzer erhält beide Newsletter am selben Tag. Nehmen wir an, dass dies ein Problem auf der Angebotsseite und nicht auf der Nachfrageseite ist. Wir befürchten nicht, dass der Benutzer sich darüber ärgert, dass er zwei Newsletter am selben Tag erhält, sondern dass sich unsere Mailserver die Bandbreite für das doppelte Senden nicht leisten können viele Newsletter. In diesem Sinne kehren wir zu der anderen Interpretation von "+1 Monat" als "Am vorletzten Tag eines jeden Monats senden" zurück, die zurückkehren würde:
- 30.01.2015
- 27.02.2015
- 30.03.2015
- 29.04.2015
- 30.05.2015
- 29.06.2015
Jetzt haben wir jede Überschneidung mit dem ersten Satz vermieden, aber wir landen auch am 29. April und 29. Juni, was sicherlich unseren ursprünglichen Intuitionen entspricht, die +1 month
einfach zurückkehren sollten m/$d/Y
oder die m/30/Y
für alle möglichen Monate attraktiv und einfach sind . Betrachten wir nun eine dritte Interpretation der +1 month
Verwendung beider Daten:
31. Januar
- 2015-01-31
- 03.03.2015
- 2015-03-31
- 2015-05-01
- 2015-05-31
- 2015-07-01
30. Januar
- 30.01.2015
- 2015-03-02
- 30.03.2015
- 30.04.2015
- 30.05.2015
- 30.06.2015
Das Obige hat einige Probleme. Der Februar wird übersprungen, was sowohl ein Problem für das Angebotsende sein kann (z. B. wenn eine monatliche Bandbreitenzuweisung vorliegt und der Februar verschwendet wird und der März verdoppelt wird) als auch für das Nachfrageende (Benutzer fühlen sich vom Februar betrogen und nehmen den zusätzlichen März wahr als Versuch, einen Fehler zu korrigieren). Beachten Sie andererseits, dass die beiden Datumsangaben:
- niemals überlappen
- sind immer am selben Datum, wenn dieser Monat das Datum hat (also sieht das Set vom 30. Januar ziemlich sauber aus)
- sind alle innerhalb von 3 Tagen (in den meisten Fällen 1 Tag) nach dem als "korrekt" geltenden Datum.
- sind alle mindestens 28 Tage (ein Mondmonat) von ihrem Nachfolger und Vorgänger entfernt, also sehr gleichmäßig verteilt.
Angesichts der letzten beiden Sätze wäre es nicht schwierig, eines der Daten einfach zurückzusetzen, wenn es außerhalb des tatsächlichen Folgemonats liegt (also zurück zum 28. Februar und 30. April im ersten Satz) und keinen Schlaf über das zu verlieren gelegentliche Überlappung und Abweichung vom Muster "letzter Tag des Monats" gegenüber dem Muster "vorletzter Tag des Monats". Die Erwartung, dass die Bibliothek zwischen "am schönsten / natürlichsten", "mathematischer Interpretation von 31.02. Und anderen Monatsüberläufen" und "relativ zum ersten oder letzten Monat" wählt, endet jedoch immer damit, dass die Erwartungen von jemandem nicht erfüllt werden und Einige Zeitpläne müssen das "falsche" Datum anpassen, um das reale Problem zu vermeiden, das durch die "falsche" Interpretation entsteht.
Auch hier würde ich erwarten +1 month
, dass ein Datum zurückgegeben wird, das tatsächlich im folgenden Monat liegt. Es ist jedoch nicht so einfach wie die Intuition und angesichts der Auswahl ist es wahrscheinlich die sichere Wahl, die Erwartungen der Webentwickler mit Mathematik zu erfüllen.
Hier ist eine alternative Lösung, die immer noch so klobig ist wie jede andere, aber ich denke, sie hat gute Ergebnisse:
foreach(range(0,5) as $count) {
$new_date = clone $date;
$new_date->modify("+$count month");
$expected_month = $count + 1;
$actual_month = $new_date->format("m");
if($expected_month != $actual_month) {
$new_date = clone $date;
$new_date->modify("+". ($count - 1) . " month");
$new_date->modify("+4 weeks");
}
echo "* " . nl2br($new_date->format("Y-m-d") . PHP_EOL);
}
Es ist nicht optimal, aber die zugrunde liegende Logik lautet: Wenn das Hinzufügen von 1 Monat zu einem anderen Datum als dem erwarteten nächsten Monat führt, verschrotten Sie dieses Datum und fügen Sie stattdessen 4 Wochen hinzu. Hier sind die Ergebnisse mit den beiden Testdaten:
31. Januar
- 2015-01-31
- 28.02.2015
- 2015-03-31
- 28.04.2015
- 2015-05-31
- 28.06.2015
30. Januar
- 30.01.2015
- 27.02.2015
- 30.03.2015
- 30.04.2015
- 30.05.2015
- 30.06.2015
(Mein Code ist ein Chaos und würde in einem mehrjährigen Szenario nicht funktionieren. Ich begrüße jeden, der die Lösung mit eleganterem Code umschreibt, solange die zugrunde liegende Prämisse intakt bleibt, dh wenn +1 Monat ein funky Datum zurückgibt, verwenden Sie +4 Wochen stattdessen.)