Source code

Revision control

Copy as Markdown

Other Tools

/* 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 <cstring>
#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.
*/
static Result<Ok, ICUError> 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<UniquePtr<icu::Calendar>, 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<icu::Calendar> 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<Ok, ICUError> ApplyCalendarOverride(UDateFormat* aDateFormat) {
#if !MOZ_SYSTEM_ICU
icu::DateFormat* df = reinterpret_cast<icu::DateFormat*>(aDateFormat);
const icu::Calendar* calendar = df->getCalendar();
const char* type = calendar->getType();
if (IsGregorianLikeCalendar(type)) {
auto* gregorian = static_cast<const icu::GregorianCalendar*>(calendar);
MOZ_TRY(
SetGregorianChangeDate(const_cast<icu::GregorianCalendar*>(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<UCalendar*>(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<UniquePtr<icu::Calendar>, ICUError> CreateCalendarOverride(
const icu::Calendar* calendar) {
const char* type = calendar->getType();
if (IsGregorianLikeCalendar(type)) {
UniquePtr<icu::GregorianCalendar> gregorian(
static_cast<const icu::GregorianCalendar*>(calendar)->clone());
if (!gregorian) {
return Err(ICUError::OutOfMemory);
}
MOZ_TRY(SetGregorianChangeDate(gregorian.get()));
return UniquePtr<icu::Calendar>{gregorian.release()};
}
if (IsCalendarReplacementSupported(type)) {
return CreateCalendarReplacement(calendar);
}
return UniquePtr<icu::Calendar>{};
}
#endif
} // namespace mozilla::intl