DateTimeFormatUtils.cpp (7043B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #include "mozilla/Assertions.h" 6 #include "mozilla/Try.h" 7 8 #include "DateTimeFormatUtils.h" 9 #include "mozilla/intl/ICU4CGlue.h" 10 11 #include <cstring> 12 13 #if !MOZ_SYSTEM_ICU 14 # include "calendar/ICU4XChineseCalendar.h" 15 # include "calendar/ICU4XDangiCalendar.h" 16 # include "unicode/datefmt.h" 17 # include "unicode/gregocal.h" 18 #endif 19 20 namespace mozilla::intl { 21 22 DateTimePartType ConvertUFormatFieldToPartType(UDateFormatField fieldName) { 23 // See intl/icu/source/i18n/unicode/udat.h for a detailed field list. This 24 // switch is deliberately exhaustive: cases might have to be added/removed 25 // if this code is compiled with a different ICU with more 26 // UDateFormatField enum initializers. Please guard such cases with 27 // appropriate ICU version-testing #ifdefs, should cross-version divergence 28 // occur. 29 switch (fieldName) { 30 case UDAT_ERA_FIELD: 31 return DateTimePartType::Era; 32 33 case UDAT_YEAR_FIELD: 34 case UDAT_YEAR_WOY_FIELD: 35 case UDAT_EXTENDED_YEAR_FIELD: 36 return DateTimePartType::Year; 37 38 case UDAT_YEAR_NAME_FIELD: 39 return DateTimePartType::YearName; 40 41 case UDAT_MONTH_FIELD: 42 case UDAT_STANDALONE_MONTH_FIELD: 43 return DateTimePartType::Month; 44 45 case UDAT_DATE_FIELD: 46 case UDAT_JULIAN_DAY_FIELD: 47 return DateTimePartType::Day; 48 49 case UDAT_HOUR_OF_DAY1_FIELD: 50 case UDAT_HOUR_OF_DAY0_FIELD: 51 case UDAT_HOUR1_FIELD: 52 case UDAT_HOUR0_FIELD: 53 return DateTimePartType::Hour; 54 55 case UDAT_MINUTE_FIELD: 56 return DateTimePartType::Minute; 57 58 case UDAT_SECOND_FIELD: 59 return DateTimePartType::Second; 60 61 case UDAT_DAY_OF_WEEK_FIELD: 62 case UDAT_STANDALONE_DAY_FIELD: 63 case UDAT_DOW_LOCAL_FIELD: 64 case UDAT_DAY_OF_WEEK_IN_MONTH_FIELD: 65 return DateTimePartType::Weekday; 66 67 case UDAT_AM_PM_FIELD: 68 case UDAT_FLEXIBLE_DAY_PERIOD_FIELD: 69 return DateTimePartType::DayPeriod; 70 71 case UDAT_TIMEZONE_FIELD: 72 case UDAT_TIMEZONE_GENERIC_FIELD: 73 case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD: 74 return DateTimePartType::TimeZoneName; 75 76 case UDAT_FRACTIONAL_SECOND_FIELD: 77 return DateTimePartType::FractionalSecondDigits; 78 79 #ifndef U_HIDE_INTERNAL_API 80 case UDAT_RELATED_YEAR_FIELD: 81 return DateTimePartType::RelatedYear; 82 #endif 83 84 case UDAT_DAY_OF_YEAR_FIELD: 85 case UDAT_WEEK_OF_YEAR_FIELD: 86 case UDAT_WEEK_OF_MONTH_FIELD: 87 case UDAT_MILLISECONDS_IN_DAY_FIELD: 88 case UDAT_TIMEZONE_RFC_FIELD: 89 case UDAT_QUARTER_FIELD: 90 case UDAT_STANDALONE_QUARTER_FIELD: 91 case UDAT_TIMEZONE_SPECIAL_FIELD: 92 case UDAT_TIMEZONE_ISO_FIELD: 93 case UDAT_TIMEZONE_ISO_LOCAL_FIELD: 94 case UDAT_AM_PM_MIDNIGHT_NOON_FIELD: 95 #ifndef U_HIDE_INTERNAL_API 96 case UDAT_TIME_SEPARATOR_FIELD: 97 #endif 98 // These fields are all unsupported. 99 return DateTimePartType::Unknown; 100 101 #ifndef U_HIDE_DEPRECATED_API 102 case UDAT_FIELD_COUNT: 103 MOZ_ASSERT_UNREACHABLE( 104 "format field sentinel value returned by " 105 "iterator!"); 106 #endif 107 } 108 109 MOZ_ASSERT_UNREACHABLE( 110 "unenumerated, undocumented format field returned " 111 "by iterator"); 112 return DateTimePartType::Unknown; 113 } 114 115 // Start of ECMAScript time. 116 static constexpr double StartOfTime = -8.64e15; 117 118 #if !MOZ_SYSTEM_ICU 119 static bool IsGregorianLikeCalendar(const char* type) { 120 return std::strcmp(type, "gregorian") == 0 || 121 std::strcmp(type, "iso8601") == 0 || 122 std::strcmp(type, "buddhist") == 0 || 123 std::strcmp(type, "japanese") == 0 || std::strcmp(type, "roc") == 0; 124 } 125 126 /** 127 * Set the start time of the Gregorian calendar. This is useful for 128 * ensuring the consistent use of a proleptic Gregorian calendar for ECMA-402. 129 * https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar 130 */ 131 static Result<Ok, ICUError> SetGregorianChangeDate( 132 icu::GregorianCalendar* gregorian) { 133 UErrorCode status = U_ZERO_ERROR; 134 gregorian->setGregorianChange(StartOfTime, status); 135 if (U_FAILURE(status)) { 136 return Err(ToICUError(status)); 137 } 138 return Ok{}; 139 } 140 141 static bool IsCalendarReplacementSupported(const char* type) { 142 return std::strcmp(type, "chinese") == 0 || std::strcmp(type, "dangi") == 0; 143 } 144 145 static Result<UniquePtr<icu::Calendar>, ICUError> CreateCalendarReplacement( 146 const icu::Calendar* calendar) { 147 const char* type = calendar->getType(); 148 MOZ_ASSERT(IsCalendarReplacementSupported(type)); 149 150 UErrorCode status = U_ZERO_ERROR; 151 icu::Locale locale = calendar->getLocale(ULOC_ACTUAL_LOCALE, status); 152 locale.setKeywordValue("calendar", type, status); 153 if (U_FAILURE(status)) { 154 return Err(ToICUError(status)); 155 } 156 157 const icu::TimeZone& timeZone = calendar->getTimeZone(); 158 159 UniquePtr<icu::Calendar> replacement = nullptr; 160 if (std::strcmp(type, "chinese") == 0) { 161 replacement.reset( 162 new calendar::ICU4XChineseCalendar(timeZone, locale, status)); 163 } else { 164 MOZ_ASSERT(std::strcmp(type, "dangi") == 0); 165 replacement.reset( 166 new calendar::ICU4XDangiCalendar(timeZone, locale, status)); 167 } 168 if (replacement == nullptr) { 169 return Err(ICUError::OutOfMemory); 170 } 171 if (U_FAILURE(status)) { 172 return Err(ToICUError(status)); 173 } 174 175 return replacement; 176 } 177 #endif 178 179 Result<Ok, ICUError> ApplyCalendarOverride(UDateFormat* aDateFormat) { 180 #if !MOZ_SYSTEM_ICU 181 icu::DateFormat* df = reinterpret_cast<icu::DateFormat*>(aDateFormat); 182 const icu::Calendar* calendar = df->getCalendar(); 183 184 const char* type = calendar->getType(); 185 186 if (IsGregorianLikeCalendar(type)) { 187 auto* gregorian = static_cast<const icu::GregorianCalendar*>(calendar); 188 MOZ_TRY( 189 SetGregorianChangeDate(const_cast<icu::GregorianCalendar*>(gregorian))); 190 } else if (IsCalendarReplacementSupported(type)) { 191 auto replacement = CreateCalendarReplacement(calendar); 192 if (replacement.isErr()) { 193 return replacement.propagateErr(); 194 } 195 df->adoptCalendar(replacement.unwrap().release()); 196 } 197 #else 198 UErrorCode status = U_ZERO_ERROR; 199 UCalendar* cal = const_cast<UCalendar*>(udat_getCalendar(aDateFormat)); 200 ucal_setGregorianChange(cal, StartOfTime, &status); 201 // An error here means the calendar is not Gregorian, and can be ignored. 202 #endif 203 204 return Ok{}; 205 } 206 207 #if !MOZ_SYSTEM_ICU 208 Result<UniquePtr<icu::Calendar>, ICUError> CreateCalendarOverride( 209 const icu::Calendar* calendar) { 210 const char* type = calendar->getType(); 211 212 if (IsGregorianLikeCalendar(type)) { 213 UniquePtr<icu::GregorianCalendar> gregorian( 214 static_cast<const icu::GregorianCalendar*>(calendar)->clone()); 215 if (!gregorian) { 216 return Err(ICUError::OutOfMemory); 217 } 218 219 MOZ_TRY(SetGregorianChangeDate(gregorian.get())); 220 221 return UniquePtr<icu::Calendar>{gregorian.release()}; 222 } 223 224 if (IsCalendarReplacementSupported(type)) { 225 return CreateCalendarReplacement(calendar); 226 } 227 228 return UniquePtr<icu::Calendar>{}; 229 } 230 #endif 231 232 } // namespace mozilla::intl