Coverage Report

Created: 2025-06-24 06:43

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