• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }