2 *******************************************************************************
3 * Copyright (C) 2016, International Business Machines
4 * Corporation and others. All Rights Reserved.
5 *******************************************************************************
8 * created on: 2016-01-20
12 #include "dayperiodrules.h"
14 #include "unicode/ures.h"
27 struct DayPeriodRulesData
: public UMemory
{
28 DayPeriodRulesData() : localeToRuleSetNumMap(NULL
), rules(NULL
), maxRuleSetNum(0) {}
30 UHashtable
*localeToRuleSetNumMap
;
31 DayPeriodRules
*rules
;
32 int32_t maxRuleSetNum
;
36 CUTOFF_TYPE_UNKNOWN
= -1,
38 CUTOFF_TYPE_AFTER
, // TODO: AFTER is deprecated in CLDR 29. Remove.
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; }
51 virtual ~DayPeriodRulesDataSink();
54 virtual ResourceTableSink
*getOrCreateTableSink(const char *key
, int32_t, UErrorCode
&errorCode
) {
55 if (U_FAILURE(errorCode
)) { return NULL
; }
57 if (uprv_strcmp(key
, "locales") == 0) {
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
;
72 // Data root -> locales.
73 struct LocalesSink
: public ResourceTableSink
{
74 virtual ~LocalesSink();
76 virtual void put(const char *key
, const ResourceValue
&value
, UErrorCode
&errorCode
) {
77 if (U_FAILURE(errorCode
)) { return; }
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
);
85 // Data root -> rules.
86 struct RulesSink
: public ResourceTableSink
{
87 DayPeriodRulesDataSink
&outer
;
88 RulesSink(DayPeriodRulesDataSink
&outer
) : outer(outer
) {}
91 virtual ResourceTableSink
*getOrCreateTableSink(const char *key
, int32_t, UErrorCode
&errorCode
) {
92 if (U_FAILURE(errorCode
)) { return NULL
; }
94 outer
.ruleSetNum
= parseSetNum(key
, errorCode
);
95 return &outer
.ruleSetSink
;
99 // Data root -> rules -> a rule set.
100 struct RuleSetSink
: public ResourceTableSink
{
101 DayPeriodRulesDataSink
&outer
;
102 RuleSetSink(DayPeriodRulesDataSink
&outer
) : outer(outer
) {}
103 virtual ~RuleSetSink();
105 virtual ResourceTableSink
*getOrCreateTableSink(const char *key
, int32_t, UErrorCode
&errorCode
) {
106 if (U_FAILURE(errorCode
)) { return NULL
; }
108 outer
.period
= DayPeriodRules::getDayPeriodFromString(key
);
109 if (outer
.period
== DayPeriodRules::DAYPERIOD_UNKNOWN
) {
110 errorCode
= U_INVALID_FORMAT_ERROR
;
114 return &outer
.periodSink
;
117 virtual void leave(UErrorCode
&errorCode
) {
118 if (U_FAILURE(errorCode
)) { return; }
120 if (!data
->rules
[outer
.ruleSetNum
].allHoursAreSet()) {
121 errorCode
= U_INVALID_FORMAT_ERROR
;
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();
134 virtual void put(const char *key
, const ResourceValue
&value
, UErrorCode
&errorCode
) {
135 if (U_FAILURE(errorCode
)) { return; }
137 CutoffType type
= getCutoffTypeFromString(key
);
138 outer
.addCutoff(type
, value
.getUnicodeString(errorCode
), errorCode
);
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
;
147 virtual void leave(UErrorCode
&errorCode
) {
148 if (U_FAILURE(errorCode
)) { return; }
150 outer
.setDayPeriodForHoursFromCutoffs(errorCode
);
151 for (int32_t i
= 0; i
< UPRV_LENGTHOF(outer
.cutoffs
); ++i
) {
152 outer
.cutoffs
[i
] = 0;
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();
164 virtual void put(int32_t, const ResourceValue
&value
, UErrorCode
&errorCode
) {
165 outer
.addCutoff(outer
.cutoffType
, value
.getUnicodeString(errorCode
), errorCode
);
170 int32_t cutoffs
[25]; // [0] thru [24]: 24 is allowed in "before 24".
174 DayPeriodRules::DayPeriod period
;
175 CutoffType cutoffType
;
178 static int32_t parseSetNum(const UnicodeString
&setNumStr
, UErrorCode
&errorCode
) {
180 cs
.appendInvariantChars(setNumStr
, errorCode
);
181 return parseSetNum(cs
.data(), errorCode
);
184 static int32_t parseSetNum(const char *setNumStr
, UErrorCode
&errorCode
) {
185 if (U_FAILURE(errorCode
)) { return -1; }
187 if (uprv_strncmp(setNumStr
, "set", 3) != 0) {
188 errorCode
= U_INVALID_FORMAT_ERROR
;
194 while (setNumStr
[i
] != 0) {
195 int32_t digit
= setNumStr
[i
] - '0';
196 if (digit
< 0 || 9 < digit
) {
197 errorCode
= U_INVALID_FORMAT_ERROR
;
200 setNum
= 10 * setNum
+ digit
;
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.
207 errorCode
= U_INVALID_FORMAT_ERROR
;
214 void addCutoff(CutoffType type
, UnicodeString hour_str
, UErrorCode
&errorCode
) {
215 if (U_FAILURE(errorCode
)) { return; }
217 if (type
== CUTOFF_TYPE_UNKNOWN
) {
218 errorCode
= U_INVALID_FORMAT_ERROR
;
222 int32_t hour
= parseHour(hour_str
, errorCode
);
223 if (U_FAILURE(errorCode
)) { return; }
225 cutoffs
[hour
] |= 1 << type
;
228 // Translate the cutoffs[] array to day period rules.
229 void setDayPeriodForHoursFromCutoffs(UErrorCode
&errorCode
) {
230 DayPeriodRules
&rule
= data
->rules
[ruleSetNum
];
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
;
240 errorCode
= U_INVALID_FORMAT_ERROR
; // Bad data.
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
;
254 if (hour
== 25) { hour
= 0; }
255 if (cutoffs
[hour
] & (1 << CUTOFF_TYPE_BEFORE
)) {
256 rule
.add(startHour
, hour
, period
);
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
;
275 return CUTOFF_TYPE_UNKNOWN
;
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
)) {
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
;
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
;
302 if (hourLimit
== 2) {
303 int32_t hourDigit2
= time
[1] - 0x30;
304 if (hourDigit2
< 0 || 9 < hourDigit2
) {
305 errorCode
= U_INVALID_FORMAT_ERROR
;
308 hour
= hour
* 10 + hourDigit2
;
310 errorCode
= U_INVALID_FORMAT_ERROR
;
317 }; // struct DayPeriodRulesDataSink
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
; }
324 int32_t setNum
= DayPeriodRulesDataSink::parseSetNum(key
, errorCode
);
325 if (setNum
> data
->maxRuleSetNum
) {
326 data
->maxRuleSetNum
= setNum
;
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() {}
341 DayPeriodRulesCountSink::~DayPeriodRulesCountSink() {}
345 UInitOnce initOnce
= U_INITONCE_INITIALIZER
;
347 UBool
dayPeriodRulesCleanup() {
348 delete[] data
->rules
;
349 uhash_close(data
->localeToRuleSetNumMap
);
357 void DayPeriodRules::load(UErrorCode
&errorCode
) {
358 if (U_FAILURE(errorCode
)) {
362 data
= new DayPeriodRulesData();
363 data
->localeToRuleSetNumMap
= uhash_open(uhash_hashChars
, uhash_compareChars
, NULL
, &errorCode
);
364 LocalUResourceBundlePointer
rb_dayPeriods(ures_openDirect(NULL
, "dayPeriods", &errorCode
));
366 // Get the largest rule set number (so we allocate enough objects).
367 DayPeriodRulesCountSink countSink
;
368 ures_getAllTableItemsWithFallback(rb_dayPeriods
.getAlias(), "rules", countSink
, errorCode
);
371 DayPeriodRulesDataSink sink
;
372 ures_getAllTableItemsWithFallback(rb_dayPeriods
.getAlias(), "", sink
, errorCode
);
374 ucln_i18n_registerCleanup(UCLN_I18N_DAYPERIODRULES
, dayPeriodRulesCleanup
);
377 const DayPeriodRules
*DayPeriodRules::getInstance(const Locale
&locale
, UErrorCode
&errorCode
) {
378 umtx_initOnce(initOnce
, DayPeriodRules::load
, errorCode
);
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
; }
384 const char *localeCode
= locale
.getName();
385 char name
[ULOC_FULLNAME_CAPACITY
];
386 char parentName
[ULOC_FULLNAME_CAPACITY
];
388 if (uprv_strlen(localeCode
) < ULOC_FULLNAME_CAPACITY
) {
389 uprv_strcpy(name
, localeCode
);
391 // Treat empty string as root.
393 uprv_strcpy(name
, "root");
396 errorCode
= U_BUFFER_OVERFLOW_ERROR
;
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.
410 uprv_strcpy(name
, parentName
);
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.
421 return &data
->rules
[ruleSetNum
];
425 DayPeriodRules::DayPeriodRules() : fHasMidnight(FALSE
), fHasNoon(FALSE
) {
426 for (int32_t i
= 0; i
< 24; ++i
) {
427 fDayPeriodForHour
[i
] = DayPeriodRules::DAYPERIOD_UNKNOWN
;
431 double DayPeriodRules::getMidPointForDayPeriod(
432 DayPeriodRules::DayPeriod dayPeriod
, UErrorCode
&errorCode
) const {
433 if (U_FAILURE(errorCode
)) { return -1; }
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; }
440 double midPoint
= (startHour
+ endHour
) / 2.0;
442 if (startHour
> endHour
) {
443 // dayPeriod wraps around midnight. Shift midPoint by 12 hours, in the direction that
444 // lands it in [0, 24).
446 if (midPoint
>= 24) {
454 int32_t DayPeriodRules::getStartHourForDayPeriod(
455 DayPeriodRules::DayPeriod dayPeriod
, UErrorCode
&errorCode
) const {
456 if (U_FAILURE(errorCode
)) { return -1; }
458 if (dayPeriod
== DAYPERIOD_MIDNIGHT
) { return 0; }
459 if (dayPeriod
== DAYPERIOD_NOON
) { return 12; }
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
) {
469 for (int32_t i
= 0; i
<= 23; ++i
) {
470 if (fDayPeriodForHour
[i
] == dayPeriod
) {
476 // dayPeriod doesn't exist in rule set; set error and exit.
477 errorCode
= U_ILLEGAL_ARGUMENT_ERROR
;
481 int32_t DayPeriodRules::getEndHourForDayPeriod(
482 DayPeriodRules::DayPeriod dayPeriod
, UErrorCode
&errorCode
) const {
483 if (U_FAILURE(errorCode
)) { return -1; }
485 if (dayPeriod
== DAYPERIOD_MIDNIGHT
) { return 0; }
486 if (dayPeriod
== DAYPERIOD_NOON
) { return 12; }
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.
497 for (int32_t i
= 23; i
>= 0; --i
) {
498 if (fDayPeriodForHour
[i
] == dayPeriod
) {
504 // dayPeriod doesn't exist in rule set; set error and exit.
505 errorCode
= U_ILLEGAL_ARGUMENT_ERROR
;
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) {
532 } else if (uprv_strcmp(type_str
, "pm") == 0) {
535 return DAYPERIOD_UNKNOWN
;
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
;
546 UBool
DayPeriodRules::allHoursAreSet() {
547 for (int32_t i
= 0; i
< 24; ++i
) {
548 if (fDayPeriodForHour
[i
] == DAYPERIOD_UNKNOWN
) { return FALSE
; }