/*
* -----------------------------------------------------------------------
* Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/>
* -----------------------------------------------------------------------
* This file (HistoricDate.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.history;
import net.time4j.base.GregorianMath;
/**
* <p>Defines a historic date which consists of era, year of era, month and day of month. </p>
*
* <p>Instances of this class are agnostic of any specific new-year-strategy other than the
* {@link NewYearRule#BEGIN_OF_JANUARY default (first of January)} but can use such a strategy
* as parameter to determine {@link #getYearOfEra(NewYearStrategy) the real historic year}
* for display purposes. </p>
*
* <p>The natural order is based on the timeline order. </p>
*
* @author Meno Hochschild
* @since 3.0
* @doctags.concurrency {immutable}
*/
/*[deutsch]
* <p>Definiert ein historisches Datum, das aus Ära, Jahr der Ära, Monat und Tag
* des Monats besteht. </p>
*
* <p>Instanzen dieser Klasse kennen selber keine andere Neujahrsstrategie als den
* {@link NewYearRule#BEGIN_OF_JANUARY Standard (erster Januar)}, können aber eine
* solche Strategie als Parameter nutzen, um {@link #getYearOfEra(NewYearStrategy)
* das reale historische Jahr} für Anzeigezwecke zu bestimmen. </p>
*
* <p>Die natürliche Ordnung basiert auf der zeitlichen Reihenfolge. </p>
*
* @author Meno Hochschild
* @since 3.0
* @doctags.concurrency {immutable}
*/
public final class HistoricDate
implements Comparable<HistoricDate> {
//~ Instanzvariablen --------------------------------------------------
private final HistoricEra era;
private final int yearOfEra;
private final int month;
private final int dom;
//~ Konstruktoren -----------------------------------------------------
// package-private
HistoricDate(
HistoricEra era,
int yearOfEra,
int month,
int dom
) {
super();
this.era = era;
this.yearOfEra = yearOfEra;
this.month = month;
this.dom = dom;
}
//~ Methoden ----------------------------------------------------------
/**
* <p>Equivalent to {@code HistoricDate.of(era, yearOfEra, month, dom,
* YearDefinition.DUAL_DATING, NewYearRule.BEGIN_OF_JANUARY.until(Integer.MAX_VALUE))}. </p>
*
* @param era historic era
* @param yearOfEra year of related era ({@code >= 1}) starting on January the first
* @param month historic month (1-12)
* @param dom historic day of month (1-31)
* @return new historic date (not yet validated)
* @throws IllegalArgumentException if any argument is out of required maximum range
* @since 3.0
* @see ChronoHistory#isValid(HistoricDate)
* @see GregorianMath#MIN_YEAR
* @see GregorianMath#MAX_YEAR
*/
/*[deutsch]
* <p>Äquivalent zu {@code HistoricDate.of(era, yearOfEra, month, dom,
* YearDefinition.DUAL_DATING, NewYearRule.BEGIN_OF_JANUARY.until(Integer.MAX_VALUE))}. </p>
*
* @param era historic era
* @param yearOfEra year of related era ({@code >= 1}) starting on January the first
* @param month historic month (1-12)
* @param dom historic day of month (1-31)
* @return new historic date (not yet validated)
* @throws IllegalArgumentException if any argument is out of required maximum range
* @since 3.0
* @see ChronoHistory#isValid(HistoricDate)
* @see GregorianMath#MIN_YEAR
* @see GregorianMath#MAX_YEAR
*/
public static HistoricDate of(
HistoricEra era,
int yearOfEra,
int month,
int dom
) {
return HistoricDate.of(era, yearOfEra, month, dom, YearDefinition.DUAL_DATING, NewYearStrategy.DEFAULT);
}
/**
* <p>Constructs a new tuple of given historic chronological components. </p>
*
* <p>Note: A detailed validation is not done. Such a validation is the responsibility
* of any {@code ChronoHistory}, however. The converted AD-year must not be beyond the
* defined range of the class {@code PlainDate}. </p>
*
* @param era historic era
* @param yearOfEra year of related era ({@code >= 1}) starting on January the first
* @param month historic month (1-12)
* @param dom historic day of month (1-31)
* @param yearDefinition defines a strategy how to interprete year of era
* @param newYearStrategy strategy how to determine New Year
* @return new historic date (not yet validated)
* @throws IllegalArgumentException if any argument is out of required maximum range or inconsistent
* @since 3.18/4.14
* @see ChronoHistory#isValid(HistoricDate)
* @see GregorianMath#MIN_YEAR
* @see GregorianMath#MAX_YEAR
*/
/*[deutsch]
* <p>Konstruiert ein neues Tupel aus den angegebenen historischen Zeitkomponenten. </p>
*
* <p>Hinweis: Eine detaillierte Validierung wird nicht gemacht. Das ist stattdessen Aufgabe
* der {@code ChronoHistory}. Das umgerechnete AD-Jahr darf nicht außerhalb des
* Definitionsbereichs der Klasse {@code PlainDate} liegen. </p>
*
* @param era historic era
* @param yearOfEra year of related era ({@code >= 1}) starting on January the first
* @param month historic month (1-12)
* @param dom historic day of month (1-31)
* @param yearDefinition defines a strategy how to interprete year of era
* @param newYearStrategy strategy how to determine New Year
* @return new historic date (not yet validated)
* @throws IllegalArgumentException if any argument is out of required maximum range or inconsistent
* @since 3.18/4.14
* @see ChronoHistory#isValid(HistoricDate)
* @see GregorianMath#MIN_YEAR
* @see GregorianMath#MAX_YEAR
*/
public static HistoricDate of(
HistoricEra era,
int yearOfEra,
int month,
int dom,
YearDefinition yearDefinition,
NewYearStrategy newYearStrategy
) {
if (era == null) {
throw new NullPointerException("Missing historic era.");
} else if (
(dom < 1 || dom > 31)
|| (month < 1 || month > 12)
|| (Math.abs(era.annoDomini(yearOfEra)) > GregorianMath.MAX_YEAR)
) {
throw new IllegalArgumentException(
"Out of range: " + toString(era, yearOfEra, month, dom));
} else if (era == HistoricEra.BYZANTINE) {
if ((yearOfEra < 0) || ((yearOfEra == 0) && (month < 9))) {
throw new IllegalArgumentException(
"Before creation of the world: " + toString(era, yearOfEra, month, dom));
}
} else if (yearOfEra < 1) {
throw new IllegalArgumentException(
"Year of era must be positive: " + toString(era, yearOfEra, month, dom));
}
if (!yearDefinition.equals(YearDefinition.DUAL_DATING)) {
// here we interprete yearOfEra as yearOfDisplay and have to translate it to standard calendar year
NewYearRule rule = newYearStrategy.rule(era, yearOfEra);
yearOfEra =
rule.standardYear(
(yearDefinition == YearDefinition.AFTER_NEW_YEAR), newYearStrategy, era, yearOfEra, month, dom);
}
return new HistoricDate(era, yearOfEra, month, dom);
}
/**
* <p>Yields the historic era. </p>
*
* @return HistoricEra
* @since 3.0
*/
/*[deutsch]
* <p>Liefert die historische Ära. </p>
*
* @return HistoricEra
* @since 3.0
*/
public HistoricEra getEra() {
return this.era;
}
/**
* <p>Yields the year of the historic era starting on January the first. </p>
*
* @return year of era ({@code >= 1})
* @see #getYearOfEra(NewYearStrategy)
* @see NewYearRule#BEGIN_OF_JANUARY
* @since 3.0
*/
/*[deutsch]
* <p>Liefert das Jahr der historischen Ära beginnend am ersten Tag des Januar. </p>
*
* @return year of era ({@code >= 1})
* @see #getYearOfEra(NewYearStrategy)
* @see NewYearRule#BEGIN_OF_JANUARY
* @since 3.0
*/
public int getYearOfEra() {
return this.yearOfEra;
}
/**
* <p>Yields the displayed historic year whose begin is determined by given new-year-strategy. </p>
*
* @param newYearStrategy strategy how to determine New Year
* @return displayed historic year (never negative)
* @see #getYearOfEra()
* @see NewYearRule
* @since 3.14/4.11
*/
/*[deutsch]
* <p>Liefert das historische Anzeigejahr, dessen Beginn von der angegebenen Neujahrsstrategie
* bestimmt wird. </p>
*
* @param newYearStrategy strategy how to determine New Year
* @return displayed historic year (never negative)
* @see #getYearOfEra()
* @see NewYearRule
* @since 3.14/4.11
*/
public int getYearOfEra(NewYearStrategy newYearStrategy) {
return newYearStrategy.displayedYear(this);
}
/**
* <p>Yields the historic month. </p>
*
* @return month (1-12)
* @since 3.0
*/
/*[deutsch]
* <p>Liefert den historischen Monat. </p>
*
* @return month (1-12)
* @since 3.0
*/
public int getMonth() {
return this.month;
}
/**
* <p>Yields the historic day of month. </p>
*
* @return day of month (1-31)
* @since 3.0
*/
/*[deutsch]
* <p>Liefert den historischen Tag des Monats. </p>
*
* @return day of month (1-31)
* @since 3.0
*/
public int getDayOfMonth() {
return this.dom;
}
@Override
public int compareTo(HistoricDate other) {
int ad1 = this.era.annoDomini(this.yearOfEra);
int ad2 = other.era.annoDomini(other.yearOfEra);
if (ad1 < ad2) {
return -1;
} else if (ad1 > ad2) {
return 1;
}
int delta = this.getMonth() - other.getMonth();
if (delta == 0) {
delta = this.getDayOfMonth() - other.getDayOfMonth();
}
return ((delta < 0) ? -1 : (delta > 0 ? 1 : 0));
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (obj instanceof HistoricDate) {
HistoricDate that = (HistoricDate) obj;
return (
(this.era == that.era)
&& (this.yearOfEra == that.yearOfEra)
&& (this.month == that.month)
&& (this.dom == that.dom)
);
} else {
return false;
}
}
@Override
public int hashCode() {
int hash = (this.yearOfEra * 1000) + this.month * 32 + this.dom;
return (this.era == HistoricEra.AD) ? hash : -hash;
}
@Override
public String toString() {
return toString(this.era, this.yearOfEra, this.month, this.dom);
}
private static String toString(
HistoricEra era,
int yearOfEra,
int month,
int dom
) {
StringBuilder sb = new StringBuilder();
sb.append(era);
sb.append('-');
String yoe = String.valueOf(yearOfEra);
for (int i = 4 - yoe.length(); i > 0; i--) {
sb.append('0');
}
sb.append(yoe);
sb.append('-');
if (month < 10) {
sb.append('0');
}
sb.append(month);
sb.append('-');
if (dom < 10) {
sb.append('0');
}
sb.append(dom);
return sb.toString();
}
}