Analyse Ihrer Erwartungen (im Pseudocode)
startDate.plus(Period.between(startDate, endDate)) == endDate
Wir müssen verschiedene Themen diskutieren:
- Wie gehe ich mit getrennten Einheiten wie Monaten oder Tagen um?
- Wie wird die Hinzufügung einer Dauer (oder "Periode") definiert?
- Wie bestimme ich den zeitlichen Abstand (Dauer) zwischen zwei Daten?
- Wie ist die Subtraktion einer Dauer (oder "Periode") definiert?
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:
startDate.plus(ChronoUnit.Days.between(startDate, endDate)) == endDate
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 a Period
eine Zusammensetzung von Elementen, die aus Jahren, Monaten und Tagen mit ganzzahligen Teilbeträgen besteht. So kann die Hinzufügung von a Period
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 in java.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). Leider java.time
wurde ein anderes Design definiert, dessen Subtraktion Period
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:
[2011-03-31] + P3M1D = [2011-06-30] + P1D = [2011-07-01] (ok)
[2011-07-01] - P3M1D = [2011-06-30] - P3M = [2011-03-30] :-(
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:
[2011-03-31] + P2M31D = [2011-05-31] + P31D = [2011-07-01] (ok)
[2011-07-01] - P2M31D = [2011-05-31] - P2M = [2011-03-31] (ok)
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:
PlainDate d1 = PlainDate.of(2011, 3, 31);
PlainDate d2 = PlainDate.of(2011, 7, 1);
TimeMetric<CalendarUnit, Duration<CalendarUnit>> metric =
Duration.inYearsMonthsDays().reversible();
Duration<CalendarUnit> duration =
metric.between(d1, d2); // P2M31D
Duration<CalendarUnit> invDur =
metric.between(d2, d1); // -P2M31D
assertThat(d1.plus(duration), is(d2)); // first invariance
assertThat(invDur, is(duration.inverse())); // second invariance
assertThat(d2.minus(duration), is(d1)); // third invariance
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.