/*
* -----------------------------------------------------------------------
* Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/>
* -----------------------------------------------------------------------
* This file (CalendarYear.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.range;
import net.time4j.CalendarUnit;
import net.time4j.Moment;
import net.time4j.Month;
import net.time4j.PlainDate;
import net.time4j.Quarter;
import net.time4j.SystemClock;
import net.time4j.base.GregorianMath;
import net.time4j.base.MathUtils;
import net.time4j.base.TimeSource;
import net.time4j.engine.AttributeQuery;
import net.time4j.engine.BridgeChronology;
import net.time4j.engine.ChronoElement;
import net.time4j.engine.ChronoEntity;
import net.time4j.engine.ChronoMerger;
import net.time4j.engine.Chronology;
import net.time4j.engine.Converter;
import net.time4j.engine.DisplayStyle;
import net.time4j.engine.FormattableElement;
import net.time4j.engine.IntElementRule;
import net.time4j.engine.ThreetenAdapter;
import net.time4j.engine.ValidationElement;
import net.time4j.format.Attributes;
import net.time4j.format.CalendarText;
import net.time4j.format.CalendarType;
import net.time4j.format.Leniency;
import net.time4j.format.LocalizedPatternSupport;
import net.time4j.tz.Timezone;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.time.Year;
import java.time.chrono.IsoChronology;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalQueries;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
/**
* <p>Represents a full gregorian calendar year as interval from 1st of January until end of December. </p>
*
* <p>The only element which is registered by this class is: </p>
*
* <ul>
* <li>{@link #YEAR}</li>
* </ul>
*
* <p>Note: The current calendar year can be determined by an expression like:
* {@code CalendarYear current = SystemClock.inLocalView().now(CalendarYear.chronology())}. </p>
*
* @author Meno Hochschild
* @since 3.21/4.17
* @doctags.concurrency {immutable}
*/
/*[deutsch]
* <p>Repräsentiert ein volles gregorianisches Kalenderjahr als Intervall vom ersten Januar bis Ende Dezember. </p>
*
* <p>Das einzige von dieser Klasse registrierte Element ist: </p>
*
* <ul>
* <li>{@link #YEAR}</li>
* </ul>
*
* <p>Hinweis: Das aktuelle Kalenderjahr kann mit einem Ausdruck wie folgt bestimmt werden:
* {@code CalendarYear current = SystemClock.inLocalView().now(CalendarYear.chronology())}. </p>
*
* @author Meno Hochschild
* @since 3.21/4.17
* @doctags.concurrency {immutable}
*/
@CalendarType("iso8601")
public final class CalendarYear
extends FixedCalendarInterval<CalendarYear>
implements ThreetenAdapter, LocalizedPatternSupport {
//~ Statische Felder/Initialisierungen --------------------------------
/**
* <p>Element with the proleptic iso-year without any era reference and
* the value range {@code -999999999} until {@code 999999999}. </p>
*
* <p>The term "proleptic" means that the rules of the gregorian
* calendar and the associated way of year counting is applied backward
* even before the introduction of gregorian calendar. The year {@code 0}
* is permitted - and negative years, too. For historic year numbers,
* this mathematical extrapolation is not recommended and usually
* wrong. </p>
*
* <p>Format pattern symbol (for CLDR standard) can be either "u"
* or "y". Format example for Japanese: </p>
*
* <pre>
* ChronoFormatter<CalendarYear> f =
* ChronoFormatter.ofStyle(DisplayMode.FULL, Locale.JAPANESE, CalendarYear.chronology());
* CalendarYear cyear = CalendarYear.of(2016);
* System.out.println(f.format(cyear)); // 2016年
* </pre>
*/
/*[deutsch]
* <p>Element mit dem proleptischen ISO-Jahr ohne Ära-Bezug mit dem
* Wertebereich {@code -999999999} bis {@code 999999999}. </p>
*
* <p>Der Begriff "proleptisch" bedeutet, daß die Regeln
* des gregorianischen Kalenders und damit verbunden die Jahreszählung
* auch rückwirkend vor der Einführung des Kalenders angewandt
* werden. Insbesondere ist auch das Jahr {@code 0} zugelassen - nebst
* negativen Jahreszahlen. Für historische Jahreszahlen ist diese
* mathematische Extrapolation nicht geeignet. </p>
*
* <p>Formatmustersymbol (für den CLDR-Standard) kann entweder "u"
* oder "y" sein. Formatbeispiel für Japanisch: </p>
*
* <pre>
* ChronoFormatter<CalendarYear> f =
* ChronoFormatter.ofStyle(DisplayMode.FULL, Locale.JAPANESE, CalendarYear.chronology());
* CalendarYear cyear = CalendarYear.of(2016);
* System.out.println(f.format(cyear)); // 2016年
* </pre>
*/
@FormattableElement(format = "u")
public static final ChronoElement<Integer> YEAR = PlainDate.YEAR;
private static final Chronology<CalendarYear> ENGINE =
Chronology.Builder
.setUp(CalendarYear.class, new Merger())
.appendElement(YEAR, new YearRule())
.build();
private static final Chronology<Year> THREETEN;
static {
Converter<Year, CalendarYear> converter =
new Converter<Year, CalendarYear>() {
@Override
public CalendarYear translate(Year source) {
return CalendarYear.of(source.getValue());
}
@Override
public Year from(CalendarYear time4j) {
return Year.of(time4j.year);
}
@Override
public Class<Year> getSourceType() {
return Year.class;
}
};
THREETEN = new BridgeChronology<>(converter, ENGINE);
}
private static final long serialVersionUID = 2151327270599436439L;
//~ Instanzvariablen --------------------------------------------------
private transient final int year;
private transient final Boundary<PlainDate> start;
private transient final Boundary<PlainDate> end;
//~ Konstruktoren -----------------------------------------------------
private CalendarYear(int year) {
super();
if ((year < GregorianMath.MIN_YEAR) || (year > GregorianMath.MAX_YEAR)) {
throw new IllegalArgumentException("Year out of bounds: " + year);
}
this.year = year;
this.start = Boundary.ofClosed(PlainDate.of(this.year, 1, 1));
this.end = Boundary.ofClosed(PlainDate.of(this.year, 12, 31));
}
//~ Methoden ----------------------------------------------------------
/**
* <p>Creates a new instance based on given gregorian calendar year. </p>
*
* @param year gregorian year within range {@code -999,999,999 / +999,999,999}
* @return new instance
* @throws IllegalArgumentException if given argument is out of range
*/
/*[deutsch]
* <p>Erzeugt eine neue Instanz, die auf dem angegebenen gregorianischen Kalendarjahr fußt. </p>
*
* @param year gregorian year within range {@code -999,999,999 / +999,999,999}
* @return new instance
* @throws IllegalArgumentException if given argument is out of range
*/
public static CalendarYear of(int year) {
return new CalendarYear(year);
}
/**
* <p>Obtains the current calendar year in system time. </p>
*
* <p>Convenient short-cut for: {@code SystemClock.inLocalView().now(CalendarYear.chronology())}. </p>
*
* @return current calendar year in system time zone using the system clock
* @see SystemClock#inLocalView()
* @see net.time4j.ZonalClock#now(net.time4j.engine.Chronology)
* @since 3.24/4.20
*/
/*[deutsch]
* <p>Ermittelt das aktuelle Kalenderjahr in der Systemzeit. </p>
*
* <p>Bequeme Abkürzung für: {@code SystemClock.inLocalView().now(CalendarYear.chronology())}. </p>
*
* @return current calendar year in system time zone using the system clock
* @see SystemClock#inLocalView()
* @see net.time4j.ZonalClock#now(net.time4j.engine.Chronology)
* @since 3.24/4.20
*/
public static CalendarYear nowInSystemTime() {
return SystemClock.inLocalView().now(CalendarYear.chronology());
}
/**
* <p>Combines this year with given quarter year to a calendar quarter. </p>
*
* @param quarter quarter year
* @return calendar quarter
*/
/*[deutsch]
* <p>Kombiniert diese Instanz mit dem angegebenen Quartal zu einem Kalenderquartal. </p>
*
* @param quarter quarter year
* @return calendar quarter
*/
public CalendarQuarter at(Quarter quarter) {
return CalendarQuarter.of(this.year, quarter);
}
/**
* <p>Combines this year with given month to a calendar month. </p>
*
* @param month gregorian month
* @return calendar month
*/
/*[deutsch]
* <p>Kombiniert diese Instanz mit dem angegebenen Monat zu einem Kalendermonat. </p>
*
* @param month gregorian month
* @return calendar month
*/
public CalendarMonth at(Month month) {
return CalendarMonth.of(this.year, month);
}
/**
* <p>Combines this year with given month to a calendar month. </p>
*
* @param month gregorian month in range 1-12
* @return calendar month
* @throws IllegalArgumentException if the month is out of range
*/
/*[deutsch]
* <p>Kombiniert diese Instanz mit dem angegebenen Monat zu einem Kalendermonat. </p>
*
* @param month gregorian month in range 1-12
* @return calendar month
* @throws IllegalArgumentException if the month is out of range
*/
public CalendarMonth atMonth(int month) {
return CalendarMonth.of(this.year, month);
}
/**
* <p>Combines this year with given day of year to a calendar date. </p>
*
* @param dayOfYear day of year in range 1-365/366
* @return calendar date
* @throws IllegalArgumentException if the day-of-year is out of range
*/
/*[deutsch]
* <p>Kombiniert dieses Jahr mit dem angegebenen Jahrestag zu einem Kalenderdatum. </p>
*
* @param dayOfYear day of year in range 1-365/366
* @return calendar date
* @throws IllegalArgumentException if the day-of-year is out of range
*/
public PlainDate atDayOfYear(int dayOfYear) {
return ((dayOfYear == 1) ? this.start.getTemporal() : PlainDate.of(this.year, dayOfYear));
}
/**
* <p>Yields the year number. </p>
*
* @return int
*/
/*[deutsch]
* <p>Liefert die Jahreszahl. </p>
*
* @return int
*/
public int getValue() {
return this.year;
}
@Override
public Boundary<PlainDate> getStart() {
return this.start;
}
@Override
public Boundary<PlainDate> getEnd() {
return this.end;
}
@Override
public boolean contains(PlainDate temporal) {
return (temporal.getYear() == this.year);
}
@Override
public boolean isAfter(PlainDate temporal) {
return (temporal.getYear() < this.year);
}
@Override
public boolean isBefore(PlainDate temporal) {
return (temporal.getYear() > this.year);
}
/**
* <p>Determines if this calendar year is a leap year with 366 days. </p>
*
* @return {@code true} if it is a leap year else {@code false}
* @see GregorianMath#isLeapYear(int)
*/
/*[deutsch]
* <p>Ermittelt, ob dieses Kalenderjahr ein Schaltjahr mit 366 Tagen ist. </p>
*
* @return {@code true} if it is a leap year else {@code false}
* @see GregorianMath#isLeapYear(int)
*/
public boolean isLeap() {
return GregorianMath.isLeapYear(this.year);
}
/**
* <p>Determines the count of days belonging to this year. </p>
*
* @return int
* @see #isLeap()
*/
/**
* <p>Ermittelt die Anzahl der Tage, die zu diesem Kalenderjahr gehören. </p>
*
* @return int
* @see #isLeap()
*/
public int length() {
return (this.isLeap() ? 366 : 365);
}
/**
* <p>Converts given JSR-310-type to a calendar year. </p>
*
* @param year Threeten-equivalent of this instance
* @return CalendarYear
* @see #toTemporalAccessor()
*/
/*[deutsch]
* <p>Konvertiert den angegebenen JSR-310-Typ zu einem Kalenderjahr. </p>
*
* @param year Threeten-equivalent of this instance
* @return CalendarYear
* @see #toTemporalAccessor()
*/
public static CalendarYear from(Year year) {
return CalendarYear.of(year.getValue());
}
/**
* <p>Adds given years to this year. </p>
*
* @param years the count of years to be added
* @return result of addition
*/
/*[deutsch]
* <p>Addiert die angegebenen Jahre zu diesem Kalenderjahr. </p>
*
* @param years the count of years to be added
* @return result of addition
*/
public CalendarYear plus(Years<CalendarUnit> years) {
if (years.isEmpty()) {
return this;
}
return CalendarYear.of(MathUtils.safeAdd(this.year, years.getAmount()));
}
/**
* <p>Subtracts given years from this year. </p>
*
* @param years the count of years to be subtracted
* @return result of subtraction
*/
/*[deutsch]
* <p>Subtrahiert die angegebenen Jahre von diesem Kalenderjahr. </p>
*
* @param years the count of years to be subtracted
* @return result of subtraction
*/
public CalendarYear minus(Years<CalendarUnit> years) {
if (years.isEmpty()) {
return this;
}
return CalendarYear.of(MathUtils.safeSubtract(this.year, years.getAmount()));
}
@Override
public int compareTo(CalendarYear other) {
return (this.year - other.year); // safe
}
/**
* <p>Iterates over all days of this year. </p>
*
* @return Iterator
*/
/*[deutsch]
* <p>Iteratiert über alle Tage dieses Jahres. </p>
*
* @return Iterator
*/
@Override
public Iterator<PlainDate> iterator() {
return new Iter();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (obj instanceof CalendarYear) {
CalendarYear that = (CalendarYear) obj;
return (this.year == that.year);
} else {
return false;
}
}
@Override
public int hashCode() {
return this.year;
}
/**
* <p>Outputs this year number as a String in CLDR-format "uuuu". </p>
*
* @return String
*/
/*[deutsch]
* <p>Gibt diese Jahreszahl als String im CLDR-Format "uuuu" aus. </p>
*
* @return String
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
formatYear(sb, this.year);
return sb.toString();
}
@Override
public Year toTemporalAccessor() {
return Year.of(this.year);
}
/**
* <p>Yields the associated chronology. </p>
*
* @return the underlying rule engine
*/
/*[deutsch]
* <p>Liefert die assoziierte Chronologie. </p>
*
* @return the underlying rule engine
*/
public static Chronology<CalendarYear> chronology() {
return ENGINE;
}
/**
* <p>Obtains a bridge chronology for the type {@code java.time.Year}. </p>
*
* @return rule engine adapted for the type {@code java.time.Year}
* @see #chronology()
*/
/*[deutsch]
* <p>Liefert eine an den Typ {@code java.time.Year} angepasste Chronologie. </p>
*
* @return rule engine adapted for the type {@code java.time.Year}
* @see #chronology()
*/
public static Chronology<Year> threeten() {
return THREETEN;
}
@Override
protected Chronology<CalendarYear> getChronology() {
return ENGINE;
}
@Override
protected CalendarYear getContext() {
return this;
}
/**
* @serialData Uses <a href="../../../serialized-form.html#net.time4j.range.SPX">
* a dedicated serialization form</a> as proxy. The format
* is bit-compressed. The first byte contains in the six
* most significant bits the type-ID {@code 36}. Then the year number
* is written as int-primitive.
*
* Schematic algorithm:
*
* <pre>
* int header = 36;
* header <<= 2;
* out.writeByte(header);
* out.writeInt(getValue());
* </pre>
*
* @return replacement object in serialization graph
*/
private Object writeReplace() {
return new SPX(this, SPX.YEAR_TYPE);
}
/**
* @serialData Blocks because a serialization proxy is required.
* @param in object input stream
* @throws InvalidObjectException (always)
*/
private void readObject(ObjectInputStream in)
throws IOException {
throw new InvalidObjectException("Serialization proxy required.");
}
//~ Innere Klassen ----------------------------------------------------
private static class Merger
implements ChronoMerger<CalendarYear> {
//~ Methoden ------------------------------------------------------
@Override
public CalendarYear createFrom(
TimeSource<?> clock,
AttributeQuery attributes
) {
Timezone zone;
if (attributes.contains(Attributes.TIMEZONE_ID)) {
zone = Timezone.of(attributes.get(Attributes.TIMEZONE_ID));
} else if (attributes.get(Attributes.LENIENCY, Leniency.SMART).isLax()) {
zone = Timezone.ofSystem();
} else {
return null;
}
int y = Moment.from(clock.currentTime()).toZonalTimestamp(zone.getID()).getYear();
return CalendarYear.of(y);
}
@Override
public CalendarYear createFrom(
ChronoEntity<?> entity,
AttributeQuery attributes,
boolean preparsing
) {
boolean lenient = attributes.get(Attributes.LENIENCY, Leniency.SMART).isLax();
return this.createFrom(entity, attributes, lenient, preparsing);
}
@Override
public CalendarYear createFrom(
ChronoEntity<?> entity,
AttributeQuery attributes,
boolean lenient,
boolean preparsing
) {
int y = entity.getInt(YEAR);
if ((y >= GregorianMath.MIN_YEAR) && (y <= GregorianMath.MAX_YEAR)) {
return CalendarYear.of(y);
} else if (y > Integer.MIN_VALUE) {
entity.with(ValidationElement.ERROR_MESSAGE, "Year out of bounds: " + y);
}
return null;
}
@Override
public String getFormatPattern(
DisplayStyle style,
Locale locale
) {
Map<String, String> map = CalendarText.getIsoInstance(locale).getTextForms();
String key = "F_y";
return (map.containsKey(key) ? map.get(key) : "uuuu");
}
@Override
public CalendarYear createFrom(
TemporalAccessor threeten,
AttributeQuery attributes
) {
if (threeten.query(TemporalQueries.chronology()) == IsoChronology.INSTANCE) {
if (threeten.isSupported(ChronoField.YEAR)) {
int year = threeten.get(ChronoField.YEAR);
return CalendarYear.of(year);
}
}
return null;
}
}
private static class YearRule
implements IntElementRule<CalendarYear> {
//~ Methoden ------------------------------------------------------
@Override
public Integer getValue(CalendarYear context) {
return Integer.valueOf(context.year);
}
@Override
public Integer getMinimum(CalendarYear context) {
return Integer.valueOf(GregorianMath.MIN_YEAR);
}
@Override
public Integer getMaximum(CalendarYear context) {
return Integer.valueOf(GregorianMath.MAX_YEAR);
}
@Override
public boolean isValid(
CalendarYear context,
Integer value
) {
if (value == null) {
return false;
}
int v = value.intValue();
return ((v >= GregorianMath.MIN_YEAR) && (v <= GregorianMath.MAX_YEAR));
}
@Override
public CalendarYear withValue(
CalendarYear context,
Integer value,
boolean lenient
) {
if (this.isValid(context, value)) {
return CalendarYear.of(value.intValue());
} else {
throw new IllegalArgumentException("Not valid: " + value);
}
}
@Override
public ChronoElement<?> getChildAtFloor(CalendarYear context) {
return null;
}
@Override
public ChronoElement<?> getChildAtCeiling(CalendarYear context) {
return null;
}
@Override
public int getInt(CalendarYear context) {
return context.year;
}
@Override
public boolean isValid(
CalendarYear context,
int value
) {
return ((value >= GregorianMath.MIN_YEAR) && (value <= GregorianMath.MAX_YEAR));
}
@Override
public CalendarYear withValue(
CalendarYear context,
int value,
boolean lenient
) {
if (this.isValid(context, value)) {
return CalendarYear.of(value);
} else {
throw new IllegalArgumentException("Not valid: " + value);
}
}
}
private class Iter
implements Iterator<PlainDate> {
//~ Instanzvariablen ----------------------------------------------
private PlainDate current = CalendarYear.this.start.getTemporal();
//~ Methoden ------------------------------------------------------
@Override
public boolean hasNext() {
return (this.current != null);
}
@Override
public PlainDate next() {
if (this.current == null) {
throw new NoSuchElementException();
} else {
PlainDate result = this.current;
PlainDate next = result.plus(1, CalendarUnit.DAYS);
this.current = ((next.getYear() == CalendarYear.this.year) ? next : null);
return result;
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
}