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