Ich habe viel mit dem gearbeitet DateTime class
und bin kürzlich beim Hinzufügen von Monaten auf einen Fehler gestoßen, den ich für einen Fehler hielt. Nach ein wenig Recherche scheint es kein Fehler zu sein, sondern wie beabsichtigt zu funktionieren. Nach der hier gefundenen Dokumentation :
Beispiel 2 Vorsicht beim Addieren oder Subtrahieren von Monaten
<?php
$date = new DateTime('2000-12-31');
$date->modify('+1 month');
echo $date->format('Y-m-d') . "\n";
$date->modify('+1 month');
echo $date->format('Y-m-d') . "\n";
?>
The above example will output: 2001-01-31 2001-03-03
Kann jemand rechtfertigen, warum dies nicht als Fehler angesehen wird?
Hat jemand elegante Lösungen, um das Problem zu beheben und es so zu gestalten, dass +1 Monat wie erwartet und nicht wie beabsichtigt funktioniert?
strtotime()
stackoverflow.com/questions/7119777/…Antworten:
Warum ist es kein Fehler:
Das aktuelle Verhalten ist korrekt. Folgendes geschieht intern:
+1 month
erhöht die Monatszahl (ursprünglich 1) um eins. Dies macht das Datum2010-02-31
.Der zweite Monat (Februar) hat 2010 nur 28 Tage, daher korrigiert PHP dies automatisch, indem nur die Tage ab dem 1. Februar weiter gezählt werden. Sie landen dann am 3. März.
So bekommen Sie, was Sie wollen:
Um zu erhalten, was Sie wollen, gehen Sie wie folgt vor: Überprüfen Sie den nächsten Monat manuell. Fügen Sie dann die Anzahl der Tage hinzu, die der nächste Monat hat.
Ich hoffe, Sie können dies selbst codieren. Ich gebe nur, was zu tun ist.
PHP 5.3 Weg:
Um das richtige Verhalten zu erhalten, können Sie eine der neuen Funktionen von PHP 5.3 verwenden, mit der die Zeilengruppe für die relative Zeit eingeführt wird
first day of
. Diese Strophe kann in Kombination verwendet werdennext month
,fifth month
oder+8 months
auf den ersten Tag des angegebenen Monats zu gehen. Anstelle dessen,+1 month
was Sie tun, können Sie diesen Code verwenden, um den ersten Tag des nächsten Monats wie folgt abzurufen:Dieses Skript wird korrekt ausgegeben
February
. Die folgenden Dinge passieren, wenn PHP diese Zeilengruppe verarbeitetfirst day of next month
:next month
erhöht die Monatszahl (ursprünglich 1) um eins. Dies macht das Datum 2010-02-31.first day of
1
Setzt die Tagesnummer auf , was zum Datum 2010-02-01 führt.quelle
Hier ist eine weitere kompakte Lösung, die ausschließlich DateTime-Methoden verwendet und das Objekt direkt ändert, ohne Klone zu erstellen.
Es gibt aus:
quelle
$dt->modify()->modify()
. Funktioniert genauso gut.Dies kann nützlich sein:
quelle
$dateTime->modify('first day of next month')->modify('-1day')
Meine Lösung für das Problem:
quelle
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:
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: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:
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 solltenm/$d/Y
oder diem/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
30. Januar
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:
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:
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
30. Januar
(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.)
quelle
Ich habe eine Funktion erstellt, die ein DateInterval zurückgibt, um sicherzustellen, dass beim Hinzufügen eines Monats der nächste Monat angezeigt wird, und die Tage danach entfernt werden.
quelle
In Verbindung mit Shamittomars Antwort könnte dies dann sein, um Monate "sicher" hinzuzufügen:
quelle
Ich habe mit dem folgenden Code einen kürzeren Weg gefunden:
quelle
Hier ist eine Implementierung einer verbesserten Version von Juhanas Antwort in einer verwandten Frage:
Bei dieser Version wird davon
$createdDate
ausgegangen, dass es sich um einen wiederkehrenden monatlichen Zeitraum handelt, z. B. ein Abonnement, das an einem bestimmten Datum wie dem 31. begonnen hat. Es dauert immer$createdDate
so spät, dass sich "Wiederholungstermine" nicht auf niedrigere Werte verschieben, wenn sie durch weniger wertvolle Monate verschoben werden (z. B. bleiben alle 29., 30. oder 31. Wiederholungstermine am 28. nach dem Bestehen nicht hängen durch ein Nicht-Schaltjahr Februar).Hier ist ein Treibercode zum Testen des Algorithmus:
Welche Ausgänge:
quelle
Dies ist eine verbesserte Version von Kasihasis Antwort in einer verwandten Frage. Dadurch wird eine beliebige Anzahl von Monaten zu einem Datum korrekt addiert oder subtrahiert.
Beispielsweise:
wird ausgegeben:
quelle
Wenn Sie nur vermeiden möchten, einen Monat zu überspringen, können Sie so etwas ausführen, um das Datum zu ermitteln und eine Schleife für den nächsten Monat auszuführen. Reduzieren Sie das Datum um eins und überprüfen Sie es erneut bis zu einem gültigen Datum, an dem $ Starting_calculated eine gültige Zeichenfolge für strtotime ist (dh mysql datetime oder "now"). Dies findet das Ende des Monats um 1 Minute bis Mitternacht statt, anstatt den Monat zu überspringen.
quelle
Erweiterung für die DateTime-Klasse, die das Problem des Addierens oder Subtrahierens von Monaten löst
https://gist.github.com/66Ton99/60571ee49bf1906aaa1c
quelle
Wenn Sie
strtotime()
nur verwenden$date = strtotime('first day of +1 month');
quelle
Ich musste einen Termin für 'diesen Monat im letzten Jahr' bekommen und es wird ziemlich schnell unangenehm, wenn dieser Monat Februar in einem Schaltjahr ist. Ich glaube jedoch, dass dies funktioniert ...: - / Der Trick scheint darin zu bestehen, Ihre Änderung auf den 1. Tag des Monats zu stützen.
quelle
quelle
wird ausgegeben
2
(Februar). wird auch für andere Monate funktionieren.quelle
Für Tage:
Wichtig:
Die Methode
add()
der DateTime-Klasse ändert den Objektwert so, dass nach dem Aufrufenadd()
eines DateTime-Objekts das neue Datumsobjekt zurückgegeben und das Objekt selbst geändert wird.quelle
Sie können dies auch mit date () und strtotime () tun. Zum Beispiel, um dem heutigen Datum 1 Monat hinzuzufügen:
Wenn Sie die datetime-Klasse verwenden möchten, ist dies ebenfalls in Ordnung, dies ist jedoch genauso einfach. Weitere Details hier
quelle
Die akzeptierte Antwort erklärt bereits, warum dies kein Aber ist, und einige andere Antworten stellen eine saubere Lösung mit PHP-Ausdrücken wie dar
first day of the +2 months
. Das Problem mit diesen Ausdrücken ist, dass sie nicht automatisch vervollständigt werden.Die Lösung ist jedoch recht einfach. Zunächst sollten Sie nützliche Abstraktionen finden, die Ihren Problembereich widerspiegeln. In diesem Fall ist es ein
ISO8601DateTime
. Zweitens sollte es mehrere Implementierungen geben, die eine gewünschte Textdarstellung bringen können. Zum BeispielToday
,Tomorrow
,The first day of this month
,Future
- alle eine spezifische Implementierung darstelltISO8601DateTime
Konzept.In Ihrem Fall benötigen Sie also eine Implementierung
TheFirstDayOfNMonthsLater
. Es ist leicht zu finden, wenn man sich die Liste der Unterklassen in einer IDE ansieht. Hier ist der Code:Das Gleiche gilt für die letzten Tage eines Monats. Weitere Beispiele für diesen Ansatz finden Sie hier .
quelle
quelle