318 lines
12 KiB
Java
318 lines
12 KiB
Java
![]() |
/* GENERATED SOURCE. DO NOT MODIFY. */
|
||
|
// © 2018 and later: Unicode, Inc. and others.
|
||
|
// License & terms of use: http://www.unicode.org/copyright.html
|
||
|
package android.icu.impl;
|
||
|
|
||
|
import java.util.Arrays;
|
||
|
|
||
|
import android.icu.util.ICUException;
|
||
|
import android.icu.util.TimeZone;
|
||
|
import android.icu.util.UResourceBundle;
|
||
|
import android.icu.util.UResourceBundleIterator;
|
||
|
|
||
|
/**
|
||
|
* <code>EraRules</code> represents calendar era rules specified
|
||
|
* in supplementalData/calendarData.
|
||
|
*
|
||
|
* @author Yoshito Umaoka
|
||
|
* @hide Only a subset of ICU is exposed in Android
|
||
|
*/
|
||
|
public class EraRules {
|
||
|
private static final int MAX_ENCODED_START_YEAR = 32767;
|
||
|
private static final int MIN_ENCODED_START_YEAR = -32768;
|
||
|
|
||
|
public static final int MIN_ENCODED_START = encodeDate(MIN_ENCODED_START_YEAR, 1, 1);
|
||
|
|
||
|
private static final int YEAR_MASK = 0xFFFF0000;
|
||
|
private static final int MONTH_MASK = 0x0000FF00;
|
||
|
private static final int DAY_MASK = 0x000000FF;
|
||
|
|
||
|
private int[] startDates;
|
||
|
private int numEras;
|
||
|
private int currentEra;
|
||
|
|
||
|
private EraRules(int[] startDates, int numEras) {
|
||
|
this.startDates = startDates;
|
||
|
this.numEras = numEras;
|
||
|
initCurrentEra();
|
||
|
}
|
||
|
|
||
|
public static EraRules getInstance(CalType calType, boolean includeTentativeEra) {
|
||
|
UResourceBundle supplementalDataRes = UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME,
|
||
|
"supplementalData", ICUResourceBundle.ICU_DATA_CLASS_LOADER);
|
||
|
UResourceBundle calendarDataRes = supplementalDataRes.get("calendarData");
|
||
|
UResourceBundle calendarTypeRes = calendarDataRes.get(calType.getId());
|
||
|
UResourceBundle erasRes = calendarTypeRes.get("eras");
|
||
|
|
||
|
int numEras = erasRes.getSize();
|
||
|
int firstTentativeIdx = Integer.MAX_VALUE; // first tentative era index
|
||
|
int[] startDates = new int[numEras];
|
||
|
|
||
|
UResourceBundleIterator itr = erasRes.getIterator();
|
||
|
while (itr.hasNext()) {
|
||
|
UResourceBundle eraRuleRes = itr.next();
|
||
|
String eraIdxStr = eraRuleRes.getKey();
|
||
|
int eraIdx = -1;
|
||
|
try {
|
||
|
eraIdx = Integer.parseInt(eraIdxStr);
|
||
|
} catch (NumberFormatException e) {
|
||
|
throw new ICUException("Invalid era rule key:" + eraIdxStr + " in era rule data for " + calType.getId());
|
||
|
}
|
||
|
if (eraIdx < 0 || eraIdx >= numEras) {
|
||
|
throw new ICUException("Era rule key:" + eraIdxStr + " in era rule data for " + calType.getId()
|
||
|
+ " must be in range [0, " + (numEras - 1) + "]");
|
||
|
}
|
||
|
if (isSet(startDates[eraIdx])) {
|
||
|
throw new ICUException(
|
||
|
"Duplicated era rule for rule key:" + eraIdxStr + " in era rule data for " + calType.getId());
|
||
|
}
|
||
|
|
||
|
boolean hasName = true;
|
||
|
boolean hasEnd = false;
|
||
|
UResourceBundleIterator ruleItr = eraRuleRes.getIterator();
|
||
|
while (ruleItr.hasNext()) {
|
||
|
UResourceBundle res = ruleItr.next();
|
||
|
String key = res.getKey();
|
||
|
if (key.equals("start")) {
|
||
|
int[] fields = res.getIntVector();
|
||
|
if (fields.length != 3 || !isValidRuleStartDate(fields[0], fields[1], fields[2])) {
|
||
|
throw new ICUException(
|
||
|
"Invalid era rule date data:" + Arrays.toString(fields) + " in era rule data for "
|
||
|
+ calType.getId());
|
||
|
}
|
||
|
startDates[eraIdx] = encodeDate(fields[0], fields[1], fields[2]);
|
||
|
} else if (key.equals("named")) {
|
||
|
String val = res.getString();
|
||
|
if (val.equals("false")) {
|
||
|
hasName = false;
|
||
|
}
|
||
|
} else if (key.equals("end")) {
|
||
|
hasEnd = true;
|
||
|
}
|
||
|
}
|
||
|
if (isSet(startDates[eraIdx])) {
|
||
|
if (hasEnd) {
|
||
|
// This implementation assumes either start or end is available, not both.
|
||
|
// For now, just ignore the end rule.
|
||
|
}
|
||
|
} else {
|
||
|
if (hasEnd) {
|
||
|
if (eraIdx != 0) {
|
||
|
// This implementation does not support end only rule for eras other than
|
||
|
// the first one.
|
||
|
throw new ICUException(
|
||
|
"Era data for " + eraIdxStr + " in era rule data for " + calType.getId()
|
||
|
+ " has only end rule.");
|
||
|
}
|
||
|
startDates[eraIdx] = MIN_ENCODED_START;
|
||
|
} else {
|
||
|
throw new ICUException("Missing era start/end rule date for key:" + eraIdxStr + " in era rule data for "
|
||
|
+ calType.getId());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (hasName) {
|
||
|
if (eraIdx >= firstTentativeIdx) {
|
||
|
throw new ICUException(
|
||
|
"Non-tentative era(" + eraIdx + ") must be placed before the first tentative era");
|
||
|
}
|
||
|
} else {
|
||
|
if (eraIdx < firstTentativeIdx) {
|
||
|
firstTentativeIdx = eraIdx;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (firstTentativeIdx < Integer.MAX_VALUE && !includeTentativeEra) {
|
||
|
return new EraRules(startDates, firstTentativeIdx);
|
||
|
}
|
||
|
|
||
|
return new EraRules(startDates, numEras);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets number of effective eras
|
||
|
* @return number of effective eras
|
||
|
*/
|
||
|
public int getNumberOfEras() {
|
||
|
return numEras;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets start date of an era
|
||
|
* @param eraIdx Era index
|
||
|
* @param fillIn Receives date fields if supplied. If null, or size of array
|
||
|
* is less than 3, then a new int[] will be newly allocated.
|
||
|
* @return An int array including values of year, month, day of month in this order.
|
||
|
* When an era has no start date, the result will be January 1st in year
|
||
|
* whose value is minimum integer.
|
||
|
*/
|
||
|
public int[] getStartDate(int eraIdx, int[] fillIn) {
|
||
|
if (eraIdx < 0 || eraIdx >= numEras) {
|
||
|
throw new IllegalArgumentException("eraIdx is out of range");
|
||
|
}
|
||
|
return decodeDate(startDates[eraIdx], fillIn);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets start year of an era
|
||
|
* @param eraIdx Era index
|
||
|
* @return The first year of an era. When a era has no start date, minimum integer
|
||
|
* value is returned.
|
||
|
*/
|
||
|
public int getStartYear(int eraIdx) {
|
||
|
if (eraIdx < 0 || eraIdx >= numEras) {
|
||
|
throw new IllegalArgumentException("eraIdx is out of range");
|
||
|
}
|
||
|
int[] fields = decodeDate(startDates[eraIdx], null);
|
||
|
return fields[0];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns era index for the specified year/month/day.
|
||
|
* @param year Year
|
||
|
* @param month Month (1-base)
|
||
|
* @param day Day of month
|
||
|
* @return era index (or 0, when the specified date is before the first era)
|
||
|
*/
|
||
|
public int getEraIndex(int year, int month, int day) {
|
||
|
if (month < 1 || month > 12 || day < 1 || day > 31) {
|
||
|
throw new IllegalArgumentException("Illegal date - year:" + year + "month:" + month + "day:" + day);
|
||
|
}
|
||
|
int high = numEras; // last index + 1
|
||
|
int low;
|
||
|
|
||
|
// Short circuit for recent years. Most modern computations will
|
||
|
// occur in the last few eras.
|
||
|
if (compareEncodedDateWithYMD(startDates[getCurrentEraIndex()], year, month, day) <= 0) {
|
||
|
low = getCurrentEraIndex();
|
||
|
} else {
|
||
|
low = 0;
|
||
|
}
|
||
|
|
||
|
// Do binary search
|
||
|
while (low < high - 1) {
|
||
|
int i = (low + high) / 2;
|
||
|
if (compareEncodedDateWithYMD(startDates[i], year, month, day) <= 0) {
|
||
|
low = i;
|
||
|
} else {
|
||
|
high = i;
|
||
|
}
|
||
|
}
|
||
|
return low;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the current era index. This is calculated only once for an instance of
|
||
|
* EraRules. The current era calculation is based on the default time zone at
|
||
|
* the time of instantiation.
|
||
|
*
|
||
|
* @return era index of current era (or 0, when current date is before the first era)
|
||
|
*/
|
||
|
public int getCurrentEraIndex() {
|
||
|
return currentEra;
|
||
|
}
|
||
|
|
||
|
private void initCurrentEra() {
|
||
|
long localMillis = System.currentTimeMillis();
|
||
|
TimeZone zone = TimeZone.getDefault();
|
||
|
localMillis += zone.getOffset(localMillis);
|
||
|
|
||
|
int[] fields = Grego.timeToFields(localMillis, null);
|
||
|
int currentEncodedDate = encodeDate(fields[0], fields[1] + 1 /* changes to 1-base */, fields[2]);
|
||
|
int eraIdx = numEras - 1;
|
||
|
while (eraIdx > 0) {
|
||
|
if (currentEncodedDate >= startDates[eraIdx]) {
|
||
|
break;
|
||
|
}
|
||
|
eraIdx--;
|
||
|
}
|
||
|
// Note: current era could be before the first era.
|
||
|
// In this case, this implementation returns the first era index (0).
|
||
|
currentEra = eraIdx;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// private methods
|
||
|
//
|
||
|
|
||
|
private static boolean isSet(int startDate) {
|
||
|
return startDate != 0;
|
||
|
}
|
||
|
|
||
|
private static boolean isValidRuleStartDate(int year, int month, int day) {
|
||
|
return year >= MIN_ENCODED_START_YEAR && year <= MAX_ENCODED_START_YEAR
|
||
|
&& month >= 1 && month <= 12 && day >= 1 && day <= 31;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Encode year/month/date to a single integer.
|
||
|
* year is high 16 bits (-32768 to 32767), month is
|
||
|
* next 8 bits and day of month is last 8 bits.
|
||
|
*
|
||
|
* @param year year
|
||
|
* @param month month (1-base)
|
||
|
* @param day day of month
|
||
|
* @return an encoded date.
|
||
|
*/
|
||
|
private static int encodeDate(int year, int month, int day) {
|
||
|
return year << 16 | month << 8 | day;
|
||
|
}
|
||
|
|
||
|
private static int[] decodeDate(int encodedDate, int[] fillIn) {
|
||
|
int year, month, day;
|
||
|
if (encodedDate == MIN_ENCODED_START) {
|
||
|
year = Integer.MIN_VALUE;
|
||
|
month = 1;
|
||
|
day = 1;
|
||
|
} else {
|
||
|
year = (encodedDate & YEAR_MASK) >> 16;
|
||
|
month = (encodedDate & MONTH_MASK) >> 8;
|
||
|
day = encodedDate & DAY_MASK;
|
||
|
}
|
||
|
|
||
|
if (fillIn != null && fillIn.length >= 3) {
|
||
|
fillIn[0] = year;
|
||
|
fillIn[1] = month;
|
||
|
fillIn[2] = day;
|
||
|
return fillIn;
|
||
|
}
|
||
|
|
||
|
int[] result = {year, month, day};
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Compare an encoded date with another date specified by year/month/day.
|
||
|
* @param encoded An encoded date
|
||
|
* @param year Year of another date
|
||
|
* @param month Month of another date
|
||
|
* @param day Day of another date
|
||
|
* @return -1 when encoded date is earlier, 0 when two dates are same,
|
||
|
* and 1 when encoded date is later.
|
||
|
*/
|
||
|
private static int compareEncodedDateWithYMD(int encoded, int year, int month, int day) {
|
||
|
if (year < MIN_ENCODED_START_YEAR) {
|
||
|
if (encoded == MIN_ENCODED_START) {
|
||
|
if (year > Integer.MIN_VALUE || month > 1 || day > 1) {
|
||
|
return -1;
|
||
|
}
|
||
|
return 0;
|
||
|
} else {
|
||
|
return 1;
|
||
|
}
|
||
|
} else if (year > MAX_ENCODED_START_YEAR) {
|
||
|
return -1;
|
||
|
} else {
|
||
|
int tmp = encodeDate(year, month, day);
|
||
|
if (encoded < tmp) {
|
||
|
return -1;
|
||
|
} else if (encoded == tmp) {
|
||
|
return 0;
|
||
|
} else {
|
||
|
return 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|