/*
* -----------------------------------------------------------------------
* Copyright © 2013-2014 Meno Hochschild, <http://www.menodata.de/>
* -----------------------------------------------------------------------
* 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 <http://www.gnu.org/licenses/>.
* -----------------------------------------------------------------------
*/
package net.time4j.engine;
import net.time4j.base.MathUtils;
import java.util.List;
/**
* <p>Defines a timespan using the default algorithm of Time4J. </p>
*
* <p><a name="algorithm"></a>Dependent on the sign of the duration
* there are three cases: </p>
*
* <ol>
* <li>Empty duration => The method {@code addTo()} just yields
* a given time point unaffected. </li>
* <li>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. </li>
* <li>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. </li>
* </ol>
*
* <p>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: </p>
*
* <ul>
* <li>[2011-05-31] + [P4D] = [2011-06-04]</li>
* <li>[2011-05-31] + [P9M] = [2012-02-29]</li>
* <li>[2011-05-31] + [-P1M] = [2011-04-30]</li>
* <li>[2011-05-30] + [P1M1D] = [2011-07-01]</li>
* <li>[2011-05-31] + [P1M1D] = [2011-07-01]</li>
* <li>[2011-07-01] + [-P1M1D] = [2011-05-30]</li>
* <li>[2011-05-31] + [-P1Y1M1D] = [2010-04-30]</li>
* </ul>
*
* <p>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}: </p>
*
* <ul><li>FIRST INVARIANCE:
* {@code t1.plus(t1.until(t2)).equals(t2) == true}
* </li><li>SECOND INVARIANCE:
* {@code t2.until(t1).equals(t1.until(t2).inverse()) == true}
* </li></ul>
*
* <p><strong>Note:</strong> 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. </p>
*
* <div style="background-color:#E0E0E0;padding:5px;margin:5px;">
*
* <p><strong>About the mathematical background of specified
* algorithm:</strong> 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: </p>
*
* <ul>
* <li>[2011-05-30] + [P1M] + [P2D] = [2011-07-02]</li>
* <li>[2011-05-30] + [P2D] + [P1M] = [2011-07-01]</li>
* </ul>
*
* <p>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): </p>
*
* <ul>
* <li>[t1] - [months] - [days] = [t2]</li>
* <li>=> [t1] - [months] - [days] + [days] = [t2] + [days]</li>
* <li>=> [t1] - [months] = [t2] + [days]</li>
* <li>=> [t1] - [months] + [months] = [t2] + [days] + [months]</li>
* <li>=> [t1] = [t2] + [days] + [months] // day-of-month <= 28</li>
* </ul>
*
* <p>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</p>
*
* <ul>
* <li>t1 = [2013-02-01]</li>
* <li>t2 = [2013-03-31]</li>
* <li>duration = t1.until(t2) = [P1M30D]</li>
* </ul>
*
* <p>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. </p>
*
* <p>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. </p>
* </div>
*
* @author Meno Hochschild
* @see AbstractMetric
* @see #addTo(TimePoint)
* @see #subtractFrom(TimePoint)
*/
/*[deutsch]
* <p>Definiert eine Zeitspanne unter Verwendung des Standard-Algorithmus
* von Time4J. </p>
*
* <p><a name="algorithm"></a>In Abhängigkeit vom Vorzeichen der Dauer
* gibt es drei Fälle zu betrachten: </p>
*
* <ol>
* <li>Leere Dauer => Die Methode {@code addTo()} liefert einfach nur
* den angegebenen Zeitpunkt unverändert zurück. </li>
* <li>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. </li>
* <li>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. </li>
* </ol>
*
* <p>Ü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: </p>
*
* <ul>
* <li>[2011-05-31] + [P4D] = [2011-06-04]</li>
* <li>[2011-05-31] + [P9M] = [2012-02-29]</li>
* <li>[2011-05-31] + [-P1M] = [2011-04-30]</li>
* <li>[2011-05-30] + [P1M1D] = [2011-07-01]</li>
* <li>[2011-05-31] + [P1M1D] = [2011-07-01]</li>
* <li>[2011-07-01] + [-P1M1D] = [2011-05-30]</li>
* <li>[2011-05-31] + [-P1Y1M1D] = [2010-04-30]</li>
* </ul>
*
* <p>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}: </p>
*
* <ul><li>ERSTE INVARIANZ:
* {@code t1.plus(t1.until(t2)).equals(t2) == true}
* </li><li>ZWEITE INVARIANZ:
* {@code t2.until(t1).equals(t1.until(t2).inverse()) == true}
* </li></ul>
*
* <p><strong>Zu beachten:</strong> 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. </p>
*
* <div style="background-color:#E0E0E0;padding:5px;margin:5px;">
*
* <p><strong>Zum mathematischen Hintergrund des spezifizierten
* Algorithmus:</strong> 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): </p>
*
* <ul>
* <li>[2011-05-30] + [P1M] + [P2D] = [2011-07-02]</li>
* <li>[2011-05-30] + [P2D] + [P1M] = [2011-07-01]</li>
* </ul>
*
* <p>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): </p>
*
* <ul>
* <li>[t1] - [months] - [days] = [t2]</li>
* <li>=> [t1] - [months] - [days] + [days] = [t2] + [days]</li>
* <li>=> [t1] - [months] = [t2] + [days]</li>
* <li>=> [t1] - [months] + [months] = [t2] + [days] + [months]</li>
* <li>=> [t1] = [t2] + [days] + [months] // day-of-month <= 28</li>
* </ul>
*
* <p>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. </p>
*
* <p>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. </p>
* </div>
*
* @author Meno Hochschild
* @see AbstractMetric
* @see #addTo(TimePoint)
* @see #subtractFrom(TimePoint)
*/
public abstract class AbstractDuration<U extends ChronoUnit>
implements TimeSpan<U> {
//~ 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;
}
/**
* <p>Creates a copy of this duration with the same amounts and
* units but the inversed sign. </p>
*
* @return inverted duration
*/
/*[deutsch]
* <p>Erzeugt die Negation dieser Dauer mit gleichen Beträgen,
* aber umgekehrtem Vorzeichen. </p>
*
* @return inverted duration
*/
public abstract AbstractDuration<U> inverse();
@Override
public boolean isPositive() {
return !(this.isNegative() || this.isEmpty());
}
@Override
public boolean isEmpty() {
List<Item<U>> items = this.getTotalLength();
for (int i = 0, n = items.size(); i < n; i++) {
if (items.get(i).getAmount() > 0) {
return false;
}
}
return true;
}
/**
* <p>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. </p>
*
* @return String
*/
/*[deutsch]
* <p>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. </p>
*
* @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<U> 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();
}
/**
* <p>Adds this duration to given time point using the
* <a href="#algorithm">default algorithm</a>. </p>
*/
/*[deutsch]
* <p>Addiert diese Dauer zum angegebenen Zeitpunkt unter Verwendung
* des <a href="#algorithm">Standard-Algorithmus</a>. </p>
*/
@Override
public final <T extends TimePoint<? super U, T>> T addTo(T time) {
return this.add(time, this, false);
}
/**
* <p>Subtracts this duration from given time point using the
* <a href="#algorithm">default algorithm</a>. </p>
*/
/*[deutsch]
* <p>Subtrahiert diese Dauer vom angegebenen Zeitpunkt unter Verwendung
* des <a href="#algorithm">Standard-Algorithmus</a>. </p>
*/
@Override
public final <T extends TimePoint<? super U, T>> T subtractFrom(T time) {
return this.add(time, this, true);
}
private <T extends TimePoint<? super U, T>> T add(
T time,
TimeSpan<U> timeSpan,
boolean inverse
) {
T result = time;
TimeAxis<? super U, T> engine = time.getChronology();
List<TimeSpan.Item<U>> items = timeSpan.getTotalLength();
boolean negative = timeSpan.isNegative();
if (inverse) {
negative = !timeSpan.isNegative();
}
if (negative) {
int index = items.size() - 1;
while (index >= 0) {
TimeSpan.Item<U> item = items.get(index);
U unit = item.getUnit();
long amount = item.getAmount();
int k = index - 1;
while (k >= 0) {
TimeSpan.Item<U> 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<U> item = items.get(index);
U unit = item.getUnit();
long amount = item.getAmount();
int k = index + 1;
while (k < end) {
TimeSpan.Item<U> 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 <U> long getFactor(
TimeAxis<U, ?> engine,
U unit1,
U unit2
) {
return Math.round(engine.getLength(unit1) / engine.getLength(unit2));
}
}