net.time4j.engine.AbstractDuration Maven / Gradle / Ivy
/*
* -----------------------------------------------------------------------
* Copyright © 2013-2014 Meno Hochschild,
* -----------------------------------------------------------------------
* This file (AbstractDuration.java) is part of project Time4J.
*
* Time4J is free software: You can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* Time4J is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Time4J. If not, see .
* -----------------------------------------------------------------------
*/
package net.time4j.engine;
import net.time4j.base.MathUtils;
import java.util.List;
/**
* Defines a timespan using the default algorithm of Time4J.
*
* Dependent on the sign of the duration
* there are three cases:
*
*
* - Empty duration => The method {@code addTo()} just yields
* a given time point unaffected.
* - Positive duration => All contained time units will be added
* in the order from largest to smallest units. Convertible units will
* be consolidated in one step. The new time point is relative to
* given time point argument in the future.
* - Negative duration => All contained time units will be
* subtracted in the reversed order from the smallest to the largest
* units. Convertible units will be consolidated in one step. The
* new time point is relative to given time point argument in the
* past.
*
*
* Usually possible element overflows will be truncated such that
* the last valid time point will be determined. The rest of the
* discussion is about the gregorian calendar system and the addition
* of months and days, but is also applicable on other calendar
* systems. Examples in pseudo-code:
*
*
* - [2011-05-31] + [P4D] = [2011-06-04]
* - [2011-05-31] + [P9M] = [2012-02-29]
* - [2011-05-31] + [-P1M] = [2011-04-30]
* - [2011-05-30] + [P1M1D] = [2011-07-01]
* - [2011-05-31] + [P1M1D] = [2011-07-01]
* - [2011-07-01] + [-P1M1D] = [2011-05-30]
* - [2011-05-31] + [-P1Y1M1D] = [2010-04-30]
*
*
* If the smallest existing time unit is used then following
* invariants hold for the addition of a duration and the delta
* between two time points. Let t1 and t2 be two time points with
* {@code t1 <= t2}:
*
* - FIRST INVARIANCE:
* {@code t1.plus(t1.until(t2)).equals(t2) == true}
*
- SECOND INVARIANCE:
* {@code t2.until(t1).equals(t1.until(t2).inverse()) == true}
*
*
* Note: The THIRD INVARIANCE
* {@code t1.plus(t1.until(t2)).minus(t1.until(t2)).equals(t1) == true}
* is often INVALID. A counter example where this invariance is
* violated is given with following dates:
* {t1, t2} = {[2011-05-31], [2011-07-01]}. But if the additional
* condition is required that the day of month is never after 28th
* of a month then this third invariance can be guaranteed.
* Therefore it is recommended to avoid dates near the end of
* month in addition.
*
*
*
* About the mathematical background of specified
* algorithm: Note that the addition is not commutative,
* hence the order of addition steps will impact the result. For
* example a two-step-addition looks like:
*
*
* - [2011-05-30] + [P1M] + [P2D] = [2011-07-02]
* - [2011-05-30] + [P2D] + [P1M] = [2011-07-01]
*
*
* In this context it is understandable that the order of
* addition steps is dependent on the sign of the duration. If
* the addition of a negative duration is interpreted as the
* reversal of the addition of a positive duration then following
* equivalent relation holds (paying attention to non-commutativity
* and given the side conditions to compute the duration without
* remainder completely and to consider a minus-operation as
* equalizing a plus-operation (with t1-day-of-month <= 28):
*
*
* - [t1] - [months] - [days] = [t2]
* - => [t1] - [months] - [days] + [days] = [t2] + [days]
* - => [t1] - [months] = [t2] + [days]
* - => [t1] - [months] + [months] = [t2] + [days] + [months]
* - => [t1] = [t2] + [days] + [months] // day-of-month <= 28
*
*
* The permutation of addition steps is obvious. If Time4J had
* tried the alternative to first add the months and then the days
* even in case of a negative duration then we would have with
*
*
* - t1 = [2013-02-01]
* - t2 = [2013-03-31]
* - duration = t1.until(t2) = [P1M30D]
*
*
* the situation that the mentioned third invariance would be violated
* even if the day of month is the first day of month: t2.minus(P1M30D)
* would not yield t1 but [2013-01-29]. Surely, the sign-dependent
* execution of addition steps cannot completely guarantee the third
* invariance but it can guarantee it at least for all days in original
* date until the 28th of month.
*
* Furthermore the specified algorithm ensures the second invariance
* {@code Duration([t1, t2]) = -Duration([t2, t1])} which expresses
* a physical property of any duration. The second invariance means
* that the sign of a duration can only qualify if the first time point
* is before the second time point or other way around. The sign must
* not qualify the always positive length of a duration itself however.
*
*
* @author Meno Hochschild
* @see AbstractMetric
* @see #addTo(TimePoint)
* @see #subtractFrom(TimePoint)
*/
/*[deutsch]
* Definiert eine Zeitspanne unter Verwendung des Standard-Algorithmus
* von Time4J.
*
* In Abhängigkeit vom Vorzeichen der Dauer
* gibt es drei Fälle zu betrachten:
*
*
* - Leere Dauer => Die Methode {@code addTo()} liefert einfach nur
* den angegebenen Zeitpunkt unverändert zurück.
* - Positive Dauer => Alle in der Dauer enthaltenen
* Zeiteinheiten werden in der Reihenfolge von den größten zu
* den kleinsten bzw. genauesten Einheiten hin addiert. Konvertierbare
* Zeiteinheiten werden in einem Additionsschritt zusammengefasst. Der
* neue Zeitpunkt liegt relativ zum Argument in der Zukunft.
* - Negative Dauer => Alle in der Dauer enthaltenen
* Zeiteinheiten werden in der umgekehrten Reihenfolge von den kleinsten
* bzw. genauesten zu den größten Einheiten hin subtrahiert.
* Konvertierbare Zeiteinheiten werden in einem Subtraktionsschritt
* zusammengefasst. Der neue Zeitpunkt liegt relativ zum Argument
* in der Vergangenheit.
*
*
* Üblicherweise werden eventuell auftretende Überläufe
* auf den zuletzt gültigen Zeitpunkt gekappt. Der Rest der Diskussion
* beschäftigt sich mit dem gregorianischen Kalendersystem und der
* Addition von Monaten und Tagen, gilt aber sinngemäß auch
* für andere Kalendersysteme. Beispiele in Pseudocode:
*
*
* - [2011-05-31] + [P4D] = [2011-06-04]
* - [2011-05-31] + [P9M] = [2012-02-29]
* - [2011-05-31] + [-P1M] = [2011-04-30]
* - [2011-05-30] + [P1M1D] = [2011-07-01]
* - [2011-05-31] + [P1M1D] = [2011-07-01]
* - [2011-07-01] + [-P1M1D] = [2011-05-30]
* - [2011-05-31] + [-P1Y1M1D] = [2010-04-30]
*
*
* Insgesamt gelten für die Addition einer Dauer und
* die Differenz zweier Zeitpunkte zu einer Dauer folgende
* Invarianzbedingungen, wenn auch die jeweils genaueste Zeiteinheit
* in der Differenzberechnung angegeben wird. Seien t1 und t2 zwei
* beliebige Zeitpunkte mit {@code t1 <= t2}:
*
* - ERSTE INVARIANZ:
* {@code t1.plus(t1.until(t2)).equals(t2) == true}
*
- ZWEITE INVARIANZ:
* {@code t2.until(t1).equals(t1.until(t2).inverse()) == true}
*
*
* Zu beachten: Allgemein gilt die DRITTE INVARIANZ
* {@code t1.plus(t1.until(t2)).minus(t1.until(t2)).equals(t1) == true}
* NICHT. Ein Gegenbeispiel ist mit {t1, t2} = {[2011-05-31], [2011-07-01]}
* gegeben. Wird aber hier als zusätzliche Randbedingung verlangt,
* daß etwa der Tag des Monats nicht nach dem 28. liegt, dann gilt
* diese Invarianz doch noch. Es wid daher empfohlen, bei der Addition von
* Monaten möglichst Datumsangaben zu vermeiden, die am Ende eines
* Monats liegen.
*
*
*
* Zum mathematischen Hintergrund des spezifizierten
* Algorithmus: Zu beachten ist, daß die Addition nicht
* kommutativ ist, also die Reihenfolge der Additionsschritte das Ergebnis
* beeinflussen kann. So gilt bei getrennten Schritten (2-malige Addition
* einer Dauer):
*
*
* - [2011-05-30] + [P1M] + [P2D] = [2011-07-02]
* - [2011-05-30] + [P2D] + [P1M] = [2011-07-01]
*
*
* Vor diesem Hintergrund ist zu verstehen, daß die Reihenfolge
* der Additionsschritte vom Vorzeichen der Dauer abhängig ist.
* Wenn die Addition einer negativen Dauer als Umkehrung der Addition
* einer positiven Dauer verstanden wird, so gilt unter Beachtung der
* Tatsache der Nicht-Kommutativität folgende Äquivalenzrelation,
* falls erstens die Dauer ohne Rest vollständig berechnet wird
* und zweitens im konkreten Datenkontext eine minus-Operation eine plus-
* Operation aufheben kann (zum Beispiel in t1 day-of-month <= 28):
*
*
* - [t1] - [months] - [days] = [t2]
* - => [t1] - [months] - [days] + [days] = [t2] + [days]
* - => [t1] - [months] = [t2] + [days]
* - => [t1] - [months] + [months] = [t2] + [days] + [months]
* - => [t1] = [t2] + [days] + [months] // day-of-month <= 28
*
*
* Die Vertauschung der Reihenfolge der Additionsschritte ist
* offensichtlich. Wäre als Alternative versucht worden, auch
* im Fall einer negativen Dauer zuerst Monate und dann Tage zu
* subtrahieren, dann ergäbe sich zum Beispiel bereits für
* die Datumsangaben t1 = [2013-02-01] und t2 = [2013-03-31] sowie der
* Dauer t1.until(t2) = [P1M30D] die Situation, daß die oben
* erwähnte dritte Invarianz nicht einmal dann gilt, wenn der
* Tag des Monats im Ausgangsdatum der erste ist. Denn: t2.minus(P1M30D)
* ergäbe dann nicht t1, sondern [2013-01-29]. Zwar kann die
* vorzeichenabhängige Ausführung der Rechenschritte nicht
* vollständig die dritte Invarianz garantieren, aber wenigstens
* doch für alle Tage im Ausgangsdatum bis zum 28. eines Monats.
*
* Gleichzeitig hilft der Time4J-Algorithmus die Invarianz
* {@code Duration([t1, t2]) = -Duration([t2, t1])} einzuhalten,
* die eine physikalische Eigenschaft einer jeden zeitlichen Dauer
* ausdrückt. Die zweite Invarianz bedeutet, daß das Vorzeichen
* einer Dauer nur die Lage zweier Zeitpunkte zueinander und nicht die
* stets positive Länge der Dauer selbst qualifizieren darf.
*
*
* @author Meno Hochschild
* @see AbstractMetric
* @see #addTo(TimePoint)
* @see #subtractFrom(TimePoint)
*/
public abstract class AbstractDuration
implements TimeSpan {
//~ Statische Felder/Initialisierungen --------------------------------
private static final int MIO = 1000000;
//~ Methoden ----------------------------------------------------------
@Override
public boolean contains(U unit) {
for (Item> item : this.getTotalLength()) {
if (item.getUnit().equals(unit)) {
return (item.getAmount() > 0);
}
}
return false;
}
@Override
public long getPartialAmount(U unit) {
for (Item> item : this.getTotalLength()) {
if (item.getUnit().equals(unit)) {
return item.getAmount();
}
}
return 0;
}
/**
* Creates a copy of this duration with the same amounts and
* units but the inversed sign.
*
* @return inverted duration
*/
/*[deutsch]
* Erzeugt die Negation dieser Dauer mit gleichen Beträgen,
* aber umgekehrtem Vorzeichen.
*
* @return inverted duration
*/
public abstract AbstractDuration inverse();
@Override
public boolean isPositive() {
return !(this.isNegative() || this.isEmpty());
}
@Override
public boolean isEmpty() {
List- > items = this.getTotalLength();
for (int i = 0, n = items.size(); i < n; i++) {
if (items.get(i).getAmount() > 0) {
return false;
}
}
return true;
}
/**
*
Yields a canonical representation which optionally starts with
* the sign then continues with the letter "P" followed
* by a comma-separated sequence of duration items.
*
* @return String
*/
/*[deutsch]
* Liefert eine kanonische Darstellung, die optional mit einem negativen
* Vorzeichen beginnt, dann mit dem Buchstaben "P" fortsetzt,
* gefolgt von einer komma-separierten Liste der Dauerelemente.
*
* @return String
*/
@Override
public String toString() {
if (this.isEmpty()) {
return "PT0S"; // Sekunde als API-Standardmaß
}
StringBuilder sb = new StringBuilder();
if (this.isNegative()) {
sb.append('-');
}
sb.append('P');
for (
int index = 0, limit = this.getTotalLength().size();
index < limit;
index++
) {
Item item = this.getTotalLength().get(index);
if (index > 0) {
sb.append(',');
}
sb.append(item.getAmount());
sb.append('{');
sb.append(item.getUnit());
sb.append('}');
}
return sb.toString();
}
/**
* Adds this duration to given time point using the
* default algorithm.
*/
/*[deutsch]
* Addiert diese Dauer zum angegebenen Zeitpunkt unter Verwendung
* des Standard-Algorithmus.
*/
@Override
public final > T addTo(T time) {
return this.add(time, this, false);
}
/**
* Subtracts this duration from given time point using the
* default algorithm.
*/
/*[deutsch]
* Subtrahiert diese Dauer vom angegebenen Zeitpunkt unter Verwendung
* des Standard-Algorithmus.
*/
@Override
public final > T subtractFrom(T time) {
return this.add(time, this, true);
}
private > T add(
T time,
TimeSpan timeSpan,
boolean inverse
) {
T result = time;
TimeAxis super U, T> engine = time.getChronology();
List> items = timeSpan.getTotalLength();
boolean negative = timeSpan.isNegative();
if (inverse) {
negative = !timeSpan.isNegative();
}
if (negative) {
int index = items.size() - 1;
while (index >= 0) {
TimeSpan.Item item = items.get(index);
U unit = item.getUnit();
long amount = item.getAmount();
int k = index - 1;
while (k >= 0) {
TimeSpan.Item nextItem = items.get(k);
U nextUnit = nextItem.getUnit();
long nextAmount = nextItem.getAmount();
long factor = getFactor(engine, nextUnit, unit);
if (
!Double.isNaN(factor)
&& (nextAmount < Integer.MAX_VALUE)
&& (factor > 1)
&& (factor < MIO)
&& engine.isConvertible(nextUnit, unit)
) {
amount =
MathUtils.safeAdd(
amount,
MathUtils.safeMultiply(nextAmount, factor)
);
k--;
} else {
break;
}
}
index = k;
result = result.plus(MathUtils.safeNegate(amount), unit);
}
} else {
int index = 0;
int end = items.size();
while (index < end) {
TimeSpan.Item item = items.get(index);
U unit = item.getUnit();
long amount = item.getAmount();
int k = index + 1;
while (k < end) {
TimeSpan.Item nextItem = items.get(k);
U nextUnit = nextItem.getUnit();
long factor = getFactor(engine, unit, nextUnit);
if (
!Double.isNaN(factor)
&& (amount < Integer.MAX_VALUE)
&& (factor > 1)
&& (factor < MIO)
&& engine.isConvertible(unit, nextUnit)
) {
amount =
MathUtils.safeAdd(
nextItem.getAmount(),
MathUtils.safeMultiply(amount, factor)
);
unit = nextUnit;
k++;
} else {
break;
}
}
index = k;
result = result.plus(amount, unit);
}
}
return result;
}
private static long getFactor(
TimeAxis engine,
U unit1,
U unit2
) {
return Math.round(engine.getLength(unit1) / engine.getLength(unit2));
}
}