blob: 348a5dd20c464ffb607190aa74dca4a6510cdd8a [file] [log] [blame]
Aurimas Liutikasdc3f8852024-07-11 10:07:48 -07001/* GENERATED SOURCE. DO NOT MODIFY. */
2// © 2016 and later: Unicode, Inc. and others.
3// License & terms of use: http://www.unicode.org/copyright.html
4/*
5 *******************************************************************************
6 * Copyright (C) 2016, International Business Machines Corporation and
7 * others. All Rights Reserved.
8 *******************************************************************************
9 */
10package android.icu.impl;
11
12import java.util.HashMap;
13import java.util.Map;
14
15import android.icu.util.ICUException;
16import android.icu.util.ULocale;
17import android.icu.util.UResourceBundle;
18
19/**
20 * @hide Only a subset of ICU is exposed in Android
21 */
22public final class DayPeriodRules {
23 /**
24 * @hide Only a subset of ICU is exposed in Android
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
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
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
83 private DayPeriodRulesDataSink(DayPeriodRulesData data) {
84 this.data = data;
85 }
86
87 @Override
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
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.
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
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
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
217 private DayPeriodRulesCountSink(DayPeriodRulesData data) {
218 this.data = data;
219 }
220
221 @Override
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
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 {@code locale}.
250 */
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
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
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
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
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.
361 public boolean hasMidnight() { return hasMidnight; }
362 public boolean hasNoon() { return hasNoon; }
363 public DayPeriod getDayPeriodForHour(int hour) { return dayPeriodForHour[hour]; }
364
365 // Helpers.
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
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}