Ich habe zwei LocalDate
s wie folgt deklariert:
val startDate = LocalDate.of(2019, 10, 31) // 2019-10-31
val endDate = LocalDate.of(2019, 9, 30) // 2019-09-30
Dann berechne ich den Zeitraum zwischen ihnen mit der Period.between
Funktion:
val period = Period.between(startDate, endDate) // P-1M-1D
Hier hat der Zeitraum die negative Anzahl von Monaten und Tagen, die erwartet wird, da dies endDate
früher als ist startDate
.
Wenn ich das jedoch period
wieder zu dem hinzufüge, startDate
ist das Ergebnis, das ich erhalte, nicht das endDate
, sondern das Datum einen Tag zuvor:
val endDate1 = startDate.plus(period) // 2019-09-29
Die Frage ist also, warum nicht die Invariante
startDate.plus(Period.between(startDate, endDate)) == endDate
für diese beiden Termine halten?
Gibt Period.between
jemand einen falschen Punkt zurück oder LocalDate.plus
fügt er ihn falsch hinzu?
date.plus(period).minus(period)
das Ergebnis nach dem Hinzufügen und Subtrahieren von Punkt ( ) nicht immer das gleiche Datum ist. In dieser Frage geht es mehr umPeriod.between
die Invarianten der Funktion.java.time
Kalenderarithmetik. Grundsätzlich sind Hinzufügen und Entfernen nicht miteinander konvertierbar, insbesondere nicht, wenn der Tag des Monats eines oder beider Daten größer als 28 ist. Weitere mathematische Hintergrundinformationen finden Sie in der Klassendokumentation von AbstractDuration in meiner Zeit lib Time4J ...AbstractDuration
Dokumente besagen, dass die Invarianz geltent1.plus(t1.until(t2)).equals(t2) == true
sollte, und ich frage , warum dies hier nicht der Falljava.time
ist.Antworten:
Wenn Sie schauen, wie
plus
für implementiert istLocalDate
du wirst sehen
plusMonths(...)
undplusDays(...)
da.plusMonths
Behandelt Fälle, in denen ein Monat 31 Tage und der andere 30 Tage hat. Der folgende Code wird also gedruckt,2019-09-30
anstatt nicht vorhanden zu sein2019-09-31
Danach führt das Subtrahieren eines Tages zu
2019-09-29
. Dies ist das richtige Ergebnis, da2019-09-29
und2019-10-31
1 Monat 1 Tag auseinander liegenDie
Period.between
Berechnung ist seltsam und läuft in diesem Fall aufWo
getProlepticMonth
ist die Gesamtzahl der Monate von 00-00-00. In diesem Fall ist es 1 Monat und 1 Tag.Von meinem Verständnis, es ist ein Fehler in einem
Period.between
undLocalDate#plus
für negative Perioden Interaktion, da der folgende Code die gleiche Bedeutung hat ,aber es druckt das richtige
2019-10-31
.Das Problem ist, dass das
LocalDate#plusMonths
Datum normalisiert wird, um immer "korrekt" zu sein. Im folgenden Code können Sie sehen, dass nach Subtraktion von 1 Monat vom2019-10-31
Ergebnis2019-09-31
das dann auf normalisiert wird2019-10-30
quelle
Ich glaube, dass Sie einfach kein Glück haben. Die Invariante, die Sie erfunden haben, klingt vernünftig, hält aber nicht in java.time.
Es scheint, dass die
between
Methode nur die Monatszahlen und die Tage des Monats subtrahiert und mit den Ergebnissen zufrieden ist, da die Ergebnisse das gleiche Vorzeichen haben. Ich glaube, ich stimme zu, dass hier wahrscheinlich eine bessere Entscheidung hätte getroffen werden können, aber wie @Meno Hochschild richtig gesagt hat, kann Mathematik mit den 29, 30 oder 31 Monaten kaum eindeutig sein, und ich wage nicht vorzuschlagen, was die bessere Regel hätte gewesen.Ich wette, sie werden es jetzt nicht ändern. Nicht einmal, wenn Sie einen Fehlerbericht einreichen (den Sie immer versuchen können). Zu viel Code hängt bereits davon ab, wie er seit mehr als fünfeinhalb Jahren funktioniert.
Das Hinzufügen
P-1M-1D
zum Startdatum funktioniert so, wie ich es erwartet hätte. Das Subtrahieren von 1 Monat vom 31. September bis zum 31. September und das Subtrahieren von 1 Tag ergibt den 29. September. Auch hier ist es nicht eindeutig, man könnte stattdessen für den 30. September argumentieren.quelle
Analyse Ihrer Erwartungen (im Pseudocode)
Wir müssen verschiedene Themen diskutieren:
Schauen wir uns zuerst die Einheiten an. Tage sind kein Problem, da sie die kleinstmögliche Kalendereinheit sind und sich jedes Kalenderdatum von jedem anderen Datum in vollen Ganzzahlen von Tagen unterscheidet. Wir haben also im Pseudocode immer gleich, ob positiv oder negativ:
Monate sind jedoch schwierig, da der gregorianische Kalender Kalendermonate mit unterschiedlichen Längen definiert. Es kann also vorkommen, dass das Hinzufügen einer Ganzzahl von Monaten zu einem Datum zu einem ungültigen Datum führen kann:
[2019-08-31] + P1M = [2019-09-31]
Die Entscheidung
java.time
, das Enddatum auf ein gültiges zu reduzieren - hier [30.09.2019] - ist vernünftig und entspricht den Erwartungen der meisten Benutzer, da das Enddatum den berechneten Monat weiterhin beibehält. Diese Addition einschließlich einer Monatsendkorrektur ist jedoch NICHT umkehrbar , siehe die rückgängig gemachte Operation namens Subtraktion:[2019-09-30] - P1M = [2019-08-30]
Das Ergebnis ist auch deshalb vernünftig, weil a) die Grundregel der Monatsaddition darin besteht, den Tag des Monats so weit wie möglich zu halten und b) [2019-08-30] + P1M = [2019-09-30].
Was genau ist die Hinzufügung einer Dauer (Periode)?
In
java.time
ist aPeriod
eine Zusammensetzung von Elementen, die aus Jahren, Monaten und Tagen mit ganzzahligen Teilbeträgen besteht. So kann die Hinzufügung von aPeriod
zur Hinzufügung der Teilbeträge zum Startdatum aufgelöst werden. Da Jahre immer in 12-Vielfache von Monaten konvertierbar sind, können wir zuerst Jahre und Monate kombinieren und dann die Summe in einem Schritt addieren, um seltsame Nebenwirkungen in Schaltjahren zu vermeiden. Die Tage können im letzten Schritt hinzugefügt werden. Ein vernünftiges Design wie injava.time
.Wie kann man das Recht
Period
zwischen zwei Daten bestimmen ?Lassen Sie uns zunächst den Fall diskutieren, in dem die Dauer positiv ist, dh das Startdatum liegt vor dem Enddatum. Dann können wir die Dauer immer definieren, indem wir zuerst die Differenz in Monaten und dann in Tagen bestimmen. Diese Reihenfolge ist wichtig, um eine Monatskomponente zu erreichen, da sonst jede Dauer zwischen zwei Daten nur aus Tagen bestehen würde. Verwenden Sie Ihre Beispieldaten:
[2019-09-30] + P1M1D = [2019-10-31]
Technisch gesehen wird das Startdatum zunächst um die berechnete Differenz in Monaten zwischen Start und Ende verschoben. Dann wird das Tagesdelta als Differenz zwischen dem verschobenen Startdatum und dem Enddatum zum verschobenen Startdatum hinzugefügt. Auf diese Weise können wir die Dauer im Beispiel als P1M1D berechnen. So weit so vernünftig.
Wie subtrahiere ich eine Dauer?
Der interessanteste Punkt im vorherigen Additionsbeispiel ist, dass es versehentlich KEINE Monatsendkorrektur gibt. Trotzdem
java.time
kann die umgekehrte Subtraktion nicht durchgeführt werden. Es subtrahiert zuerst die Monate und dann die Tage:[2019-10-31] - P1M1D = [2019-09-29]
Wenn
java.time
stattdessen versucht worden wäre, die Schritte in der Addition vorher umzukehren, wäre die natürliche Wahl gewesen, zuerst die Tage und dann die Monate zu subtrahieren . Mit dieser geänderten Reihenfolge würden wir [2019-09-30] erhalten. Die geänderte Reihenfolge in der Subtraktion würde helfen, solange im entsprechenden Additionsschritt keine Korrektur zum Monatsende erfolgt. Dies gilt insbesondere dann, wenn der Tag des Monats eines Start- oder Enddatums nicht größer als 28 ist (die minimal mögliche Monatslänge). Leiderjava.time
wurde ein anderes Design definiert, dessen SubtraktionPeriod
zu weniger konsistenten Ergebnissen führt.Ist die Addition einer Dauer in der Subtraktion reversibel?
Zunächst müssen wir verstehen, dass die vorgeschlagene geänderte Reihenfolge bei der Subtraktion einer Dauer von einem bestimmten Kalenderdatum nicht die Umkehrbarkeit der Addition garantiert. Gegenbeispiel mit einer Monatsendkorrektur im Zusatz:
Das Ändern der Reihenfolge ist nicht schlecht, da es konsistentere Ergebnisse liefert. Aber wie können die verbleibenden Mängel behoben werden? Die einzige Möglichkeit besteht darin, auch die Berechnung der Dauer zu ändern. Anstatt P3M1D zu verwenden, können wir sehen, dass die Dauer P2M31D in beide Richtungen funktioniert:
Die Idee ist also, die Normalisierung der berechneten Dauer zu ändern. Dies kann erreicht werden, indem geprüft wird, ob die Addition des berechneten Monatsdeltas in einem Subtraktionsschritt reversibel ist - dh die Notwendigkeit einer Korrektur zum Monatsende wird vermieden.
java.time
bietet leider keine solche lösung an. Es ist kein Fehler, kann aber als Designbeschränkung angesehen werden.Alternativen?
Ich habe meine Zeitbibliothek Time4J um reversible Metriken erweitert, die die oben angegebenen Ideen implementieren. Siehe folgendes Beispiel:
quelle