• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GENERATED SOURCE. DO NOT MODIFY. */
2 // © 2016 and later: Unicode, Inc. and others.
3 // License & terms of use: http://www.unicode.org/copyright.html#License
4 /*
5  *******************************************************************************
6  * Copyright (C) 2016, International Business Machines Corporation and
7  * others. All Rights Reserved.
8  *******************************************************************************
9  */
10 package ohos.global.icu.impl;
11 
12 import java.util.HashMap;
13 import java.util.Map;
14 
15 import ohos.global.icu.util.ICUException;
16 import ohos.global.icu.util.ULocale;
17 import ohos.global.icu.util.UResourceBundle;
18 
19 /**
20  * @hide exposed on OHOS
21  */
22 public final class DayPeriodRules {
23     /**
24      * @hide exposed on OHOS
25      */
26     public enum DayPeriod {
27         MIDNIGHT,
28         NOON,
29         MORNING1,
30         AFTERNOON1,
31         EVENING1,
32         NIGHT1,
33         MORNING2,
34         AFTERNOON2,
35         EVENING2,
36         NIGHT2,
37         AM,
38         PM;
39 
40         public static DayPeriod[] VALUES = DayPeriod.values();
41 
fromStringOrNull(CharSequence str)42         private static DayPeriod fromStringOrNull(CharSequence str) {
43             if ("midnight".contentEquals(str)) { return MIDNIGHT; }
44             if ("noon".contentEquals(str)) { return NOON; }
45             if ("morning1".contentEquals(str)) { return MORNING1; }
46             if ("afternoon1".contentEquals(str)) { return AFTERNOON1; }
47             if ("evening1".contentEquals(str)) { return EVENING1; }
48             if ("night1".contentEquals(str)) { return NIGHT1; }
49             if ("morning2".contentEquals(str)) { return MORNING2; }
50             if ("afternoon2".contentEquals(str)) { return AFTERNOON2; }
51             if ("evening2".contentEquals(str)) { return EVENING2; }
52             if ("night2".contentEquals(str)) { return NIGHT2; }
53             if ("am".contentEquals(str)) { return AM; }
54             if ("pm".contentEquals(str)) { return PM; }
55             return null;
56         }
57     }
58 
59     private enum CutoffType {
60         BEFORE,
61         AFTER,  // TODO: AFTER is deprecated in CLDR 29. Remove.
62         FROM,
63         AT;
64 
fromStringOrNull(CharSequence str)65         private static CutoffType fromStringOrNull(CharSequence str) {
66             if ("from".contentEquals(str)) { return CutoffType.FROM; }
67             if ("before".contentEquals(str)) { return CutoffType.BEFORE; }
68             if ("after".contentEquals(str)) { return CutoffType.AFTER; }
69             if ("at".contentEquals(str)) { return CutoffType.AT; }
70             return null;
71         }
72     }
73 
74     private static final class DayPeriodRulesData {
75         Map<String, Integer> localesToRuleSetNumMap = new HashMap<String, Integer>();
76         DayPeriodRules[] rules;
77         int maxRuleSetNum = -1;
78     }
79 
80     private static final class DayPeriodRulesDataSink extends UResource.Sink {
81         private DayPeriodRulesData data;
82 
DayPeriodRulesDataSink(DayPeriodRulesData data)83         private DayPeriodRulesDataSink(DayPeriodRulesData data) {
84             this.data = data;
85         }
86 
87         @Override
put(UResource.Key key, UResource.Value value, boolean noFallback)88         public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
89             UResource.Table dayPeriodData = value.getTable();
90             for (int i = 0; dayPeriodData.getKeyAndValue(i, key, value); ++i) {
91                 if (key.contentEquals("locales")) {
92                     UResource.Table locales = value.getTable();
93                     for (int j = 0; locales.getKeyAndValue(j, key, value); ++j) {
94                         int setNum = parseSetNum(value.getString());
95                         data.localesToRuleSetNumMap.put(key.toString(), setNum);
96                     }
97                 } else if (key.contentEquals("rules")) {
98                     UResource.Table rules = value.getTable();
99                     processRules(rules, key, value);
100                 }
101             }
102         }
103 
processRules(UResource.Table rules, UResource.Key key, UResource.Value value)104         private void processRules(UResource.Table rules, UResource.Key key, UResource.Value value) {
105             for (int i = 0; rules.getKeyAndValue(i, key, value); ++i) {
106                 ruleSetNum = parseSetNum(key.toString());
107                 data.rules[ruleSetNum] = new DayPeriodRules();
108 
109                 UResource.Table ruleSet = value.getTable();
110                 for (int j = 0; ruleSet.getKeyAndValue(j, key, value); ++j) {
111                     period = DayPeriod.fromStringOrNull(key);
112                     if (period == null) { throw new ICUException("Unknown day period in data."); }
113 
114                     UResource.Table periodDefinition = value.getTable();
115                     for (int k = 0; periodDefinition.getKeyAndValue(k, key, value); ++k) {
116                         if (value.getType() == UResourceBundle.STRING) {
117                             // Key-value pairs (e.g. before{6:00})
118                             CutoffType type = CutoffType.fromStringOrNull(key);
119                             addCutoff(type, value.getString());
120                         } else {
121                             // Arrays (e.g. before{6:00, 24:00}
122                             cutoffType = CutoffType.fromStringOrNull(key);
123                             UResource.Array cutoffArray = value.getArray();
124                             int length = cutoffArray.getSize();
125                             for (int l = 0; l < length; ++l) {
126                                 cutoffArray.getValue(l, value);
127                                 addCutoff(cutoffType, value.getString());
128                             }
129                         }
130                     }
131                     setDayPeriodForHoursFromCutoffs();
132                     for (int k = 0; k < cutoffs.length; ++k) {
133                         cutoffs[k] = 0;
134                     }
135                 }
136                 for (DayPeriod period : data.rules[ruleSetNum].dayPeriodForHour) {
137                     if (period == null) {
138                         throw new ICUException("Rules in data don't cover all 24 hours (they should).");
139                     }
140                 }
141             }
142         }
143 
144         // Members.
145         private int cutoffs[] = new int[25];  // [0] thru [24]; 24 is allowed is "before 24".
146 
147         // "Path" to data.
148         private int ruleSetNum;
149         private DayPeriod period;
150         private CutoffType cutoffType;
151 
152         // Helpers.
addCutoff(CutoffType type, String hourStr)153         private void addCutoff(CutoffType type, String hourStr) {
154             if (type == null) { throw new ICUException("Cutoff type not recognized."); }
155             int hour = parseHour(hourStr);
156             cutoffs[hour] |= 1 << type.ordinal();
157         }
158 
setDayPeriodForHoursFromCutoffs()159         private void setDayPeriodForHoursFromCutoffs() {
160             DayPeriodRules rule = data.rules[ruleSetNum];
161             for (int startHour = 0; startHour <= 24; ++startHour) {
162                 // AT cutoffs must be either midnight or noon.
163                 if ((cutoffs[startHour] & (1 << CutoffType.AT.ordinal())) > 0) {
164                     if (startHour == 0 && period == DayPeriod.MIDNIGHT) {
165                         rule.hasMidnight = true;
166                     } else if (startHour == 12 && period == DayPeriod.NOON) {
167                         rule.hasNoon = true;
168                     } else {
169                         throw new ICUException("AT cutoff must only be set for 0:00 or 12:00.");
170                     }
171                 }
172 
173                 // FROM/AFTER and BEFORE must come in a pair.
174                 if ((cutoffs[startHour] & (1 << CutoffType.FROM.ordinal())) > 0 ||
175                         (cutoffs[startHour] & (1 << CutoffType.AFTER.ordinal())) > 0) {
176                     for (int hour = startHour + 1;; ++hour) {
177                         if (hour == startHour) {
178                             // We've gone around the array once and can't find a BEFORE.
179                             throw new ICUException(
180                                     "FROM/AFTER cutoffs must have a matching BEFORE cutoff.");
181                         }
182                         if (hour == 25) { hour = 0; }
183                         if ((cutoffs[hour] & (1 << CutoffType.BEFORE.ordinal())) > 0) {
184                             rule.add(startHour, hour, period);
185                             break;
186                         }
187                     }
188                 }
189             }
190         }
191 
parseHour(String str)192         private static int parseHour(String str) {
193             int firstColonPos = str.indexOf(':');
194             if (firstColonPos < 0 || !str.substring(firstColonPos).equals(":00")) {
195                 throw new ICUException("Cutoff time must end in \":00\".");
196             }
197 
198             String hourStr = str.substring(0, firstColonPos);
199             if (firstColonPos != 1 && firstColonPos != 2) {
200                 throw new ICUException("Cutoff time must begin with h: or hh:");
201             }
202 
203             int hour = Integer.parseInt(hourStr);
204             // parseInt() throws NumberFormatException if hourStr isn't proper.
205 
206             if (hour < 0 || hour > 24) {
207                 throw new ICUException("Cutoff hour must be between 0 and 24, inclusive.");
208             }
209 
210             return hour;
211         }
212     }  // DayPeriodRulesDataSink
213 
214     private static class DayPeriodRulesCountSink extends UResource.Sink {
215         private DayPeriodRulesData data;
216 
DayPeriodRulesCountSink(DayPeriodRulesData data)217         private DayPeriodRulesCountSink(DayPeriodRulesData data) {
218             this.data = data;
219         }
220 
221         @Override
put(UResource.Key key, UResource.Value value, boolean noFallback)222         public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
223             UResource.Table rules = value.getTable();
224             for (int i = 0; rules.getKeyAndValue(i, key, value); ++i) {
225                 int setNum = parseSetNum(key.toString());
226                 if (setNum > data.maxRuleSetNum) {
227                     data.maxRuleSetNum = setNum;
228                 }
229             }
230         }
231     }
232 
233     private static final DayPeriodRulesData DATA = loadData();
234 
235     private boolean hasMidnight;
236     private boolean hasNoon;
237     private DayPeriod[] dayPeriodForHour;
238 
DayPeriodRules()239     private DayPeriodRules() {
240         hasMidnight = false;
241         hasNoon = false;
242         dayPeriodForHour = new DayPeriod[24];
243     }
244 
245     /**
246      * Get a DayPeriodRules object given a locale.
247      * If data hasn't been loaded, it will be loaded for all locales at once.
248      * @param locale locale for which the DayPeriodRules object is requested.
249      * @return a DayPeriodRules object for `locale`.
250      */
getInstance(ULocale locale)251     public static DayPeriodRules getInstance(ULocale locale) {
252         String localeCode = locale.getBaseName();
253         if (localeCode.isEmpty()) { localeCode = "root"; }
254 
255         Integer ruleSetNum = null;
256         while (ruleSetNum == null) {
257             ruleSetNum = DATA.localesToRuleSetNumMap.get(localeCode);
258             if (ruleSetNum == null) {
259                 localeCode = ULocale.getFallback(localeCode);
260                 if (localeCode.isEmpty()) {
261                     // Saves a lookup in the map.
262                     break;
263                 }
264             } else {
265                 break;
266             }
267         }
268 
269         if (ruleSetNum == null || DATA.rules[ruleSetNum] == null) {
270             // Data doesn't exist for the locale requested.
271             return null;
272         }
273 
274         return DATA.rules[ruleSetNum];
275     }
276 
getMidPointForDayPeriod(DayPeriod dayPeriod)277     public double getMidPointForDayPeriod(DayPeriod dayPeriod) {
278         int startHour = getStartHourForDayPeriod(dayPeriod);
279         int endHour = getEndHourForDayPeriod(dayPeriod);
280 
281         double midPoint = (startHour + endHour) / 2.0;
282 
283         if (startHour > endHour) {
284             // dayPeriod wraps around midnight. Shift midPoint by 12 hours, in the direction that
285             // lands it in [0, 24).
286             midPoint += 12;
287             if (midPoint >= 24) {
288                 midPoint -= 24;
289             }
290         }
291 
292         return midPoint;
293     }
294 
loadData()295     private static DayPeriodRulesData loadData() {
296         DayPeriodRulesData data = new DayPeriodRulesData();
297         ICUResourceBundle rb = ICUResourceBundle.getBundleInstance(
298                 ICUData.ICU_BASE_NAME,
299                 "dayPeriods",
300                 ICUResourceBundle.ICU_DATA_CLASS_LOADER,
301                 true);
302 
303         DayPeriodRulesCountSink countSink = new DayPeriodRulesCountSink(data);
304         rb.getAllItemsWithFallback("rules", countSink);
305 
306         data.rules = new DayPeriodRules[data.maxRuleSetNum + 1];
307         DayPeriodRulesDataSink sink = new DayPeriodRulesDataSink(data);
308         rb.getAllItemsWithFallback("", sink);
309 
310         return data;
311     }
312 
getStartHourForDayPeriod(DayPeriod dayPeriod)313     private int getStartHourForDayPeriod(DayPeriod dayPeriod) throws IllegalArgumentException {
314         if (dayPeriod == DayPeriod.MIDNIGHT) { return 0; }
315         if (dayPeriod == DayPeriod.NOON) { return 12; }
316 
317         if (dayPeriodForHour[0] == dayPeriod && dayPeriodForHour[23] == dayPeriod) {
318             // dayPeriod wraps around midnight. Start hour is later than end hour.
319             for (int i = 22; i >= 1; --i) {
320                 if (dayPeriodForHour[i] != dayPeriod) {
321                     return (i + 1);
322                 }
323             }
324         } else {
325             for (int i = 0; i <= 23; ++i) {
326                 if (dayPeriodForHour[i] == dayPeriod) {
327                     return i;
328                 }
329             }
330         }
331 
332         // dayPeriod doesn't exist in rule set; throw exception.
333         throw new IllegalArgumentException();
334     }
335 
getEndHourForDayPeriod(DayPeriod dayPeriod)336     private int getEndHourForDayPeriod(DayPeriod dayPeriod) {
337         if (dayPeriod == DayPeriod.MIDNIGHT) { return 0; }
338         if (dayPeriod == DayPeriod.NOON) { return 12; }
339 
340         if (dayPeriodForHour[0] == dayPeriod && dayPeriodForHour[23] == dayPeriod) {
341             // dayPeriod wraps around midnight. End hour is before start hour.
342             for (int i = 1; i <= 22; ++i) {
343                 if (dayPeriodForHour[i] != dayPeriod) {
344                     // i o'clock is when a new period starts, therefore when the old period ends.
345                     return i;
346                 }
347             }
348         } else {
349             for (int i = 23; i >= 0; --i) {
350                 if (dayPeriodForHour[i] == dayPeriod) {
351                     return (i + 1);
352                 }
353             }
354         }
355 
356         // dayPeriod doesn't exist in rule set; throw exception.
357         throw new IllegalArgumentException();
358     }
359 
360     // Getters.
hasMidnight()361     public boolean hasMidnight() { return hasMidnight; }
hasNoon()362     public boolean hasNoon() { return hasNoon; }
getDayPeriodForHour(int hour)363     public DayPeriod getDayPeriodForHour(int hour) { return dayPeriodForHour[hour]; }
364 
365     // Helpers.
add(int startHour, int limitHour, DayPeriod period)366     private void add(int startHour, int limitHour, DayPeriod period) {
367         for (int i = startHour; i != limitHour; ++i) {
368             if (i == 24) { i = 0; }
369             dayPeriodForHour[i] = period;
370         }
371     }
372 
parseSetNum(String setNumStr)373     private static int parseSetNum(String setNumStr) {
374         if (!setNumStr.startsWith("set")) {
375             throw new ICUException("Set number should start with \"set\".");
376         }
377 
378         String numStr = setNumStr.substring(3);  // e.g. "set17" -> "17"
379         return Integer.parseInt(numStr);  // This throws NumberFormatException if numStr isn't a proper number.
380     }
381 }
382