]> git.saurik.com Git - apple/icu.git/blob - icuSources/i18n/dayperiodrules.cpp
ICU-57166.0.1.tar.gz
[apple/icu.git] / icuSources / i18n / dayperiodrules.cpp
1 /*
2 *******************************************************************************
3 * Copyright (C) 2016, International Business Machines
4 * Corporation and others. All Rights Reserved.
5 *******************************************************************************
6 * dayperiodrules.cpp
7 *
8 * created on: 2016-01-20
9 * created by: kazede
10 */
11
12 #include "dayperiodrules.h"
13
14 #include "unicode/ures.h"
15 #include "charstr.h"
16 #include "cstring.h"
17 #include "ucln_in.h"
18 #include "uhash.h"
19 #include "umutex.h"
20 #include "uresimp.h"
21
22
23 U_NAMESPACE_BEGIN
24
25 namespace {
26
27 struct DayPeriodRulesData : public UMemory {
28 DayPeriodRulesData() : localeToRuleSetNumMap(NULL), rules(NULL), maxRuleSetNum(0) {}
29
30 UHashtable *localeToRuleSetNumMap;
31 DayPeriodRules *rules;
32 int32_t maxRuleSetNum;
33 } *data = NULL;
34
35 enum CutoffType {
36 CUTOFF_TYPE_UNKNOWN = -1,
37 CUTOFF_TYPE_BEFORE,
38 CUTOFF_TYPE_AFTER, // TODO: AFTER is deprecated in CLDR 29. Remove.
39 CUTOFF_TYPE_FROM,
40 CUTOFF_TYPE_AT
41 };
42
43 } // namespace
44
45 struct DayPeriodRulesDataSink : public ResourceTableSink {
46 // Initialize sub-sinks.
47 DayPeriodRulesDataSink() :
48 rulesSink(*this), ruleSetSink(*this), periodSink(*this), cutoffSink(*this) {
49 for (int32_t i = 0; i < UPRV_LENGTHOF(cutoffs); ++i) { cutoffs[i] = 0; }
50 }
51 virtual ~DayPeriodRulesDataSink();
52
53 // Entry point.
54 virtual ResourceTableSink *getOrCreateTableSink(const char *key, int32_t, UErrorCode &errorCode) {
55 if (U_FAILURE(errorCode)) { return NULL; }
56
57 if (uprv_strcmp(key, "locales") == 0) {
58 return &localesSink;
59 } else if (uprv_strcmp(key, "rules") == 0) {
60 // Allocate one more than needed to skip [0]. See comment in parseSetNum().
61 data->rules = new DayPeriodRules[data->maxRuleSetNum + 1];
62 if (data->rules == NULL) {
63 errorCode = U_MEMORY_ALLOCATION_ERROR;
64 return NULL;
65 } else {
66 return &rulesSink;
67 }
68 }
69 return NULL;
70 }
71
72 // Data root -> locales.
73 struct LocalesSink : public ResourceTableSink {
74 virtual ~LocalesSink();
75
76 virtual void put(const char *key, const ResourceValue &value, UErrorCode &errorCode) {
77 if (U_FAILURE(errorCode)) { return; }
78
79 UnicodeString setNum_str = value.getUnicodeString(errorCode);
80 int32_t setNum = parseSetNum(setNum_str, errorCode);
81 uhash_puti(data->localeToRuleSetNumMap, const_cast<char *>(key), setNum, &errorCode);
82 }
83 } localesSink;
84
85 // Data root -> rules.
86 struct RulesSink : public ResourceTableSink {
87 DayPeriodRulesDataSink &outer;
88 RulesSink(DayPeriodRulesDataSink &outer) : outer(outer) {}
89 virtual ~RulesSink();
90
91 virtual ResourceTableSink *getOrCreateTableSink(const char *key, int32_t, UErrorCode &errorCode) {
92 if (U_FAILURE(errorCode)) { return NULL; }
93
94 outer.ruleSetNum = parseSetNum(key, errorCode);
95 return &outer.ruleSetSink;
96 }
97 } rulesSink;
98
99 // Data root -> rules -> a rule set.
100 struct RuleSetSink : public ResourceTableSink {
101 DayPeriodRulesDataSink &outer;
102 RuleSetSink(DayPeriodRulesDataSink &outer) : outer(outer) {}
103 virtual ~RuleSetSink();
104
105 virtual ResourceTableSink *getOrCreateTableSink(const char *key, int32_t, UErrorCode &errorCode) {
106 if (U_FAILURE(errorCode)) { return NULL; }
107
108 outer.period = DayPeriodRules::getDayPeriodFromString(key);
109 if (outer.period == DayPeriodRules::DAYPERIOD_UNKNOWN) {
110 errorCode = U_INVALID_FORMAT_ERROR;
111 return NULL;
112 }
113
114 return &outer.periodSink;
115 }
116
117 virtual void leave(UErrorCode &errorCode) {
118 if (U_FAILURE(errorCode)) { return; }
119
120 if (!data->rules[outer.ruleSetNum].allHoursAreSet()) {
121 errorCode = U_INVALID_FORMAT_ERROR;
122 }
123 }
124 } ruleSetSink;
125
126 // Data root -> rules -> a rule set -> a period (e.g. "morning1").
127 // Key-value pairs (e.g. before{6:00}) will be captured here.
128 // Arrays (e.g. before{6:00, 24:00}) will be redirected to the next sink.
129 struct PeriodSink : public ResourceTableSink {
130 DayPeriodRulesDataSink &outer;
131 PeriodSink(DayPeriodRulesDataSink &outer) : outer(outer) {}
132 virtual ~PeriodSink();
133
134 virtual void put(const char *key, const ResourceValue &value, UErrorCode &errorCode) {
135 if (U_FAILURE(errorCode)) { return; }
136
137 CutoffType type = getCutoffTypeFromString(key);
138 outer.addCutoff(type, value.getUnicodeString(errorCode), errorCode);
139 }
140
141 virtual ResourceArraySink *getOrCreateArraySink(const char *key, int32_t, UErrorCode &errorCode) {
142 if (U_FAILURE(errorCode)) { return NULL; }
143 outer.cutoffType = getCutoffTypeFromString(key);
144 return &outer.cutoffSink;
145 }
146
147 virtual void leave(UErrorCode &errorCode) {
148 if (U_FAILURE(errorCode)) { return; }
149
150 outer.setDayPeriodForHoursFromCutoffs(errorCode);
151 for (int32_t i = 0; i < UPRV_LENGTHOF(outer.cutoffs); ++i) {
152 outer.cutoffs[i] = 0;
153 }
154 }
155 } periodSink;
156
157 // Data root -> rules -> a rule set -> a period -> a cutoff type.
158 // Will enter this sink if 2+ times appear in a single cutoff type (e.g. before{6:00, 24:00}).
159 struct CutoffSink : public ResourceArraySink {
160 DayPeriodRulesDataSink &outer;
161 CutoffSink(DayPeriodRulesDataSink &outer) : outer(outer) {}
162 virtual ~CutoffSink();
163
164 virtual void put(int32_t, const ResourceValue &value, UErrorCode &errorCode) {
165 outer.addCutoff(outer.cutoffType, value.getUnicodeString(errorCode), errorCode);
166 }
167 } cutoffSink;
168
169 // Members.
170 int32_t cutoffs[25]; // [0] thru [24]: 24 is allowed in "before 24".
171
172 // "Path" to data.
173 int32_t ruleSetNum;
174 DayPeriodRules::DayPeriod period;
175 CutoffType cutoffType;
176
177 // Helpers.
178 static int32_t parseSetNum(const UnicodeString &setNumStr, UErrorCode &errorCode) {
179 CharString cs;
180 cs.appendInvariantChars(setNumStr, errorCode);
181 return parseSetNum(cs.data(), errorCode);
182 }
183
184 static int32_t parseSetNum(const char *setNumStr, UErrorCode &errorCode) {
185 if (U_FAILURE(errorCode)) { return -1; }
186
187 if (uprv_strncmp(setNumStr, "set", 3) != 0) {
188 errorCode = U_INVALID_FORMAT_ERROR;
189 return -1;
190 }
191
192 int32_t i = 3;
193 int32_t setNum = 0;
194 while (setNumStr[i] != 0) {
195 int32_t digit = setNumStr[i] - '0';
196 if (digit < 0 || 9 < digit) {
197 errorCode = U_INVALID_FORMAT_ERROR;
198 return -1;
199 }
200 setNum = 10 * setNum + digit;
201 ++i;
202 }
203
204 // Rule set number must not be zero. (0 is used to indicate "not found" by hashmap.)
205 // Currently ICU data conveniently starts numbering rule sets from 1.
206 if (setNum == 0) {
207 errorCode = U_INVALID_FORMAT_ERROR;
208 return -1;
209 } else {
210 return setNum;
211 }
212 }
213
214 void addCutoff(CutoffType type, UnicodeString hour_str, UErrorCode &errorCode) {
215 if (U_FAILURE(errorCode)) { return; }
216
217 if (type == CUTOFF_TYPE_UNKNOWN) {
218 errorCode = U_INVALID_FORMAT_ERROR;
219 return;
220 }
221
222 int32_t hour = parseHour(hour_str, errorCode);
223 if (U_FAILURE(errorCode)) { return; }
224
225 cutoffs[hour] |= 1 << type;
226 }
227
228 // Translate the cutoffs[] array to day period rules.
229 void setDayPeriodForHoursFromCutoffs(UErrorCode &errorCode) {
230 DayPeriodRules &rule = data->rules[ruleSetNum];
231
232 for (int32_t startHour = 0; startHour <= 24; ++startHour) {
233 // AT cutoffs must be either midnight or noon.
234 if (cutoffs[startHour] & (1 << CUTOFF_TYPE_AT)) {
235 if (startHour == 0 && period == DayPeriodRules::DAYPERIOD_MIDNIGHT) {
236 rule.fHasMidnight = TRUE;
237 } else if (startHour == 12 && period == DayPeriodRules::DAYPERIOD_NOON) {
238 rule.fHasNoon = TRUE;
239 } else {
240 errorCode = U_INVALID_FORMAT_ERROR; // Bad data.
241 return;
242 }
243 }
244
245 // FROM/AFTER and BEFORE must come in a pair.
246 if (cutoffs[startHour] & (1 << CUTOFF_TYPE_FROM) ||
247 cutoffs[startHour] & (1 << CUTOFF_TYPE_AFTER)) {
248 for (int32_t hour = startHour + 1;; ++hour) {
249 if (hour == startHour) {
250 // We've gone around the array once and can't find a BEFORE.
251 errorCode = U_INVALID_FORMAT_ERROR;
252 return;
253 }
254 if (hour == 25) { hour = 0; }
255 if (cutoffs[hour] & (1 << CUTOFF_TYPE_BEFORE)) {
256 rule.add(startHour, hour, period);
257 break;
258 }
259 }
260 }
261 }
262 }
263
264 // Translate "before" to CUTOFF_TYPE_BEFORE, for example.
265 static CutoffType getCutoffTypeFromString(const char *type_str) {
266 if (uprv_strcmp(type_str, "from") == 0) {
267 return CUTOFF_TYPE_FROM;
268 } else if (uprv_strcmp(type_str, "before") == 0) {
269 return CUTOFF_TYPE_BEFORE;
270 } else if (uprv_strcmp(type_str, "after") == 0) {
271 return CUTOFF_TYPE_AFTER;
272 } else if (uprv_strcmp(type_str, "at") == 0) {
273 return CUTOFF_TYPE_AT;
274 } else {
275 return CUTOFF_TYPE_UNKNOWN;
276 }
277 }
278
279 // Gets the numerical value of the hour from the Unicode string.
280 static int32_t parseHour(const UnicodeString &time, UErrorCode &errorCode) {
281 if (U_FAILURE(errorCode)) {
282 return 0;
283 }
284
285 int32_t hourLimit = time.length() - 3;
286 // `time` must look like "x:00" or "xx:00".
287 // If length is wrong or `time` doesn't end with ":00", error out.
288 if ((hourLimit != 1 && hourLimit != 2) ||
289 time[hourLimit] != 0x3A || time[hourLimit + 1] != 0x30 ||
290 time[hourLimit + 2] != 0x30) {
291 errorCode = U_INVALID_FORMAT_ERROR;
292 return 0;
293 }
294
295 // If `time` doesn't begin with a number in [0, 24], error out.
296 // Note: "24:00" is possible in "before 24:00".
297 int32_t hour = time[0] - 0x30;
298 if (hour < 0 || 9 < hour) {
299 errorCode = U_INVALID_FORMAT_ERROR;
300 return 0;
301 }
302 if (hourLimit == 2) {
303 int32_t hourDigit2 = time[1] - 0x30;
304 if (hourDigit2 < 0 || 9 < hourDigit2) {
305 errorCode = U_INVALID_FORMAT_ERROR;
306 return 0;
307 }
308 hour = hour * 10 + hourDigit2;
309 if (hour > 24) {
310 errorCode = U_INVALID_FORMAT_ERROR;
311 return 0;
312 }
313 }
314
315 return hour;
316 }
317 }; // struct DayPeriodRulesDataSink
318
319 struct DayPeriodRulesCountSink : public ResourceTableSink {
320 virtual ~DayPeriodRulesCountSink();
321 virtual ResourceTableSink *getOrCreateTableSink(const char *key, int32_t, UErrorCode &errorCode) {
322 if (U_FAILURE(errorCode)) { return NULL; }
323
324 int32_t setNum = DayPeriodRulesDataSink::parseSetNum(key, errorCode);
325 if (setNum > data->maxRuleSetNum) {
326 data->maxRuleSetNum = setNum;
327 }
328
329 return NULL;
330 }
331 };
332
333 // Out-of-line virtual destructors.
334 DayPeriodRulesDataSink::LocalesSink::~LocalesSink() {}
335 DayPeriodRulesDataSink::CutoffSink::~CutoffSink() {}
336 DayPeriodRulesDataSink::PeriodSink::~PeriodSink() {}
337 DayPeriodRulesDataSink::RuleSetSink::~RuleSetSink() {}
338 DayPeriodRulesDataSink::RulesSink::~RulesSink() {}
339 DayPeriodRulesDataSink::~DayPeriodRulesDataSink() {}
340
341 DayPeriodRulesCountSink::~DayPeriodRulesCountSink() {}
342
343 namespace {
344
345 UInitOnce initOnce = U_INITONCE_INITIALIZER;
346
347 UBool dayPeriodRulesCleanup() {
348 delete[] data->rules;
349 uhash_close(data->localeToRuleSetNumMap);
350 delete data;
351 data = NULL;
352 return TRUE;
353 }
354
355 } // namespace
356
357 void DayPeriodRules::load(UErrorCode &errorCode) {
358 if (U_FAILURE(errorCode)) {
359 return;
360 }
361
362 data = new DayPeriodRulesData();
363 data->localeToRuleSetNumMap = uhash_open(uhash_hashChars, uhash_compareChars, NULL, &errorCode);
364 LocalUResourceBundlePointer rb_dayPeriods(ures_openDirect(NULL, "dayPeriods", &errorCode));
365
366 // Get the largest rule set number (so we allocate enough objects).
367 DayPeriodRulesCountSink countSink;
368 ures_getAllTableItemsWithFallback(rb_dayPeriods.getAlias(), "rules", countSink, errorCode);
369
370 // Populate rules.
371 DayPeriodRulesDataSink sink;
372 ures_getAllTableItemsWithFallback(rb_dayPeriods.getAlias(), "", sink, errorCode);
373
374 ucln_i18n_registerCleanup(UCLN_I18N_DAYPERIODRULES, dayPeriodRulesCleanup);
375 }
376
377 const DayPeriodRules *DayPeriodRules::getInstance(const Locale &locale, UErrorCode &errorCode) {
378 umtx_initOnce(initOnce, DayPeriodRules::load, errorCode);
379
380 // If the entire day period rules data doesn't conform to spec (even if the part we want
381 // does), return NULL.
382 if(U_FAILURE(errorCode)) { return NULL; }
383
384 const char *localeCode = locale.getName();
385 char name[ULOC_FULLNAME_CAPACITY];
386 char parentName[ULOC_FULLNAME_CAPACITY];
387
388 if (uprv_strlen(localeCode) < ULOC_FULLNAME_CAPACITY) {
389 uprv_strcpy(name, localeCode);
390
391 // Treat empty string as root.
392 if (*name == '\0') {
393 uprv_strcpy(name, "root");
394 }
395 } else {
396 errorCode = U_BUFFER_OVERFLOW_ERROR;
397 return NULL;
398 }
399
400 int32_t ruleSetNum = 0; // NB there is no rule set 0 and 0 is returned upon lookup failure.
401 while (*name != '\0') {
402 ruleSetNum = uhash_geti(data->localeToRuleSetNumMap, name);
403 if (ruleSetNum == 0) {
404 // name and parentName can't be the same pointer, so fill in parent then copy to child.
405 uloc_getParent(name, parentName, ULOC_FULLNAME_CAPACITY, &errorCode);
406 if (*parentName == '\0') {
407 // Saves a lookup in the hash table.
408 break;
409 }
410 uprv_strcpy(name, parentName);
411 } else {
412 break;
413 }
414 }
415
416 if (ruleSetNum <= 0 || data->rules[ruleSetNum].getDayPeriodForHour(0) == DAYPERIOD_UNKNOWN) {
417 // If day period for hour 0 is UNKNOWN then day period for all hours are UNKNOWN.
418 // Data doesn't exist even with fallback.
419 return NULL;
420 } else {
421 return &data->rules[ruleSetNum];
422 }
423 }
424
425 DayPeriodRules::DayPeriodRules() : fHasMidnight(FALSE), fHasNoon(FALSE) {
426 for (int32_t i = 0; i < 24; ++i) {
427 fDayPeriodForHour[i] = DayPeriodRules::DAYPERIOD_UNKNOWN;
428 }
429 }
430
431 double DayPeriodRules::getMidPointForDayPeriod(
432 DayPeriodRules::DayPeriod dayPeriod, UErrorCode &errorCode) const {
433 if (U_FAILURE(errorCode)) { return -1; }
434
435 int32_t startHour = getStartHourForDayPeriod(dayPeriod, errorCode);
436 int32_t endHour = getEndHourForDayPeriod(dayPeriod, errorCode);
437 // Can't obtain startHour or endHour; bail out.
438 if (U_FAILURE(errorCode)) { return -1; }
439
440 double midPoint = (startHour + endHour) / 2.0;
441
442 if (startHour > endHour) {
443 // dayPeriod wraps around midnight. Shift midPoint by 12 hours, in the direction that
444 // lands it in [0, 24).
445 midPoint += 12;
446 if (midPoint >= 24) {
447 midPoint -= 24;
448 }
449 }
450
451 return midPoint;
452 }
453
454 int32_t DayPeriodRules::getStartHourForDayPeriod(
455 DayPeriodRules::DayPeriod dayPeriod, UErrorCode &errorCode) const {
456 if (U_FAILURE(errorCode)) { return -1; }
457
458 if (dayPeriod == DAYPERIOD_MIDNIGHT) { return 0; }
459 if (dayPeriod == DAYPERIOD_NOON) { return 12; }
460
461 if (fDayPeriodForHour[0] == dayPeriod && fDayPeriodForHour[23] == dayPeriod) {
462 // dayPeriod wraps around midnight. Start hour is later than end hour.
463 for (int32_t i = 22; i >= 1; --i) {
464 if (fDayPeriodForHour[i] != dayPeriod) {
465 return (i + 1);
466 }
467 }
468 } else {
469 for (int32_t i = 0; i <= 23; ++i) {
470 if (fDayPeriodForHour[i] == dayPeriod) {
471 return i;
472 }
473 }
474 }
475
476 // dayPeriod doesn't exist in rule set; set error and exit.
477 errorCode = U_ILLEGAL_ARGUMENT_ERROR;
478 return -1;
479 }
480
481 int32_t DayPeriodRules::getEndHourForDayPeriod(
482 DayPeriodRules::DayPeriod dayPeriod, UErrorCode &errorCode) const {
483 if (U_FAILURE(errorCode)) { return -1; }
484
485 if (dayPeriod == DAYPERIOD_MIDNIGHT) { return 0; }
486 if (dayPeriod == DAYPERIOD_NOON) { return 12; }
487
488 if (fDayPeriodForHour[0] == dayPeriod && fDayPeriodForHour[23] == dayPeriod) {
489 // dayPeriod wraps around midnight. End hour is before start hour.
490 for (int32_t i = 1; i <= 22; ++i) {
491 if (fDayPeriodForHour[i] != dayPeriod) {
492 // i o'clock is when a new period starts, therefore when the old period ends.
493 return i;
494 }
495 }
496 } else {
497 for (int32_t i = 23; i >= 0; --i) {
498 if (fDayPeriodForHour[i] == dayPeriod) {
499 return (i + 1);
500 }
501 }
502 }
503
504 // dayPeriod doesn't exist in rule set; set error and exit.
505 errorCode = U_ILLEGAL_ARGUMENT_ERROR;
506 return -1;
507 }
508
509 DayPeriodRules::DayPeriod DayPeriodRules::getDayPeriodFromString(const char *type_str) {
510 if (uprv_strcmp(type_str, "midnight") == 0) {
511 return DAYPERIOD_MIDNIGHT;
512 } else if (uprv_strcmp(type_str, "noon") == 0) {
513 return DAYPERIOD_NOON;
514 } else if (uprv_strcmp(type_str, "morning1") == 0) {
515 return DAYPERIOD_MORNING1;
516 } else if (uprv_strcmp(type_str, "afternoon1") == 0) {
517 return DAYPERIOD_AFTERNOON1;
518 } else if (uprv_strcmp(type_str, "evening1") == 0) {
519 return DAYPERIOD_EVENING1;
520 } else if (uprv_strcmp(type_str, "night1") == 0) {
521 return DAYPERIOD_NIGHT1;
522 } else if (uprv_strcmp(type_str, "morning2") == 0) {
523 return DAYPERIOD_MORNING2;
524 } else if (uprv_strcmp(type_str, "afternoon2") == 0) {
525 return DAYPERIOD_AFTERNOON2;
526 } else if (uprv_strcmp(type_str, "evening2") == 0) {
527 return DAYPERIOD_EVENING2;
528 } else if (uprv_strcmp(type_str, "night2") == 0) {
529 return DAYPERIOD_NIGHT2;
530 } else if (uprv_strcmp(type_str, "am") == 0) {
531 return DAYPERIOD_AM;
532 } else if (uprv_strcmp(type_str, "pm") == 0) {
533 return DAYPERIOD_PM;
534 } else {
535 return DAYPERIOD_UNKNOWN;
536 }
537 }
538
539 void DayPeriodRules::add(int32_t startHour, int32_t limitHour, DayPeriod period) {
540 for (int32_t i = startHour; i != limitHour; ++i) {
541 if (i == 24) { i = 0; }
542 fDayPeriodForHour[i] = period;
543 }
544 }
545
546 UBool DayPeriodRules::allHoursAreSet() {
547 for (int32_t i = 0; i < 24; ++i) {
548 if (fDayPeriodForHour[i] == DAYPERIOD_UNKNOWN) { return FALSE; }
549 }
550
551 return TRUE;
552 }
553
554
555
556 U_NAMESPACE_END