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 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 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 80 private DayPeriodRulesDataSink(DayPeriodRulesData data) { 81 this.data = data; 82 } 83 84 @Override 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 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. 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 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 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 214 private DayPeriodRulesCountSink(DayPeriodRulesData data) { 215 this.data = data; 216 } 217 218 @Override 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 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 */ 248 public static DayPeriodRules getInstance(ULocale locale) { 249 String localeCode = locale.getBaseName(); 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 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 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 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 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. 358 public boolean hasMidnight() { return hasMidnight; } 359 public boolean hasNoon() { return hasNoon; } 360 public DayPeriod getDayPeriodForHour(int hour) { return dayPeriodForHour[hour]; } 361 362 // Helpers. 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 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