1 /* GENERATED SOURCE. DO NOT MODIFY. */ 2 // © 2018 and later: Unicode, Inc. and others. 3 // License & terms of use: http://www.unicode.org/copyright.html#License 4 package ohos.global.icu.impl; 5 6 import java.util.Arrays; 7 8 import ohos.global.icu.util.ICUException; 9 import ohos.global.icu.util.TimeZone; 10 import ohos.global.icu.util.UResourceBundle; 11 import ohos.global.icu.util.UResourceBundleIterator; 12 13 /** 14 * <code>EraRules</code> represents calendar era rules specified 15 * in supplementalData/calendarData. 16 * 17 * @author Yoshito Umaoka 18 * @hide exposed on OHOS 19 */ 20 public class EraRules { 21 private static final int MAX_ENCODED_START_YEAR = 32767; 22 private static final int MIN_ENCODED_START_YEAR = -32768; 23 24 public static final int MIN_ENCODED_START = encodeDate(MIN_ENCODED_START_YEAR, 1, 1); 25 26 private static final int YEAR_MASK = 0xFFFF0000; 27 private static final int MONTH_MASK = 0x0000FF00; 28 private static final int DAY_MASK = 0x000000FF; 29 30 private int[] startDates; 31 private int numEras; 32 private int currentEra; 33 EraRules(int[] startDates, int numEras)34 private EraRules(int[] startDates, int numEras) { 35 this.startDates = startDates; 36 this.numEras = numEras; 37 initCurrentEra(); 38 } 39 getInstance(CalType calType, boolean includeTentativeEra)40 public static EraRules getInstance(CalType calType, boolean includeTentativeEra) { 41 UResourceBundle supplementalDataRes = UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, 42 "supplementalData", ICUResourceBundle.ICU_DATA_CLASS_LOADER); 43 UResourceBundle calendarDataRes = supplementalDataRes.get("calendarData"); 44 UResourceBundle calendarTypeRes = calendarDataRes.get(calType.getId()); 45 UResourceBundle erasRes = calendarTypeRes.get("eras"); 46 47 int numEras = erasRes.getSize(); 48 int firstTentativeIdx = Integer.MAX_VALUE; // first tentative era index 49 int[] startDates = new int[numEras]; 50 51 UResourceBundleIterator itr = erasRes.getIterator(); 52 while (itr.hasNext()) { 53 UResourceBundle eraRuleRes = itr.next(); 54 String eraIdxStr = eraRuleRes.getKey(); 55 int eraIdx = -1; 56 try { 57 eraIdx = Integer.parseInt(eraIdxStr); 58 } catch (NumberFormatException e) { 59 throw new ICUException("Invald era rule key:" + eraIdxStr + " in era rule data for " + calType.getId()); 60 } 61 if (eraIdx < 0 || eraIdx >= numEras) { 62 throw new ICUException("Era rule key:" + eraIdxStr + " in era rule data for " + calType.getId() 63 + " must be in range [0, " + (numEras - 1) + "]"); 64 } 65 if (isSet(startDates[eraIdx])) { 66 throw new ICUException( 67 "Dupulicated era rule for rule key:" + eraIdxStr + " in era rule data for " + calType.getId()); 68 } 69 70 boolean hasName = true; 71 boolean hasEnd = false; 72 UResourceBundleIterator ruleItr = eraRuleRes.getIterator(); 73 while (ruleItr.hasNext()) { 74 UResourceBundle res = ruleItr.next(); 75 String key = res.getKey(); 76 if (key.equals("start")) { 77 int[] fields = res.getIntVector(); 78 if (fields.length != 3 || !isValidRuleStartDate(fields[0], fields[1], fields[2])) { 79 throw new ICUException( 80 "Invalid era rule date data:" + Arrays.toString(fields) + " in era rule data for " 81 + calType.getId()); 82 } 83 startDates[eraIdx] = encodeDate(fields[0], fields[1], fields[2]); 84 } else if (key.equals("named")) { 85 String val = res.getString(); 86 if (val.equals("false")) { 87 hasName = false; 88 } 89 } else if (key.equals("end")) { 90 hasEnd = true; 91 } 92 } 93 if (isSet(startDates[eraIdx])) { 94 if (hasEnd) { 95 // This implementation assumes either start or end is available, not both. 96 // For now, just ignore the end rule. 97 } 98 } else { 99 if (hasEnd) { 100 if (eraIdx != 0) { 101 // This implementation does not support end only rule for eras other than 102 // the first one. 103 throw new ICUException( 104 "Era data for " + eraIdxStr + " in era rule data for " + calType.getId() 105 + " has only end rule."); 106 } 107 startDates[eraIdx] = MIN_ENCODED_START; 108 } else { 109 throw new ICUException("Missing era start/end rule date for key:" + eraIdxStr + " in era rule data for " 110 + calType.getId()); 111 } 112 } 113 114 if (hasName) { 115 if (eraIdx >= firstTentativeIdx) { 116 throw new ICUException( 117 "Non-tentative era(" + eraIdx + ") must be placed before the first tentative era"); 118 } 119 } else { 120 if (eraIdx < firstTentativeIdx) { 121 firstTentativeIdx = eraIdx; 122 } 123 } 124 } 125 126 if (firstTentativeIdx < Integer.MAX_VALUE && !includeTentativeEra) { 127 return new EraRules(startDates, firstTentativeIdx); 128 } 129 130 return new EraRules(startDates, numEras); 131 } 132 133 /** 134 * Gets number of effective eras 135 * @return number of effective eras 136 */ getNumberOfEras()137 public int getNumberOfEras() { 138 return numEras; 139 } 140 141 /** 142 * Gets start date of an era 143 * @param eraIdx Era index 144 * @param fillIn Receives date fields if supplied. If null, or size of array 145 * is less than 3, then a new int[] will be newly allocated. 146 * @return An int array including values of year, month, day of month in this order. 147 * When an era has no start date, the result will be January 1st in year 148 * whose value is minimum integer. 149 */ getStartDate(int eraIdx, int[] fillIn)150 public int[] getStartDate(int eraIdx, int[] fillIn) { 151 if (eraIdx < 0 || eraIdx >= numEras) { 152 throw new IllegalArgumentException("eraIdx is out of range"); 153 } 154 return decodeDate(startDates[eraIdx], fillIn); 155 } 156 157 /** 158 * Gets start year of an era 159 * @param eraIdx Era index 160 * @return The first year of an era. When a era has no start date, minimum integer 161 * value is returned. 162 */ getStartYear(int eraIdx)163 public int getStartYear(int eraIdx) { 164 if (eraIdx < 0 || eraIdx >= numEras) { 165 throw new IllegalArgumentException("eraIdx is out of range"); 166 } 167 int[] fields = decodeDate(startDates[eraIdx], null); 168 return fields[0]; 169 } 170 171 /** 172 * Returns era index for the specified year/month/day. 173 * @param year Year 174 * @param month Month (1-base) 175 * @param day Day of month 176 * @return era index (or 0, when the specified date is before the first era) 177 */ getEraIndex(int year, int month, int day)178 public int getEraIndex(int year, int month, int day) { 179 if (month < 1 || month > 12 || day < 1 || day > 31) { 180 throw new IllegalArgumentException("Illegal date - year:" + year + "month:" + month + "day:" + day); 181 } 182 int high = numEras; // last index + 1 183 int low; 184 185 // Short circuit for recent years. Most modern computations will 186 // occur in the last few eras. 187 if (compareEncodedDateWithYMD(startDates[getCurrentEraIndex()], year, month, day) <= 0) { 188 low = getCurrentEraIndex(); 189 } else { 190 low = 0; 191 } 192 193 // Do binary search 194 while (low < high - 1) { 195 int i = (low + high) / 2; 196 if (compareEncodedDateWithYMD(startDates[i], year, month, day) <= 0) { 197 low = i; 198 } else { 199 high = i; 200 } 201 } 202 return low; 203 } 204 205 /** 206 * Gets the current era index. This is calculated only once for an instance of 207 * EraRules. The current era calculation is based on the default time zone at 208 * the time of instantiation. 209 * 210 * @return era index of current era (or 0, when current date is before the first era) 211 */ getCurrentEraIndex()212 public int getCurrentEraIndex() { 213 return currentEra; 214 } 215 initCurrentEra()216 private void initCurrentEra() { 217 long localMillis = System.currentTimeMillis(); 218 TimeZone zone = TimeZone.getDefault(); 219 localMillis += zone.getOffset(localMillis); 220 221 int[] fields = Grego.timeToFields(localMillis, null); 222 int currentEncodedDate = encodeDate(fields[0], fields[1] + 1 /* changes to 1-base */, fields[2]); 223 int eraIdx = numEras - 1; 224 while (eraIdx > 0) { 225 if (currentEncodedDate >= startDates[eraIdx]) { 226 break; 227 } 228 eraIdx--; 229 } 230 // Note: current era could be before the first era. 231 // In this case, this implementation returns the first era index (0). 232 currentEra = eraIdx; 233 } 234 235 // 236 // private methods 237 // 238 isSet(int startDate)239 private static boolean isSet(int startDate) { 240 return startDate != 0; 241 } 242 isValidRuleStartDate(int year, int month, int day)243 private static boolean isValidRuleStartDate(int year, int month, int day) { 244 return year >= MIN_ENCODED_START_YEAR && year <= MAX_ENCODED_START_YEAR 245 && month >= 1 && month <= 12 && day >= 1 && day <= 31; 246 } 247 248 /** 249 * Encode year/month/date to a single integer. 250 * year is high 16 bits (-32768 to 32767), month is 251 * next 8 bits and day of month is last 8 bits. 252 * 253 * @param year year 254 * @param month month (1-base) 255 * @param day day of month 256 * @return an encoded date. 257 */ encodeDate(int year, int month, int day)258 private static int encodeDate(int year, int month, int day) { 259 return year << 16 | month << 8 | day; 260 } 261 decodeDate(int encodedDate, int[] fillIn)262 private static int[] decodeDate(int encodedDate, int[] fillIn) { 263 int year, month, day; 264 if (encodedDate == MIN_ENCODED_START) { 265 year = Integer.MIN_VALUE; 266 month = 1; 267 day = 1; 268 } else { 269 year = (encodedDate & YEAR_MASK) >> 16; 270 month = (encodedDate & MONTH_MASK) >> 8; 271 day = encodedDate & DAY_MASK; 272 } 273 274 if (fillIn != null && fillIn.length >= 3) { 275 fillIn[0] = year; 276 fillIn[1] = month; 277 fillIn[2] = day; 278 return fillIn; 279 } 280 281 int[] result = {year, month, day}; 282 return result; 283 } 284 285 /** 286 * Compare an encoded date with another date specified by year/month/day. 287 * @param encoded An encoded date 288 * @param year Year of another date 289 * @param month Month of another date 290 * @param day Day of another date 291 * @return -1 when encoded date is earlier, 0 when two dates are same, 292 * and 1 when encoded date is later. 293 */ compareEncodedDateWithYMD(int encoded, int year, int month, int day)294 private static int compareEncodedDateWithYMD(int encoded, int year, int month, int day) { 295 if (year < MIN_ENCODED_START_YEAR) { 296 if (encoded == MIN_ENCODED_START) { 297 if (year > Integer.MIN_VALUE || month > 1 || day > 1) { 298 return -1; 299 } 300 return 0; 301 } else { 302 return 1; 303 } 304 } else if (year > MAX_ENCODED_START_YEAR) { 305 return -1; 306 } else { 307 int tmp = encodeDate(year, month, day); 308 if (encoded < tmp) { 309 return -1; 310 } else if (encoded == tmp) { 311 return 0; 312 } else { 313 return 1; 314 } 315 } 316 } 317 }