1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
4 *******************************************************************************
5 * Copyright (C) 2016, International Business Machines
6 * Corporation and others. All Rights Reserved.
7 *******************************************************************************
10 * created on: 2016-01-20
14 #include "dayperiodrules.h"
16 #include "unicode/ures.h"
29 struct DayPeriodRulesData
: public UMemory
{
30 DayPeriodRulesData() : localeToRuleSetNumMap(NULL
), rules(NULL
), maxRuleSetNum(0) {}
32 UHashtable
*localeToRuleSetNumMap
;
33 DayPeriodRules
*rules
;
34 int32_t maxRuleSetNum
;
38 CUTOFF_TYPE_UNKNOWN
= -1,
40 CUTOFF_TYPE_AFTER
, // TODO: AFTER is deprecated in CLDR 29. Remove.
47 struct DayPeriodRulesDataSink
: public ResourceSink
{
48 DayPeriodRulesDataSink() {
49 for (int32_t i
= 0; i
< UPRV_LENGTHOF(cutoffs
); ++i
) { cutoffs
[i
] = 0; }
51 virtual ~DayPeriodRulesDataSink();
53 virtual void put(const char *key
, ResourceValue
&value
, UBool
, UErrorCode
&errorCode
) {
54 ResourceTable dayPeriodData
= value
.getTable(errorCode
);
55 if (U_FAILURE(errorCode
)) { return; }
57 for (int32_t i
= 0; dayPeriodData
.getKeyAndValue(i
, key
, value
); ++i
) {
58 if (uprv_strcmp(key
, "locales") == 0) {
59 ResourceTable locales
= value
.getTable(errorCode
);
60 if (U_FAILURE(errorCode
)) { return; }
62 for (int32_t j
= 0; locales
.getKeyAndValue(j
, key
, value
); ++j
) {
63 UnicodeString setNum_str
= value
.getUnicodeString(errorCode
);
64 int32_t setNum
= parseSetNum(setNum_str
, errorCode
);
65 uhash_puti(data
->localeToRuleSetNumMap
, const_cast<char *>(key
), setNum
, &errorCode
);
67 } else if (uprv_strcmp(key
, "rules") == 0) {
68 // Allocate one more than needed to skip [0]. See comment in parseSetNum().
69 data
->rules
= new DayPeriodRules
[data
->maxRuleSetNum
+ 1];
70 if (data
->rules
== NULL
) {
71 errorCode
= U_MEMORY_ALLOCATION_ERROR
;
74 ResourceTable rules
= value
.getTable(errorCode
);
75 processRules(rules
, key
, value
, errorCode
);
76 if (U_FAILURE(errorCode
)) { return; }
81 void processRules(const ResourceTable
&rules
, const char *key
,
82 ResourceValue
&value
, UErrorCode
&errorCode
) {
83 if (U_FAILURE(errorCode
)) { return; }
85 for (int32_t i
= 0; rules
.getKeyAndValue(i
, key
, value
); ++i
) {
86 ruleSetNum
= parseSetNum(key
, errorCode
);
87 ResourceTable ruleSet
= value
.getTable(errorCode
);
88 if (U_FAILURE(errorCode
)) { return; }
90 for (int32_t j
= 0; ruleSet
.getKeyAndValue(j
, key
, value
); ++j
) {
91 period
= DayPeriodRules::getDayPeriodFromString(key
);
92 if (period
== DayPeriodRules::DAYPERIOD_UNKNOWN
) {
93 errorCode
= U_INVALID_FORMAT_ERROR
;
96 ResourceTable periodDefinition
= value
.getTable(errorCode
);
97 if (U_FAILURE(errorCode
)) { return; }
99 for (int32_t k
= 0; periodDefinition
.getKeyAndValue(k
, key
, value
); ++k
) {
100 if (value
.getType() == URES_STRING
) {
101 // Key-value pairs (e.g. before{6:00}).
102 CutoffType type
= getCutoffTypeFromString(key
);
103 addCutoff(type
, value
.getUnicodeString(errorCode
), errorCode
);
104 if (U_FAILURE(errorCode
)) { return; }
106 // Arrays (e.g. before{6:00, 24:00}).
107 cutoffType
= getCutoffTypeFromString(key
);
108 ResourceArray cutoffArray
= value
.getArray(errorCode
);
109 if (U_FAILURE(errorCode
)) { return; }
111 int32_t length
= cutoffArray
.getSize();
112 for (int32_t l
= 0; l
< length
; ++l
) {
113 cutoffArray
.getValue(l
, value
);
114 addCutoff(cutoffType
, value
.getUnicodeString(errorCode
), errorCode
);
115 if (U_FAILURE(errorCode
)) { return; }
119 setDayPeriodForHoursFromCutoffs(errorCode
);
120 for (int32_t k
= 0; k
< UPRV_LENGTHOF(cutoffs
); ++k
) {
125 if (!data
->rules
[ruleSetNum
].allHoursAreSet()) {
126 errorCode
= U_INVALID_FORMAT_ERROR
;
133 int32_t cutoffs
[25]; // [0] thru [24]: 24 is allowed in "before 24".
137 DayPeriodRules::DayPeriod period
;
138 CutoffType cutoffType
;
141 static int32_t parseSetNum(const UnicodeString
&setNumStr
, UErrorCode
&errorCode
) {
143 cs
.appendInvariantChars(setNumStr
, errorCode
);
144 return parseSetNum(cs
.data(), errorCode
);
147 static int32_t parseSetNum(const char *setNumStr
, UErrorCode
&errorCode
) {
148 if (U_FAILURE(errorCode
)) { return -1; }
150 if (uprv_strncmp(setNumStr
, "set", 3) != 0) {
151 errorCode
= U_INVALID_FORMAT_ERROR
;
157 while (setNumStr
[i
] != 0) {
158 int32_t digit
= setNumStr
[i
] - '0';
159 if (digit
< 0 || 9 < digit
) {
160 errorCode
= U_INVALID_FORMAT_ERROR
;
163 setNum
= 10 * setNum
+ digit
;
167 // Rule set number must not be zero. (0 is used to indicate "not found" by hashmap.)
168 // Currently ICU data conveniently starts numbering rule sets from 1.
170 errorCode
= U_INVALID_FORMAT_ERROR
;
177 void addCutoff(CutoffType type
, const UnicodeString
&hour_str
, UErrorCode
&errorCode
) {
178 if (U_FAILURE(errorCode
)) { return; }
180 if (type
== CUTOFF_TYPE_UNKNOWN
) {
181 errorCode
= U_INVALID_FORMAT_ERROR
;
185 int32_t hour
= parseHour(hour_str
, errorCode
);
186 if (U_FAILURE(errorCode
)) { return; }
188 cutoffs
[hour
] |= 1 << type
;
191 // Translate the cutoffs[] array to day period rules.
192 void setDayPeriodForHoursFromCutoffs(UErrorCode
&errorCode
) {
193 DayPeriodRules
&rule
= data
->rules
[ruleSetNum
];
195 for (int32_t startHour
= 0; startHour
<= 24; ++startHour
) {
196 // AT cutoffs must be either midnight or noon.
197 if (cutoffs
[startHour
] & (1 << CUTOFF_TYPE_AT
)) {
198 if (startHour
== 0 && period
== DayPeriodRules::DAYPERIOD_MIDNIGHT
) {
199 rule
.fHasMidnight
= TRUE
;
200 } else if (startHour
== 12 && period
== DayPeriodRules::DAYPERIOD_NOON
) {
201 rule
.fHasNoon
= TRUE
;
203 errorCode
= U_INVALID_FORMAT_ERROR
; // Bad data.
208 // FROM/AFTER and BEFORE must come in a pair.
209 if (cutoffs
[startHour
] & (1 << CUTOFF_TYPE_FROM
) ||
210 cutoffs
[startHour
] & (1 << CUTOFF_TYPE_AFTER
)) {
211 for (int32_t hour
= startHour
+ 1;; ++hour
) {
212 if (hour
== startHour
) {
213 // We've gone around the array once and can't find a BEFORE.
214 errorCode
= U_INVALID_FORMAT_ERROR
;
217 if (hour
== 25) { hour
= 0; }
218 if (cutoffs
[hour
] & (1 << CUTOFF_TYPE_BEFORE
)) {
219 rule
.add(startHour
, hour
, period
);
227 // Translate "before" to CUTOFF_TYPE_BEFORE, for example.
228 static CutoffType
getCutoffTypeFromString(const char *type_str
) {
229 if (uprv_strcmp(type_str
, "from") == 0) {
230 return CUTOFF_TYPE_FROM
;
231 } else if (uprv_strcmp(type_str
, "before") == 0) {
232 return CUTOFF_TYPE_BEFORE
;
233 } else if (uprv_strcmp(type_str
, "after") == 0) {
234 return CUTOFF_TYPE_AFTER
;
235 } else if (uprv_strcmp(type_str
, "at") == 0) {
236 return CUTOFF_TYPE_AT
;
238 return CUTOFF_TYPE_UNKNOWN
;
242 // Gets the numerical value of the hour from the Unicode string.
243 static int32_t parseHour(const UnicodeString
&time
, UErrorCode
&errorCode
) {
244 if (U_FAILURE(errorCode
)) {
248 int32_t hourLimit
= time
.length() - 3;
249 // `time` must look like "x:00" or "xx:00".
250 // If length is wrong or `time` doesn't end with ":00", error out.
251 if ((hourLimit
!= 1 && hourLimit
!= 2) ||
252 time
[hourLimit
] != 0x3A || time
[hourLimit
+ 1] != 0x30 ||
253 time
[hourLimit
+ 2] != 0x30) {
254 errorCode
= U_INVALID_FORMAT_ERROR
;
258 // If `time` doesn't begin with a number in [0, 24], error out.
259 // Note: "24:00" is possible in "before 24:00".
260 int32_t hour
= time
[0] - 0x30;
261 if (hour
< 0 || 9 < hour
) {
262 errorCode
= U_INVALID_FORMAT_ERROR
;
265 if (hourLimit
== 2) {
266 int32_t hourDigit2
= time
[1] - 0x30;
267 if (hourDigit2
< 0 || 9 < hourDigit2
) {
268 errorCode
= U_INVALID_FORMAT_ERROR
;
271 hour
= hour
* 10 + hourDigit2
;
273 errorCode
= U_INVALID_FORMAT_ERROR
;
280 }; // struct DayPeriodRulesDataSink
282 struct DayPeriodRulesCountSink
: public ResourceSink
{
283 virtual ~DayPeriodRulesCountSink();
285 virtual void put(const char *key
, ResourceValue
&value
, UBool
, UErrorCode
&errorCode
) {
286 ResourceTable rules
= value
.getTable(errorCode
);
287 if (U_FAILURE(errorCode
)) { return; }
289 for (int32_t i
= 0; rules
.getKeyAndValue(i
, key
, value
); ++i
) {
290 int32_t setNum
= DayPeriodRulesDataSink::parseSetNum(key
, errorCode
);
291 if (setNum
> data
->maxRuleSetNum
) {
292 data
->maxRuleSetNum
= setNum
;
298 // Out-of-line virtual destructors.
299 DayPeriodRulesDataSink::~DayPeriodRulesDataSink() {}
300 DayPeriodRulesCountSink::~DayPeriodRulesCountSink() {}
304 UInitOnce initOnce
= U_INITONCE_INITIALIZER
;
306 U_CFUNC UBool U_CALLCONV
dayPeriodRulesCleanup() {
307 delete[] data
->rules
;
308 uhash_close(data
->localeToRuleSetNumMap
);
316 void U_CALLCONV
DayPeriodRules::load(UErrorCode
&errorCode
) {
317 if (U_FAILURE(errorCode
)) {
321 data
= new DayPeriodRulesData();
322 data
->localeToRuleSetNumMap
= uhash_open(uhash_hashChars
, uhash_compareChars
, NULL
, &errorCode
);
323 LocalUResourceBundlePointer
rb_dayPeriods(ures_openDirect(NULL
, "dayPeriods", &errorCode
));
325 // Get the largest rule set number (so we allocate enough objects).
326 DayPeriodRulesCountSink countSink
;
327 ures_getAllItemsWithFallback(rb_dayPeriods
.getAlias(), "rules", countSink
, errorCode
);
330 DayPeriodRulesDataSink sink
;
331 ures_getAllItemsWithFallback(rb_dayPeriods
.getAlias(), "", sink
, errorCode
);
333 ucln_i18n_registerCleanup(UCLN_I18N_DAYPERIODRULES
, dayPeriodRulesCleanup
);
336 const DayPeriodRules
*DayPeriodRules::getInstance(const Locale
&locale
, UErrorCode
&errorCode
) {
337 umtx_initOnce(initOnce
, DayPeriodRules::load
, errorCode
);
339 // If the entire day period rules data doesn't conform to spec (even if the part we want
340 // does), return NULL.
341 if(U_FAILURE(errorCode
)) { return NULL
; }
343 const char *localeCode
= locale
.getBaseName();
344 char name
[ULOC_FULLNAME_CAPACITY
];
345 char parentName
[ULOC_FULLNAME_CAPACITY
];
347 if (uprv_strlen(localeCode
) < ULOC_FULLNAME_CAPACITY
) {
348 uprv_strcpy(name
, localeCode
);
350 // Treat empty string as root.
352 uprv_strcpy(name
, "root");
355 errorCode
= U_BUFFER_OVERFLOW_ERROR
;
359 int32_t ruleSetNum
= 0; // NB there is no rule set 0 and 0 is returned upon lookup failure.
360 while (*name
!= '\0') {
361 ruleSetNum
= uhash_geti(data
->localeToRuleSetNumMap
, name
);
362 if (ruleSetNum
== 0) {
363 // name and parentName can't be the same pointer, so fill in parent then copy to child.
364 uloc_getParent(name
, parentName
, ULOC_FULLNAME_CAPACITY
, &errorCode
);
365 if (*parentName
== '\0') {
366 // Saves a lookup in the hash table.
369 uprv_strcpy(name
, parentName
);
375 if (ruleSetNum
<= 0 || data
->rules
[ruleSetNum
].getDayPeriodForHour(0) == DAYPERIOD_UNKNOWN
) {
376 // If day period for hour 0 is UNKNOWN then day period for all hours are UNKNOWN.
377 // Data doesn't exist even with fallback.
380 return &data
->rules
[ruleSetNum
];
384 DayPeriodRules::DayPeriodRules() : fHasMidnight(FALSE
), fHasNoon(FALSE
) {
385 for (int32_t i
= 0; i
< 24; ++i
) {
386 fDayPeriodForHour
[i
] = DayPeriodRules::DAYPERIOD_UNKNOWN
;
390 double DayPeriodRules::getMidPointForDayPeriod(
391 DayPeriodRules::DayPeriod dayPeriod
, UErrorCode
&errorCode
) const {
392 if (U_FAILURE(errorCode
)) { return -1; }
394 int32_t startHour
= getStartHourForDayPeriod(dayPeriod
, errorCode
);
395 int32_t endHour
= getEndHourForDayPeriod(dayPeriod
, errorCode
);
396 // Can't obtain startHour or endHour; bail out.
397 if (U_FAILURE(errorCode
)) { return -1; }
399 double midPoint
= (startHour
+ endHour
) / 2.0;
401 if (startHour
> endHour
) {
402 // dayPeriod wraps around midnight. Shift midPoint by 12 hours, in the direction that
403 // lands it in [0, 24).
405 if (midPoint
>= 24) {
413 int32_t DayPeriodRules::getStartHourForDayPeriod(
414 DayPeriodRules::DayPeriod dayPeriod
, UErrorCode
&errorCode
) const {
415 if (U_FAILURE(errorCode
)) { return -1; }
417 if (dayPeriod
== DAYPERIOD_MIDNIGHT
) { return 0; }
418 if (dayPeriod
== DAYPERIOD_NOON
) { return 12; }
420 if (fDayPeriodForHour
[0] == dayPeriod
&& fDayPeriodForHour
[23] == dayPeriod
) {
421 // dayPeriod wraps around midnight. Start hour is later than end hour.
422 for (int32_t i
= 22; i
>= 1; --i
) {
423 if (fDayPeriodForHour
[i
] != dayPeriod
) {
428 for (int32_t i
= 0; i
<= 23; ++i
) {
429 if (fDayPeriodForHour
[i
] == dayPeriod
) {
435 // dayPeriod doesn't exist in rule set; set error and exit.
436 errorCode
= U_ILLEGAL_ARGUMENT_ERROR
;
440 int32_t DayPeriodRules::getEndHourForDayPeriod(
441 DayPeriodRules::DayPeriod dayPeriod
, UErrorCode
&errorCode
) const {
442 if (U_FAILURE(errorCode
)) { return -1; }
444 if (dayPeriod
== DAYPERIOD_MIDNIGHT
) { return 0; }
445 if (dayPeriod
== DAYPERIOD_NOON
) { return 12; }
447 if (fDayPeriodForHour
[0] == dayPeriod
&& fDayPeriodForHour
[23] == dayPeriod
) {
448 // dayPeriod wraps around midnight. End hour is before start hour.
449 for (int32_t i
= 1; i
<= 22; ++i
) {
450 if (fDayPeriodForHour
[i
] != dayPeriod
) {
451 // i o'clock is when a new period starts, therefore when the old period ends.
456 for (int32_t i
= 23; i
>= 0; --i
) {
457 if (fDayPeriodForHour
[i
] == dayPeriod
) {
463 // dayPeriod doesn't exist in rule set; set error and exit.
464 errorCode
= U_ILLEGAL_ARGUMENT_ERROR
;
468 DayPeriodRules::DayPeriod
DayPeriodRules::getDayPeriodFromString(const char *type_str
) {
469 if (uprv_strcmp(type_str
, "midnight") == 0) {
470 return DAYPERIOD_MIDNIGHT
;
471 } else if (uprv_strcmp(type_str
, "noon") == 0) {
472 return DAYPERIOD_NOON
;
473 } else if (uprv_strcmp(type_str
, "morning1") == 0) {
474 return DAYPERIOD_MORNING1
;
475 } else if (uprv_strcmp(type_str
, "afternoon1") == 0) {
476 return DAYPERIOD_AFTERNOON1
;
477 } else if (uprv_strcmp(type_str
, "evening1") == 0) {
478 return DAYPERIOD_EVENING1
;
479 } else if (uprv_strcmp(type_str
, "night1") == 0) {
480 return DAYPERIOD_NIGHT1
;
481 } else if (uprv_strcmp(type_str
, "morning2") == 0) {
482 return DAYPERIOD_MORNING2
;
483 } else if (uprv_strcmp(type_str
, "afternoon2") == 0) {
484 return DAYPERIOD_AFTERNOON2
;
485 } else if (uprv_strcmp(type_str
, "evening2") == 0) {
486 return DAYPERIOD_EVENING2
;
487 } else if (uprv_strcmp(type_str
, "night2") == 0) {
488 return DAYPERIOD_NIGHT2
;
489 } else if (uprv_strcmp(type_str
, "am") == 0) {
491 } else if (uprv_strcmp(type_str
, "pm") == 0) {
494 return DAYPERIOD_UNKNOWN
;
498 void DayPeriodRules::add(int32_t startHour
, int32_t limitHour
, DayPeriod period
) {
499 for (int32_t i
= startHour
; i
!= limitHour
; ++i
) {
500 if (i
== 24) { i
= 0; }
501 fDayPeriodForHour
[i
] = period
;
505 UBool
DayPeriodRules::allHoursAreSet() {
506 for (int32_t i
= 0; i
< 24; ++i
) {
507 if (fDayPeriodForHour
[i
] == DAYPERIOD_UNKNOWN
) { return FALSE
; }