/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/Assertions.h" #include "mozilla/Try.h" #include "DateTimeFormatUtils.h" #include "mozilla/intl/ICU4CGlue.h" #include #if !MOZ_SYSTEM_ICU # include "calendar/ICU4XChineseCalendar.h" # include "calendar/ICU4XDangiCalendar.h" # include "unicode/datefmt.h" # include "unicode/gregocal.h" #endif namespace mozilla::intl { DateTimePartType ConvertUFormatFieldToPartType(UDateFormatField fieldName) { // See intl/icu/source/i18n/unicode/udat.h for a detailed field list. This // switch is deliberately exhaustive: cases might have to be added/removed // if this code is compiled with a different ICU with more // UDateFormatField enum initializers. Please guard such cases with // appropriate ICU version-testing #ifdefs, should cross-version divergence // occur. switch (fieldName) { case UDAT_ERA_FIELD: return DateTimePartType::Era; case UDAT_YEAR_FIELD: case UDAT_YEAR_WOY_FIELD: case UDAT_EXTENDED_YEAR_FIELD: return DateTimePartType::Year; case UDAT_YEAR_NAME_FIELD: return DateTimePartType::YearName; case UDAT_MONTH_FIELD: case UDAT_STANDALONE_MONTH_FIELD: return DateTimePartType::Month; case UDAT_DATE_FIELD: case UDAT_JULIAN_DAY_FIELD: return DateTimePartType::Day; case UDAT_HOUR_OF_DAY1_FIELD: case UDAT_HOUR_OF_DAY0_FIELD: case UDAT_HOUR1_FIELD: case UDAT_HOUR0_FIELD: return DateTimePartType::Hour; case UDAT_MINUTE_FIELD: return DateTimePartType::Minute; case UDAT_SECOND_FIELD: return DateTimePartType::Second; case UDAT_DAY_OF_WEEK_FIELD: case UDAT_STANDALONE_DAY_FIELD: case UDAT_DOW_LOCAL_FIELD: case UDAT_DAY_OF_WEEK_IN_MONTH_FIELD: return DateTimePartType::Weekday; case UDAT_AM_PM_FIELD: case UDAT_FLEXIBLE_DAY_PERIOD_FIELD: return DateTimePartType::DayPeriod; case UDAT_TIMEZONE_FIELD: case UDAT_TIMEZONE_GENERIC_FIELD: case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD: return DateTimePartType::TimeZoneName; case UDAT_FRACTIONAL_SECOND_FIELD: return DateTimePartType::FractionalSecondDigits; #ifndef U_HIDE_INTERNAL_API case UDAT_RELATED_YEAR_FIELD: return DateTimePartType::RelatedYear; #endif case UDAT_DAY_OF_YEAR_FIELD: case UDAT_WEEK_OF_YEAR_FIELD: case UDAT_WEEK_OF_MONTH_FIELD: case UDAT_MILLISECONDS_IN_DAY_FIELD: case UDAT_TIMEZONE_RFC_FIELD: case UDAT_QUARTER_FIELD: case UDAT_STANDALONE_QUARTER_FIELD: case UDAT_TIMEZONE_SPECIAL_FIELD: case UDAT_TIMEZONE_ISO_FIELD: case UDAT_TIMEZONE_ISO_LOCAL_FIELD: case UDAT_AM_PM_MIDNIGHT_NOON_FIELD: #ifndef U_HIDE_INTERNAL_API case UDAT_TIME_SEPARATOR_FIELD: #endif // These fields are all unsupported. return DateTimePartType::Unknown; #ifndef U_HIDE_DEPRECATED_API case UDAT_FIELD_COUNT: MOZ_ASSERT_UNREACHABLE( "format field sentinel value returned by " "iterator!"); #endif } MOZ_ASSERT_UNREACHABLE( "unenumerated, undocumented format field returned " "by iterator"); return DateTimePartType::Unknown; } // Start of ECMAScript time. static constexpr double StartOfTime = -8.64e15; #if !MOZ_SYSTEM_ICU static bool IsGregorianLikeCalendar(const char* type) { return std::strcmp(type, "gregorian") == 0 || std::strcmp(type, "iso8601") == 0 || std::strcmp(type, "buddhist") == 0 || std::strcmp(type, "japanese") == 0 || std::strcmp(type, "roc") == 0; } /** * Set the start time of the Gregorian calendar. This is useful for * ensuring the consistent use of a proleptic Gregorian calendar for ECMA-402. * https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar */ static Result SetGregorianChangeDate( icu::GregorianCalendar* gregorian) { UErrorCode status = U_ZERO_ERROR; gregorian->setGregorianChange(StartOfTime, status); if (U_FAILURE(status)) { return Err(ToICUError(status)); } return Ok{}; } static bool IsCalendarReplacementSupported(const char* type) { return std::strcmp(type, "chinese") == 0 || std::strcmp(type, "dangi") == 0; } static Result, ICUError> CreateCalendarReplacement( const icu::Calendar* calendar) { const char* type = calendar->getType(); MOZ_ASSERT(IsCalendarReplacementSupported(type)); UErrorCode status = U_ZERO_ERROR; icu::Locale locale = calendar->getLocale(ULOC_ACTUAL_LOCALE, status); locale.setKeywordValue("calendar", type, status); if (U_FAILURE(status)) { return Err(ToICUError(status)); } const icu::TimeZone& timeZone = calendar->getTimeZone(); UniquePtr replacement = nullptr; if (std::strcmp(type, "chinese") == 0) { replacement.reset( new calendar::ICU4XChineseCalendar(timeZone, locale, status)); } else { MOZ_ASSERT(std::strcmp(type, "dangi") == 0); replacement.reset( new calendar::ICU4XDangiCalendar(timeZone, locale, status)); } if (replacement == nullptr) { return Err(ICUError::OutOfMemory); } if (U_FAILURE(status)) { return Err(ToICUError(status)); } return replacement; } #endif Result ApplyCalendarOverride(UDateFormat* aDateFormat) { #if !MOZ_SYSTEM_ICU icu::DateFormat* df = reinterpret_cast(aDateFormat); const icu::Calendar* calendar = df->getCalendar(); const char* type = calendar->getType(); if (IsGregorianLikeCalendar(type)) { auto* gregorian = static_cast(calendar); MOZ_TRY( SetGregorianChangeDate(const_cast(gregorian))); } else if (IsCalendarReplacementSupported(type)) { auto replacement = CreateCalendarReplacement(calendar); if (replacement.isErr()) { return replacement.propagateErr(); } df->adoptCalendar(replacement.unwrap().release()); } #else UErrorCode status = U_ZERO_ERROR; UCalendar* cal = const_cast(udat_getCalendar(aDateFormat)); ucal_setGregorianChange(cal, StartOfTime, &status); // An error here means the calendar is not Gregorian, and can be ignored. #endif return Ok{}; } #if !MOZ_SYSTEM_ICU Result, ICUError> CreateCalendarOverride( const icu::Calendar* calendar) { const char* type = calendar->getType(); if (IsGregorianLikeCalendar(type)) { UniquePtr gregorian( static_cast(calendar)->clone()); if (!gregorian) { return Err(ICUError::OutOfMemory); } MOZ_TRY(SetGregorianChangeDate(gregorian.get())); return UniquePtr{gregorian.release()}; } if (IsCalendarReplacementSupported(type)) { return CreateCalendarReplacement(calendar); } return UniquePtr{}; } #endif } // namespace mozilla::intl