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