/*
* Copyright (c) 2012, Stephen Colebourne & Michael Nascimento Santos
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of JSR-310 nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package java.time.chrono.global;
import java.io.Serializable;
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.calendrical.ChronoField;
import java.time.calendrical.DateTimeAccessor;
import java.time.calendrical.DateTimeValueRange;
import java.time.chrono.Chrono;
import java.time.chrono.ChronoLocalDate;
import java.time.chrono.Era;
import java.time.chrono.ISOChrono;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import sun.util.calendar.CalendarSystem;
import sun.util.calendar.LocalGregorianCalendar;
/**
* The Japanese Imperial calendar system.
* <p>
* This chronology defines the rules of the Japanese Imperial calendar system. This calendar system is
* primarily used in Japan. The Japanese Imperial calendar system is the same as the ISO calendar system apart
* from the era-based year numbering.
* <p>
* Only Meiji (1865-04-07 - 1868-09-07) and later eras are supported. Older eras are handled as an unknown era
* where the year-of-era is the ISO year.
*
* <h4>Implementation notes</h4> This class is immutable and thread-safe.
*/
public final class JapaneseChrono extends Chrono<JapaneseChrono> implements Serializable {
// TODO: definition for unknown era may break requirement that year-of-era >= 1
static final LocalGregorianCalendar JCAL = (LocalGregorianCalendar) CalendarSystem.forName("japanese");
// Locale for creating a JapaneseImpericalCalendar.
static final Locale LOCALE = new Locale("ja", "JP", "JP"); // Locale.forLanguageTag("ja-JP-u-ca-japanese");
/**
* Singleton instance for Japanese chronology.
*/
public static final JapaneseChrono INSTANCE = new JapaneseChrono();
/**
* The singleton instance for the before Meiji era ( - 1868-09-07) which has the value -999.
*/
public static final Era<JapaneseChrono> ERA_SEIREKI = JapaneseEra.SEIREKI;
/**
* The singleton instance for the Meiji era (1868-09-08 - 1912-07-29) which has the value -1.
*/
public static final Era<JapaneseChrono> ERA_MEIJI = JapaneseEra.MEIJI;
/**
* The singleton instance for the Taisho era (1912-07-30 - 1926-12-24) which has the value 0.
*/
public static final Era<JapaneseChrono> ERA_TAISHO = JapaneseEra.TAISHO;
/**
* The singleton instance for the Showa era (1926-12-25 - 1989-01-07) which has the value 1.
*/
public static final Era<JapaneseChrono> ERA_SHOWA = JapaneseEra.SHOWA;
/**
* The singleton instance for the Heisei era (1989-01-08 - current) which has the value 2.
*/
public static final Era<JapaneseChrono> ERA_HEISEI = JapaneseEra.HEISEI;
/**
* Serialization version.
*/
private static final long serialVersionUID = 459996390165777884L;
/**
* Narrow names for eras.
*/
private static final Map<String, String[]> ERA_NARROW_NAMES = new HashMap<String, String[]>();
/**
* Short names for eras.
*/
private static final Map<String, String[]> ERA_SHORT_NAMES = new HashMap<String, String[]>();
/**
* Full names for eras.
*/
private static final Map<String, String[]> ERA_FULL_NAMES = new HashMap<String, String[]>();
/**
* Fallback language for the era names.
*/
private static final String FALLBACK_LANGUAGE = "en";
/**
* Language that has the era names.
*/
private static final String TARGET_LANGUAGE = "ja";
/**
* Name data.
*/
// TODO: replace all the hard-coded Maps with locale resources
static {
ERA_NARROW_NAMES.put(FALLBACK_LANGUAGE, new String[] { "Unknown", "K", "M", "T", "S", "H" });
ERA_NARROW_NAMES.put(TARGET_LANGUAGE, new String[] { "Unknown", "K", "M", "T", "S", "H" });
ERA_SHORT_NAMES.put(FALLBACK_LANGUAGE, new String[] { "Unknown", "K", "M", "T", "S", "H" });
ERA_SHORT_NAMES.put(TARGET_LANGUAGE, new String[] { "Unknown", "\u6176", "\u660e", "\u5927", "\u662d", "\u5e73" });
ERA_FULL_NAMES.put(FALLBACK_LANGUAGE, new String[] { "Unknown", "Keio", "Meiji", "Taisho", "Showa", "Heisei" });
ERA_FULL_NAMES.put(TARGET_LANGUAGE, new String[] { "Unknown", "\u6176\u5fdc", "\u660e\u6cbb", "\u5927\u6b63",
"\u662d\u548c", "\u5e73\u6210" });
}
// -----------------------------------------------------------------------
/**
* Restricted constructor.
*/
private JapaneseChrono() {
}
/**
* Resolve singleton.
*
* @return the singleton instance, not null
*/
private Object readResolve() {
return INSTANCE;
}
// -----------------------------------------------------------------------
/**
* Gets the ID of the chronology - 'Japanese'.
* <p>
* The ID uniquely identifies the {@code Chrono}. It can be used to lookup the {@code Chrono} using
* {@link #of(String)}.
*
* @return the chronology ID - 'Japanese'
* @see #getCalendarType()
*/
@Override
public String getId() {
return "Japanese";
}
/**
* Gets the calendar type of the underlying calendar system - 'japanese'.
* <p>
* The calendar type is an identifier defined by the <em>Unicode Locale Data Markup Language (LDML)</em>
* specification. It can be used to lookup the {@code Chrono} using {@link #of(String)}. It can also be used
* as part of a locale, accessible via {@link Locale#getUnicodeLocaleType(String)} with the key 'ca'.
*
* @return the calendar system type - 'japanese'
* @see #getId()
*/
@Override
public String getCalendarType() {
return "japanese";
}
// -----------------------------------------------------------------------
@Override
public ChronoLocalDate<JapaneseChrono> date(Era<JapaneseChrono> era, int yearOfEra, int month, int dayOfMonth) {
if (era instanceof JapaneseEra == false) {
throw new DateTimeException("Era must be JapaneseEra");
}
return JapaneseDate.of((JapaneseEra) era, yearOfEra, month, dayOfMonth);
}
@Override
public ChronoLocalDate<JapaneseChrono> date(int prolepticYear, int month, int dayOfMonth) {
return new JapaneseDate(LocalDate.of(prolepticYear, month, dayOfMonth));
}
@Override
public ChronoLocalDate<JapaneseChrono> dateYearDay(int prolepticYear, int dayOfYear) {
LocalDate date = LocalDate.ofYearDay(prolepticYear, dayOfYear);
return date(prolepticYear, date.getMonthValue(), date.getDayOfMonth());
}
@Override
public ChronoLocalDate<JapaneseChrono> date(DateTimeAccessor dateTime) {
if (dateTime instanceof JapaneseDate) {
return (JapaneseDate) dateTime;
}
return new JapaneseDate(LocalDate.from(dateTime));
}
// -----------------------------------------------------------------------
/**
* Checks if the specified year is a leap year.
* <p>
* Japanese calendar leap years occur exactly in line with ISO leap years. This method does not validate the
* year passed in, and only has a well-defined result for years in the supported range.
*
* @param prolepticYear the proleptic-year to check, not validated for range
* @return true if the year is a leap year
*/
@Override
public boolean isLeapYear(long prolepticYear) {
return ISOChrono.INSTANCE.isLeapYear(prolepticYear);
}
@Override
public int prolepticYear(Era<JapaneseChrono> era, int yearOfEra) {
if (era instanceof JapaneseEra == false) {
throw new DateTimeException("Era must be JapaneseEra");
}
JapaneseEra jera = (JapaneseEra) era;
int gregorianYear = jera.getPrivateEra().getSinceDate().getYear() + yearOfEra - 1;
if (yearOfEra == 1) {
return gregorianYear;
}
LocalGregorianCalendar.Date jdate = JCAL.newCalendarDate(null);
jdate.setEra(jera.getPrivateEra()).setDate(yearOfEra, 1, 1);
JCAL.normalize(jdate);
if (jdate.getNormalizedYear() == gregorianYear) {
return gregorianYear;
}
throw new DateTimeException("invalid yearOfEra value");
}
/**
* Returns the calendar system era object from the given numeric value.
*
* See the description of each Era for the numeric values of: {@link #ERA_HEISEI}, {@link #ERA_SHOWA},
* {@link #ERA_TAISHO}, {@link #ERA_MEIJI}), only Meiji and later eras are supported. Prior to Meiji
* {@link #ERA_SEIREKI} is used.
*
* @param eraValue the era value
* @return the Japanese {@code Era} for the given numeric era value
* @throws DateTimeException if {@code eraValue} is invalid
*/
@Override
public Era<JapaneseChrono> eraOf(int eraValue) {
return JapaneseEra.of(eraValue);
}
@Override
public List<Era<JapaneseChrono>> eras() {
return Arrays.<Era<JapaneseChrono>> asList(JapaneseEra.values());
}
// -----------------------------------------------------------------------
@Override
public DateTimeValueRange range(ChronoField field) {
switch (field) {
case DAY_OF_MONTH:
case DAY_OF_WEEK:
case MICRO_OF_DAY:
case MICRO_OF_SECOND:
case HOUR_OF_DAY:
case HOUR_OF_AMPM:
case MINUTE_OF_DAY:
case MINUTE_OF_HOUR:
case SECOND_OF_DAY:
case SECOND_OF_MINUTE:
case MILLI_OF_DAY:
case MILLI_OF_SECOND:
case NANO_OF_DAY:
case NANO_OF_SECOND:
case CLOCK_HOUR_OF_DAY:
case CLOCK_HOUR_OF_AMPM:
case EPOCH_DAY:
case EPOCH_MONTH:
return field.range();
}
Calendar jcal = Calendar.getInstance(LOCALE);
int fieldIndex;
switch (field) {
case ERA:
return DateTimeValueRange.of(jcal.getMinimum(Calendar.ERA) - JapaneseEra.ERA_OFFSET,
jcal.getMaximum(Calendar.ERA) - JapaneseEra.ERA_OFFSET);
case YEAR:
case YEAR_OF_ERA:
return DateTimeValueRange.of(LocalDate.MIN_YEAR, jcal.getGreatestMinimum(Calendar.YEAR),
jcal.getLeastMaximum(Calendar.YEAR), LocalDate.MAX_YEAR);
case MONTH_OF_YEAR:
return DateTimeValueRange.of(jcal.getMinimum(Calendar.MONTH) + 1, jcal.getGreatestMinimum(Calendar.MONTH) + 1,
jcal.getLeastMaximum(Calendar.MONTH) + 1, jcal.getMaximum(Calendar.MONTH) + 1);
case DAY_OF_YEAR:
fieldIndex = Calendar.DAY_OF_YEAR;
break;
default :
// TODO: review the remaining fields
throw new UnsupportedOperationException("Unimplementable field: " + field);
}
return DateTimeValueRange.of(jcal.getMinimum(fieldIndex), jcal.getGreatestMinimum(fieldIndex),
jcal.getLeastMaximum(fieldIndex), jcal.getMaximum(fieldIndex));
}
}