net.time4j.CalendarUnit Maven / Gradle / Ivy
/*
* -----------------------------------------------------------------------
* Copyright © 2013-2016 Meno Hochschild,
* -----------------------------------------------------------------------
* This file (CalendarUnit.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;
import net.time4j.engine.ChronoEntity;
import net.time4j.engine.TimePoint;
import net.time4j.engine.UnitRule;
/**
* Represents the most common time units related to a standard
* ISO-8601-calendar.
*
* Default behaviour of addition or subtraction of month-related
* units:
*
* If the addition of months results in an invalid intermediate date
* then the final date will be just the last valid date that is the last
* day of current month. Example:
*
*
* PlainDate date = PlainDate.of(2013, 1, 31);
* System.out.println(date.plus(1, MONTHS));
* // Output: 2013-02-28
*
*
* @author Meno Hochschild
*/
/*[deutsch]
* Repräsentiert die am meisten gebräuchlichen Zeiteinheiten
* bezogen auf einen ISO-8601-konformen Kalender.
*
* Standardverhalten der Addition oder Subtraktion von
* monatsbezogenen Einheiten:
*
* Wenn die Addition von Monaten zu einem ungültigen Datum
* führt, dann wird das zuletzt gültige Datum bestimmt, also der
* letzte Tag des aktuellen Monats. Beispiel:
*
*
* PlainDate date = PlainDate.of(2013, 1, 31);
* System.out.println(date.plus(1, MONTHS));
* // Ausgabe: 2013-02-28
*
*
* @author Meno Hochschild
*/
public enum CalendarUnit
implements IsoDateUnit {
//~ Statische Felder/Initialisierungen --------------------------------
/** Time unit "millennia" (symbol I) */
/*[deutsch] Zeiteinheit "Jahrtausende" (Symbol I) */
MILLENNIA() {
@Override
public char getSymbol() {
return 'I';
}
@Override
public double getLength() {
return 31556952000.0; // 1000.0 * 365.2425 * 86400.0
}
},
/** Time unit "centuries" (symbol C) */
/*[deutsch] Zeiteinheit "Jahrhunderte" (Symbol C) */
CENTURIES() {
@Override
public char getSymbol() {
return 'C';
}
@Override
public double getLength() {
return 3155695200.0; // 100.0 * 365.2425 * 86400.0
}
},
/** Time unit "decades" (symbol E) */
/*[deutsch] Zeiteinheit "Jahrzehnte" (Symbol E) */
DECADES() {
@Override
public char getSymbol() {
return 'E';
}
@Override
public double getLength() {
return 315569520.0; // 10.0 * 365.2425 * 86400.0
}
},
/** Time unit "calendar years" (symbol Y) */
/*[deutsch] Zeiteinheit "Jahre" (Symbol Y) */
YEARS() {
@Override
public char getSymbol() {
return 'Y';
}
@Override
public double getLength() {
return 31556952.0; // 365.2425 * 86400.0
}
},
/** Time unit "quarter years" (symbol Q) */
/*[deutsch] Zeiteinheit "Quartale" (Symbol Q) */
QUARTERS() {
@Override
public char getSymbol() {
return 'Q';
}
@Override
public double getLength() {
return 7889238.0; // 365.2425 * 86400.0 / 4.0
}
},
/** Time unit "months" (symbol M) */
/*[deutsch] Zeiteinheit "Monate" (Symbol M) */
MONTHS() {
@Override
public char getSymbol() {
return 'M';
}
@Override
public double getLength() {
return 2629746.0; // 365.2425 * 86400.0 / 12.0
}
},
/** Time unit "weeks" (symbol W) */
/*[deutsch] Zeiteinheit "Wochen" (Symbol W) */
WEEKS() {
@Override
public char getSymbol() {
return 'W';
}
@Override
public double getLength() {
return 604800.0; // 86400.0 * 7
}
},
/** Time unit "days" (symbol D) */
/*[deutsch] Zeiteinheit "Tage" (Symbol D) */
DAYS() {
@Override
public char getSymbol() {
return 'D';
}
@Override
public double getLength() {
return 86400.0;
}
};
//~ Instanzvariablen --------------------------------------------------
private final IsoDateUnit eof =
new OverflowUnit(this, OverflowUnit.POLICY_END_OF_MONTH);
private final IsoDateUnit kld =
new OverflowUnit(this, OverflowUnit.POLICY_KEEPING_LAST_DATE);
private final IsoDateUnit ui =
new OverflowUnit(this, OverflowUnit.POLICY_UNLESS_INVALID);
private final IsoDateUnit nvd =
new OverflowUnit(this, OverflowUnit.POLICY_NEXT_VALID_DATE);
private final IsoDateUnit co =
new OverflowUnit(this, OverflowUnit.POLICY_CARRY_OVER);
//~ Methoden ----------------------------------------------------------
/**
* Calculates the temporal distance between given calendar dates
* in this calendar unit.
*
* @param generic type of calendar date
* @param start starting date
* @param end ending date
* @return duration as count of this unit
*/
/*[deutsch]
* Ermittelt den zeitlichen Abstand zwischen den angegebenen
* Datumsangaben gemessen in dieser Einheit.
*
* @param generic type of calendar date
* @param start starting date
* @param end ending date
* @return duration as count of this unit
*/
public > long between(
T start,
T end
) {
return start.until(end, this);
}
/**
* A calendar unit is always calendrical.
*
* @return {@code true}
*/
/*[deutsch]
* Eine Datumseinheit ist immer kalendarisch.
*
* @return {@code true}
*/
@Override
public boolean isCalendrical() {
return true;
}
/**
* Defines a variation of this unit which resolves invalid intermediate
* dates in additions and subtractions to the first valid date after
* (the first day of following month).
*
* Example for months:
*
*
* PlainDate date = PlainDate.of(2013, 1, 31);
* System.out.println(date.plus(1, MONTHS.nextValidDate()));
* // Output: 2013-03-01
*
*
* Note: The metric for calculation of temporal distances remains
* unaffected.
*
* @return calendar unit with modified addition behaviour, but still
* the same metric
*/
/*[deutsch]
* Definiert eine Variante dieser Zeiteinheit, in der bei Additionen
* und Subtraktionen ungültige Datumswerte zum ersten Tag des
* Folgemonats aufgelöst werden.
*
* Beispiel für Monate:
*
*
* PlainDate date = PlainDate.of(2013, 1, 31);
* System.out.println(date.plus(1, MONTHS.nextValidDate()));
* // Ausgabe: 2013-03-01
*
*
* Notiz: Die Metrik zur Berechnung von Zeitabständen bleibt
* unverändert erhalten.
*
* @return calendar unit with modified addition behaviour, but still
* the same metric
*/
public IsoDateUnit nextValidDate() {
switch (this) {
case WEEKS:
case DAYS:
return this;
default:
return this.nvd;
}
}
/**
* Defines a variation of this unit which resolves invalid intermediate
* dates in additions and subtractions by transferring any day overflow
* to the following month.
*
* Example for months:
*
*
* PlainDate date = PlainDate.of(2013, 1, 31);
* System.out.println(date.plus(1, MONTHS.withCarryOver()));
* // Output: 2013-03-03
*
*
* Note: The metric for calculation of temporal distances remains
* unaffected.
*
* @return calendar unit with modified addition behaviour, but still
* the same metric
*/
/*[deutsch]
* Definiert eine Variante dieser Zeiteinheit, in der bei Additionen
* und Subtraktionen ein Überlauf auf den Folgemonat übertragen
* wird.
*
* Beispiel für Monate:
*
*
* PlainDate date = PlainDate.of(2013, 1, 31);
* System.out.println(date.plus(1, MONTHS.withCarryOver()));
* // Ausgabe: 2013-03-03
*
*
* Notiz: Die Metrik zur Berechnung von Zeitabständen bleibt
* unverändert erhalten.
*
* @return calendar unit with modified addition behaviour, but still
* the same metric
*/
public IsoDateUnit withCarryOver() {
switch (this) {
case WEEKS:
case DAYS:
return this;
default:
return this.co;
}
}
/**
* Defines a variation of this unit which handles invalid
* intermediate dates in additions and subtractions by throwing
* a {@code ChronoException}.
*
* Example for months:
*
*
* PlainDate date = PlainDate.of(2013, 1, 31);
* System.out.println(date.plus(1, MONTHS.unlessInvalid()));
* // February 31th does not exist => throws ChronoException
*
*
* Note: The metric for calculation of temporal distances remains
* unaffected.
*
* @return calendar unit with modified addition behaviour, but still
* the same metric
*/
/*[deutsch]
* Definiert eine Variante dieser Zeiteinheit, in der bei Additionen
* und Subtraktionen ungültige Datumswerte nicht korrigiert, sondern
* mit einer Ausnahme vom Typ {@code ChronoException} quittiert werden.
*
* Beispiel für Monate:
*
*
* PlainDate date = PlainDate.of(2013, 1, 31);
* System.out.println(date.plus(1, MONTHS.unlessInvalid()));
* // 31. Februar nicht vorhanden => throws ChronoException
*
*
* Notiz: Die Metrik zur Berechnung von Zeitabständen bleibt
* unverändert erhalten.
*
* @return calendar unit with modified addition behaviour, but still
* the same metric
*/
public IsoDateUnit unlessInvalid() {
switch (this) {
case WEEKS:
case DAYS:
return this;
default:
return this.ui;
}
}
/**
* Defines a variation of this unit which always sets the resulting
* date in additions and subtractions to the end of month even if there
* is no day overflow.
*
* Example for months:
*
*
* PlainDate date1 = PlainDate.of(2013, 2, 27);
* System.out.println(date1.plus(2, MONTHS.atEndOfMonth()));
* // Ausgabe: 2013-04-30
* PlainDate date2 = PlainDate.of(2013, 2, 28);
* System.out.println(date2.plus(2, MONTHS.atEndOfMonth()));
* // Ausgabe: 2013-04-30
*
*
* Note: The metric for calculation of temporal distances remains
* unaffected. An alternative which only jumps to the end of month
* if the original date is the last day of month can be achieved
* by {@link #keepingEndOfMonth()}.
*
* @return calendar unit with modified addition behaviour, but still
* the same metric
*/
/*[deutsch]
* Definiert eine Variante dieser Zeiteinheit, in der bei Additionen
* und Subtraktionen grundsätzlich der letzte Tag des Monats gesetzt
* wird, selbst wenn kein Überlauf vorliegt.
*
* Beispiel für Monate:
*
*
* PlainDate date1 = PlainDate.of(2013, 2, 27);
* System.out.println(date1.plus(2, MONTHS.atEndOfMonth()));
* // Ausgabe: 2013-04-30
* PlainDate date2 = PlainDate.of(2013, 2, 28);
* System.out.println(date2.plus(2, MONTHS.atEndOfMonth()));
* // Ausgabe: 2013-04-30
*
*
* Notiz: Die Metrik zur Berechnung von Zeitabständen bleibt
* unverändert erhalten. Eine Alternative, die nur dann zum Ende
* des Monats springt, wenn das aktuelle Datum der letzte Tag des Monats
* ist, ist mittels {@link #keepingEndOfMonth()} erhältlich.
*
* @return calendar unit with modified addition behaviour, but still
* the same metric
*/
public IsoDateUnit atEndOfMonth() {
return this.eof;
}
/**
* Defines a variation of this unit which always sets the resulting
* date in additions and subtractions to the end of month if the original
* date is the last day of month.
*
* Example for months:
*
*
* PlainDate date1 = PlainDate.of(2013, 2, 27);
* System.out.println(date1.plus(2, MONTHS.keepingEndOfMonth()));
* // Ausgabe: 2013-04-27
* PlainDate date2 = PlainDate.of(2013, 2, 28);
* System.out.println(date2.plus(2, MONTHS.keepingEndOfMonth()));
* // Ausgabe: 2013-04-30
*
*
* Note: The metric for calculation of temporal distances remains
* unaffected. An alternative which unconditionally jumps to the end
* of month can be achieved by {@link #atEndOfMonth()}.
*
* @return calendar unit with modified addition behaviour, but still
* the same metric
* @since 2.3
*/
/*[deutsch]
* Definiert eine Variante dieser Zeiteinheit, in der bei Additionen
* und Subtraktionen grundsätzlich der letzte Tag des Monats gesetzt
* wird, wenn das Ausgangsdatum bereits der letzte Tag des Monats ist.
*
* Beispiel für Monate:
*
*
* PlainDate date1 = PlainDate.of(2013, 2, 27);
* System.out.println(date1.plus(2, MONTHS.keepingEndOfMonth()));
* // Ausgabe: 2013-04-27
* PlainDate date2 = PlainDate.of(2013, 2, 28);
* System.out.println(date2.plus(2, MONTHS.keepingEndOfMonth()));
* // Ausgabe: 2013-04-30
*
*
* Notiz: Die Metrik zur Berechnung von Zeitabständen bleibt
* unverändert erhalten. Eine Alternative, die bedingungslos zum
* Ende des Monats springt, ist mittels {@link #atEndOfMonth()}
* erhältlich.
*
* @return calendar unit with modified addition behaviour, but still
* the same metric
* @since 2.3
*/
public IsoDateUnit keepingEndOfMonth() {
return this.kld;
}
/**
* Defines a special calendar unit for week-based years which are
* not bound to the calendar year but to the week cycle of a year
* preserving the day of week and (if possible) the week of year.
*
* Note: If the week of year is originally 53, but there is no such
* value after addition or subtraction then the week of year will be
* reduced to value 52.
*
*
* PlainDate start = PlainDate.of(2000, 2, 29); // 2000-W09-2
* System.out.println(start.plus(14, CalendarUnit.weekBasedYears()));
* // Output: 2014-02-25 (= 2014-W09-2)
*
*
* @return calendar unit for week-based years
* @see Weekcycle#YEARS
*/
/*[deutsch]
* Definiert eine spezielle Zeiteinheit für wochenbasierte Jahre,
* die an den Wochen-Zyklus gebunden sind.
*
* Notiz: Wenn die Kalenderwoche ursprünglich den Wert 53 hat,
* aber nach einer Addition oder Subtraktion dieser Wert nicht möglich
* ist, dann wird die Kalenderwoche auf den Wert 52 reduziert.
*
*
* PlainDate start = PlainDate.of(2000, 2, 29); // 2000-W09-2
* System.out.println(start.plus(14, CalendarUnit.weekBasedYears()));
* // Ausgabe: 2014-02-25 (= 2014-W09-2)
*
*
* @return calendar unit for week-based years
* @see Weekcycle#YEARS
*/
public static IsoDateUnit weekBasedYears() {
return Weekcycle.YEARS;
}
//~ Innere Klassen ----------------------------------------------------
/**
* Realisiert die Zeitarithmetik für eine kalendarische
* Zeiteinheit.
*
* @param generic type of calendrical context
*/
static class Rule>
implements UnitRule {
//~ Instanzvariablen ----------------------------------------------
private final CalendarUnit unit;
private final int policy;
//~ Konstruktoren -------------------------------------------------
/**
* Constructs a new rule for a calendar unit using the policy
* {@code OverflowUnit.POLICY_PREVIOUS_VALID_DATE}.
*
* @param unit calendar unit
*/
Rule(CalendarUnit unit) {
this(unit, OverflowUnit.POLICY_PREVIOUS_VALID_DATE);
}
/**
* Constructs a new rule for a calendar unit using the given
* policy.
*
* @param unit calendar unit as delegate
* @param policy strategy for handling day overflow
*/
Rule(
CalendarUnit unit,
int policy
) {
super();
this.unit = unit;
this.policy = policy;
}
//~ Methoden ------------------------------------------------------
@Override
public T addTo(
T context,
long amount
) {
PlainDate date = context.get(PlainDate.CALENDAR_DATE);
date = PlainDate.doAdd(this.unit, date, amount, this.policy);
return context.with(PlainDate.CALENDAR_DATE, date);
}
@Override
public long between(T start, T end) {
PlainDate d1 = start.get(PlainDate.CALENDAR_DATE);
PlainDate d2 = end.get(PlainDate.CALENDAR_DATE);
long amount;
switch (this.unit) {
case MILLENNIA:
amount = monthDelta(d1, d2) / 12000;
break;
case CENTURIES:
amount = monthDelta(d1, d2) / 1200;
break;
case DECADES:
amount = monthDelta(d1, d2) / 120;
break;
case YEARS:
amount = monthDelta(d1, d2) / 12;
break;
case QUARTERS:
amount = monthDelta(d1, d2) / 3;
break;
case MONTHS:
amount = monthDelta(d1, d2);
break;
case WEEKS:
amount = dayDelta(d1, d2) / 7;
break;
case DAYS:
amount = dayDelta(d1, d2);
break;
default:
throw new UnsupportedOperationException(this.unit.name());
}
if (
(amount != 0)
&& start.contains(PlainTime.WALL_TIME)
&& end.contains(PlainTime.WALL_TIME)
) {
boolean needsTimeCorrection;
if (this.unit == DAYS) {
needsTimeCorrection = true;
} else {
PlainDate d = d1.plus(amount, this.unit);
needsTimeCorrection = (d.compareByTime(d2) == 0);
}
if (needsTimeCorrection) {
PlainTime t1 = start.get(PlainTime.WALL_TIME);
PlainTime t2 = end.get(PlainTime.WALL_TIME);
if ((amount > 0) && t1.isAfter(t2)) {
amount--;
} else if ((amount < 0) && t1.isBefore(t2)) {
amount++;
}
}
}
return amount;
}
private static long monthDelta(
PlainDate start,
PlainDate end
) {
long result = (end.getEpochMonths() - start.getEpochMonths());
if (
(result > 0)
&& (end.getDayOfMonth() < start.getDayOfMonth())
) {
result--;
} else if (
(result < 0)
&& (end.getDayOfMonth() > start.getDayOfMonth())
) {
result++;
}
return result;
}
private static long dayDelta(
PlainDate start,
PlainDate end
) {
if (start.getYear() == end.getYear()) {
return end.getDayOfYear() - start.getDayOfYear();
}
return end.getDaysSinceUTC() - start.getDaysSinceUTC();
}
}
}