+namespace {
+
+struct AllowedHourFormatsSink : public ResourceSink {
+ // Initialize sub-sinks.
+ AllowedHourFormatsSink() {}
+ virtual ~AllowedHourFormatsSink();
+
+ virtual void put(const char *key, ResourceValue &value, UBool /*noFallback*/,
+ UErrorCode &errorCode) {
+ ResourceTable timeData = value.getTable(errorCode);
+ if (U_FAILURE(errorCode)) { return; }
+ for (int32_t i = 0; timeData.getKeyAndValue(i, key, value); ++i) {
+ const char *regionOrLocale = key;
+ ResourceTable formatList = value.getTable(errorCode);
+ if (U_FAILURE(errorCode)) { return; }
+ // below we construct a list[] that has an entry for the "preferred" value at [0],
+ // followed by 1 or more entries for the "allowed" values, terminated with an
+ // entry for ALLOWED_HOUR_FORMAT_UNKNOWN (not included in length below)
+ LocalMemory<int32_t> list;
+ int32_t length = 0;
+ int32_t preferredFormat = ALLOWED_HOUR_FORMAT_UNKNOWN;
+ for (int32_t j = 0; formatList.getKeyAndValue(j, key, value); ++j) {
+ if (uprv_strcmp(key, "allowed") == 0) {
+ if (value.getType() == URES_STRING) {
+ length = 2; // 1 preferred to add later, 1 allowed to add now
+ if (list.allocateInsteadAndReset(length + 1) == nullptr) {
+ errorCode = U_MEMORY_ALLOCATION_ERROR;
+ return;
+ }
+ list[1] = getHourFormatFromUnicodeString(value.getUnicodeString(errorCode));
+ }
+ else {
+ ResourceArray allowedFormats = value.getArray(errorCode);
+ length = allowedFormats.getSize() + 1; // 1 preferred, getSize allowed
+ if (list.allocateInsteadAndReset(length + 1) == nullptr) {
+ errorCode = U_MEMORY_ALLOCATION_ERROR;
+ return;
+ }
+ for (int32_t k = 1; k < length; ++k) {
+ allowedFormats.getValue(k-1, value);
+ list[k] = getHourFormatFromUnicodeString(value.getUnicodeString(errorCode));
+ }
+ }
+ } else if (uprv_strcmp(key, "preferred") == 0) {
+ preferredFormat = getHourFormatFromUnicodeString(value.getUnicodeString(errorCode));
+ }
+ }
+ if (length > 1) {
+ list[0] = (preferredFormat!=ALLOWED_HOUR_FORMAT_UNKNOWN)? preferredFormat: list[1];
+ } else {
+ // fallback handling for missing data
+ length = 2; // 1 preferred, 1 allowed
+ if (list.allocateInsteadAndReset(length + 1) == nullptr) {
+ errorCode = U_MEMORY_ALLOCATION_ERROR;
+ return;
+ }
+ list[0] = (preferredFormat!=ALLOWED_HOUR_FORMAT_UNKNOWN)? preferredFormat: ALLOWED_HOUR_FORMAT_H;
+ list[1] = list[0];
+ }
+ list[length] = ALLOWED_HOUR_FORMAT_UNKNOWN;
+ // At this point list[] will have at least two non-ALLOWED_HOUR_FORMAT_UNKNOWN entries,
+ // followed by ALLOWED_HOUR_FORMAT_UNKNOWN.
+ uhash_put(localeToAllowedHourFormatsMap, const_cast<char *>(regionOrLocale), list.orphan(), &errorCode);
+ if (U_FAILURE(errorCode)) { return; }
+ }
+ }
+
+ AllowedHourFormat getHourFormatFromUnicodeString(const UnicodeString &s) {
+ if (s.length() == 1) {
+ if (s[0] == LOW_H) { return ALLOWED_HOUR_FORMAT_h; }
+ if (s[0] == CAP_H) { return ALLOWED_HOUR_FORMAT_H; }
+ if (s[0] == CAP_K) { return ALLOWED_HOUR_FORMAT_K; }
+ if (s[0] == LOW_K) { return ALLOWED_HOUR_FORMAT_k; }
+ } else if (s.length() == 2) {
+ if (s[0] == LOW_H && s[1] == LOW_B) { return ALLOWED_HOUR_FORMAT_hb; }
+ if (s[0] == LOW_H && s[1] == CAP_B) { return ALLOWED_HOUR_FORMAT_hB; }
+ if (s[0] == CAP_K && s[1] == LOW_B) { return ALLOWED_HOUR_FORMAT_Kb; }
+ if (s[0] == CAP_K && s[1] == CAP_B) { return ALLOWED_HOUR_FORMAT_KB; }
+ if (s[0] == CAP_H && s[1] == LOW_B) { return ALLOWED_HOUR_FORMAT_Hb; }
+ if (s[0] == CAP_H && s[1] == CAP_B) { return ALLOWED_HOUR_FORMAT_HB; }
+ }
+
+ return ALLOWED_HOUR_FORMAT_UNKNOWN;
+ }
+};
+
+} // namespace
+
+AllowedHourFormatsSink::~AllowedHourFormatsSink() {}
+
+U_CFUNC void U_CALLCONV DateTimePatternGenerator::loadAllowedHourFormatsData(UErrorCode &status) {
+ if (U_FAILURE(status)) { return; }
+ localeToAllowedHourFormatsMap = uhash_open(
+ uhash_hashChars, uhash_compareChars, nullptr, &status);
+ if (U_FAILURE(status)) { return; }
+
+ uhash_setValueDeleter(localeToAllowedHourFormatsMap, deleteAllowedHourFormats);
+ ucln_i18n_registerCleanup(UCLN_I18N_ALLOWED_HOUR_FORMATS, allowedHourFormatsCleanup);
+
+ LocalUResourceBundlePointer rb(ures_openDirect(nullptr, "supplementalData", &status));
+ if (U_FAILURE(status)) { return; }
+
+ AllowedHourFormatsSink sink;
+ // TODO: Currently in the enumeration each table allocates a new array.
+ // Try to reduce the number of memory allocations. Consider storing a
+ // UVector32 with the concatenation of all of the sub-arrays, put the start index
+ // into the hashmap, store 6 single-value sub-arrays right at the beginning of the
+ // vector (at index enum*2) for easy data sharing, copy sub-arrays into runtime
+ // object. Remember to clean up the vector, too.
+ ures_getAllItemsWithFallback(rb.getAlias(), "timeData", sink, status);
+}
+
+static int32_t* getAllowedHourFormatsLangCountry(const char* language, const char* country, UErrorCode& status) {
+ CharString langCountry;
+ langCountry.append(language, status);
+ langCountry.append('_', status);
+ langCountry.append(country, status);
+
+ int32_t* allowedFormats;
+ allowedFormats = (int32_t *)uhash_get(localeToAllowedHourFormatsMap, langCountry.data());
+ if (allowedFormats == nullptr) {
+ allowedFormats = (int32_t *)uhash_get(localeToAllowedHourFormatsMap, const_cast<char *>(country));
+ }
+
+ return allowedFormats;
+}
+
+void DateTimePatternGenerator::getAllowedHourFormats(const Locale &locale, UErrorCode &status) {
+ if (U_FAILURE(status)) { return; }
+
+ const char *language = locale.getLanguage();
+ const char *country = locale.getCountry();
+ const char *locName = locale.getName(); // Apple addition
+ if (*locName==0 || uprv_strcmp(locName,"root")==0 || uprv_strcmp(locName,"und")==0) { // Apple addition
+ language = "und";
+ country = "001";
+ }
+ Locale maxLocale; // must be here for correct lifetime
+ if (*language == '\0' || *country == '\0') {
+ maxLocale = locale;
+ UErrorCode localStatus = U_ZERO_ERROR;
+ maxLocale.addLikelySubtags(localStatus);
+ if (U_SUCCESS(localStatus)) {
+ language = maxLocale.getLanguage();
+ country = maxLocale.getCountry();
+ }
+ }
+ if (*language == '\0') {
+ // Unexpected, but fail gracefully
+ language = "und";
+ }
+ if (*country == '\0') {
+ country = "001";
+ }
+
+ int32_t* allowedFormats = getAllowedHourFormatsLangCountry(language, country, status);
+
+ // Check if the region has an alias
+ if (allowedFormats == nullptr) {
+ UErrorCode localStatus = U_ZERO_ERROR;
+ const Region* region = Region::getInstance(country, localStatus);
+ if (U_SUCCESS(localStatus)) {
+ country = region->getRegionCode(); // the real region code
+ allowedFormats = getAllowedHourFormatsLangCountry(language, country, status);
+ }
+ }
+
+ if (allowedFormats != nullptr) { // Lookup is successful
+ // Here allowedFormats points to a list consisting of key for preferredFormat,
+ // followed by one or more keys for allowedFormats, then followed by ALLOWED_HOUR_FORMAT_UNKNOWN.
+ switch (allowedFormats[0]) {
+ case ALLOWED_HOUR_FORMAT_h: fDefaultHourFormatChar = LOW_H; break;
+ case ALLOWED_HOUR_FORMAT_H: fDefaultHourFormatChar = CAP_H; break;
+ case ALLOWED_HOUR_FORMAT_K: fDefaultHourFormatChar = CAP_K; break;
+ case ALLOWED_HOUR_FORMAT_k: fDefaultHourFormatChar = LOW_K; break;
+ default: fDefaultHourFormatChar = CAP_H; break;
+ }
+ for (int32_t i = 0; i < UPRV_LENGTHOF(fAllowedHourFormats); ++i) {
+ fAllowedHourFormats[i] = allowedFormats[i + 1];
+ if (fAllowedHourFormats[i] == ALLOWED_HOUR_FORMAT_UNKNOWN) {
+ break;
+ }
+ }
+ } else { // Lookup failed, twice
+ fDefaultHourFormatChar = CAP_H;
+ fAllowedHourFormats[0] = ALLOWED_HOUR_FORMAT_H;
+ fAllowedHourFormats[1] = ALLOWED_HOUR_FORMAT_UNKNOWN;
+ }
+}
+