Bug 1954138 - Part 3: Add ICU4X-based Chinese and Dangun calendars. r=dminor,sylvestre

Add ICU4X based calendars for Chinese and Dangun to ensure consistent behaviour
across `Intl.DateTimeFormat` and `Temporal`.

This requires using the ICU4C C++ API, so it's not possible to use when
`MOZ_SYSTEM_ICU` is defined. And ICU4X's calendar FFI needs to be available,
which is currently guarded by `JS_HAS_TEMPORAL_API`.

Differential Revision: https://phabricator.services.mozilla.com/D241645
This commit is contained in:
André Bargull
2025-04-23 11:23:30 +00:00
parent c943966835
commit 2c4c691853
15 changed files with 1590 additions and 0 deletions

View File

@@ -77,6 +77,9 @@ LOCAL_INCLUDES += [
"/intl/icu_capi/bindings/c",
]
if not CONFIG["MOZ_SYSTEM_ICU"]:
DIRS += ["src/calendar"]
# At the time of this writing the MOZ_HAS_MOZGLUE define must be true in order to
# correctly include ConvertUtf8toUtf16 in certain include paths, otherwise it results
# in a compile time "undeclared identifier" error. See:

View File

@@ -0,0 +1,637 @@
/* 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/intl/calendar/ICU4XCalendar.h"
#include "mozilla/Assertions.h"
#include "mozilla/TextUtils.h"
#include "mozilla/intl/ICU4XGeckoDataProvider.h"
#include <cstring>
#include <mutex>
#include <stdint.h>
#include <type_traits>
#include "unicode/timezone.h"
#include "diplomat_runtime.h"
#include "ICU4XDataProvider.h"
#include "ICU4XError.h"
namespace mozilla::intl::calendar {
// Copied from js/src/util/Text.h
template <typename CharT>
static constexpr uint8_t AsciiDigitToNumber(CharT c) {
using UnsignedCharT = std::make_unsigned_t<CharT>;
auto uc = static_cast<UnsignedCharT>(c);
return uc - '0';
}
static UniqueICU4XCalendar CreateICU4XCalendar(
capi::ICU4XAnyCalendarKind kind) {
auto result = capi::ICU4XCalendar_create_for_kind(GetDataProvider(), kind);
if (!result.is_ok) {
return nullptr;
}
return UniqueICU4XCalendar{result.ok};
}
static UniqueICU4XDate CreateICU4XDate(const ISODate& date,
const capi::ICU4XCalendar* calendar) {
auto result = capi::ICU4XDate_create_from_iso_in_calendar(
date.year, date.month, date.day, calendar);
if (!result.is_ok) {
return nullptr;
}
return UniqueICU4XDate{result.ok};
}
static UniqueICU4XDate CreateDateFromCodes(const capi::ICU4XCalendar* calendar,
std::string_view era,
int32_t eraYear, MonthCode monthCode,
int32_t day) {
auto monthCodeView = std::string_view{monthCode};
auto date = capi::ICU4XDate_create_from_codes_in_calendar(
era.data(), era.length(), eraYear, monthCodeView.data(),
monthCodeView.length(), day, calendar);
if (date.is_ok) {
return UniqueICU4XDate{date.ok};
}
return nullptr;
}
// Copied from js/src/builtin/temporal/Calendar.cpp
static UniqueICU4XDate CreateDateFrom(const capi::ICU4XCalendar* calendar,
std::string_view era, int32_t eraYear,
int32_t month, int32_t day) {
MOZ_ASSERT(1 <= month && month <= 13);
// Create date with month number replaced by month-code.
auto monthCode = MonthCode{std::min(month, 12)};
auto date = CreateDateFromCodes(calendar, era, eraYear, monthCode, day);
if (!date) {
return nullptr;
}
// If the ordinal month of |date| matches the input month, no additional
// changes are necessary and we can directly return |date|.
int32_t ordinal = capi::ICU4XDate_ordinal_month(date.get());
if (ordinal == month) {
return date;
}
// Otherwise we need to handle three cases:
// 1. The input year contains a leap month and we need to adjust the
// month-code.
// 2. The thirteenth month of a year without leap months was requested.
// 3. The thirteenth month of a year with leap months was requested.
if (ordinal > month) {
MOZ_ASSERT(1 < month && month <= 12);
// This case can only happen in leap years.
MOZ_ASSERT(capi::ICU4XDate_months_in_year(date.get()) == 13);
// Leap months can occur after any month in the Chinese calendar.
//
// Example when the fourth month is a leap month between M03 and M04.
//
// Month code: M01 M02 M03 M03L M04 M05 M06 ...
// Ordinal month: 1 2 3 4 5 6 7
// The month can be off by exactly one.
MOZ_ASSERT((ordinal - month) == 1);
// First try the case when the previous month isn't a leap month. This
// case can only occur when |month > 2|, because otherwise we know that
// "M01L" is the correct answer.
if (month > 2) {
auto previousMonthCode = MonthCode{month - 1};
date =
CreateDateFromCodes(calendar, era, eraYear, previousMonthCode, day);
if (!date) {
return nullptr;
}
int32_t ordinal = capi::ICU4XDate_ordinal_month(date.get());
if (ordinal == month) {
return date;
}
}
// Fall-through when the previous month is a leap month.
} else {
MOZ_ASSERT(month == 13);
MOZ_ASSERT(ordinal == 12);
// Years with leap months contain thirteen months.
if (capi::ICU4XDate_months_in_year(date.get()) != 13) {
return nullptr;
}
// Fall-through to return leap month "M12L" at the end of the year.
}
// Finally handle the case when the previous month is a leap month.
auto leapMonthCode = MonthCode{month - 1, /* isLeapMonth= */ true};
return CreateDateFromCodes(calendar, era, eraYear, leapMonthCode, day);
}
static ISODate ToISODate(const capi::ICU4XDate* date) {
UniqueICU4XIsoDate isoDate{capi::ICU4XDate_to_iso(date)};
int32_t isoYear = capi::ICU4XIsoDate_year(isoDate.get());
int32_t isoMonth = capi::ICU4XIsoDate_month(isoDate.get());
int32_t isoDay = capi::ICU4XIsoDate_day_of_month(isoDate.get());
return {isoYear, isoMonth, isoDay};
}
////////////////////////////////////////////////////////////////////////////////
ICU4XCalendar::ICU4XCalendar(capi::ICU4XAnyCalendarKind kind,
const icu::Locale& locale, UErrorCode& success)
: icu::Calendar(icu::TimeZone::forLocaleOrDefault(locale), locale, success),
kind_(kind) {}
ICU4XCalendar::ICU4XCalendar(capi::ICU4XAnyCalendarKind kind,
const icu::TimeZone& timeZone,
const icu::Locale& locale, UErrorCode& success)
: icu::Calendar(timeZone, locale, success), kind_(kind) {}
ICU4XCalendar::ICU4XCalendar(const ICU4XCalendar& other)
: icu::Calendar(other), kind_(other.kind_) {}
ICU4XCalendar::~ICU4XCalendar() = default;
/**
* Get or create the underlying ICU4X calendar.
*/
capi::ICU4XCalendar* ICU4XCalendar::getICU4XCalendar(UErrorCode& status) const {
if (U_FAILURE(status)) {
return nullptr;
}
if (!calendar_) {
auto result = CreateICU4XCalendar(kind_);
if (!result) {
status = U_INTERNAL_PROGRAM_ERROR;
return nullptr;
}
calendar_ = std::move(result);
}
return calendar_.get();
}
/**
* Get or create the fallback ICU4C calendar. Used for dates outside the range
* supported by ICU4X.
*/
icu::Calendar* ICU4XCalendar::getFallbackCalendar(UErrorCode& status) const {
if (U_FAILURE(status)) {
return nullptr;
}
if (!fallback_) {
icu::Locale locale = getLocale(ULOC_ACTUAL_LOCALE, status);
locale.setKeywordValue("calendar", getType(), status);
fallback_.reset(
icu::Calendar::createInstance(getTimeZone(), locale, status));
}
return fallback_.get();
}
UniqueICU4XDate ICU4XCalendar::createICU4XDate(const ISODate& date,
UErrorCode& status) const {
MOZ_ASSERT(U_SUCCESS(status));
auto* calendar = getICU4XCalendar(status);
if (U_FAILURE(status)) {
return nullptr;
}
auto dt = CreateICU4XDate(date, calendar);
if (!dt) {
status = U_INTERNAL_PROGRAM_ERROR;
}
return dt;
}
UniqueICU4XDate ICU4XCalendar::createICU4XDate(const CalendarDate& date,
UErrorCode& status) const {
MOZ_ASSERT(U_SUCCESS(status));
auto* calendar = getICU4XCalendar(status);
if (U_FAILURE(status)) {
return nullptr;
}
auto era = eraName(date.year);
auto dt =
CreateDateFromCodes(calendar, era, date.year, date.monthCode, date.day);
if (!dt) {
status = U_INTERNAL_PROGRAM_ERROR;
}
return dt;
}
MonthCode ICU4XCalendar::monthCodeFrom(const capi::ICU4XDate* date,
UErrorCode& status) {
MOZ_ASSERT(U_SUCCESS(status));
// Storage for the largest valid month code and the terminating NUL-character.
char buf[4 + 1] = {};
auto writable = capi::diplomat_simple_writeable(buf, std::size(buf));
if (!capi::ICU4XDate_month_code(date, &writable).is_ok) {
status = U_INTERNAL_PROGRAM_ERROR;
return {};
}
auto view = std::string_view{writable.buf, writable.len};
MOZ_ASSERT(view.length() >= 3);
MOZ_ASSERT(view[0] == 'M');
MOZ_ASSERT(mozilla::IsAsciiDigit(view[1]));
MOZ_ASSERT(mozilla::IsAsciiDigit(view[2]));
MOZ_ASSERT_IF(view.length() > 3, view[3] == 'L');
int32_t ordinal =
AsciiDigitToNumber(view[1]) * 10 + AsciiDigitToNumber(view[2]);
bool isLeapMonth = view.length() > 3;
return MonthCode{ordinal, isLeapMonth};
}
////////////////////////////////////////////
// icu::Calendar implementation overrides //
////////////////////////////////////////////
const char* ICU4XCalendar::getTemporalMonthCode(UErrorCode& status) const {
int32_t month = get(UCAL_MONTH, status);
int32_t isLeapMonth = get(UCAL_IS_LEAP_MONTH, status);
if (U_FAILURE(status)) {
return nullptr;
}
static const char* MonthCodes[] = {
// Non-leap months.
"M01",
"M02",
"M03",
"M04",
"M05",
"M06",
"M07",
"M08",
"M09",
"M10",
"M11",
"M12",
"M13",
// Leap months. (Note: There's no thirteenth leap month.)
"M01L",
"M02L",
"M03L",
"M04L",
"M05L",
"M06L",
"M07L",
"M08L",
"M09L",
"M10L",
"M11L",
"M12L",
};
size_t index = month + (isLeapMonth ? 12 : 0);
if (index >= std::size(MonthCodes)) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return nullptr;
}
return MonthCodes[index];
}
void ICU4XCalendar::setTemporalMonthCode(const char* code, UErrorCode& status) {
if (U_FAILURE(status)) {
return;
}
size_t len = std::strlen(code);
if (len < 3 || len > 4 || code[0] != 'M' || !IsAsciiDigit(code[1]) ||
!IsAsciiDigit(code[2]) || (len == 4 && code[3] != 'L')) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
int32_t month =
AsciiDigitToNumber(code[1]) * 10 + AsciiDigitToNumber(code[2]);
bool isLeapMonth = len == 4;
if (month < 1 || month > 13 || (month == 13 && isLeapMonth)) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
// Check if this calendar supports the requested month code.
auto monthCode = MonthCode{month, isLeapMonth};
if (!hasMonthCode(monthCode)) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
set(UCAL_MONTH, monthCode.ordinal() - 1);
set(UCAL_IS_LEAP_MONTH, int32_t(monthCode.isLeapMonth()));
}
int32_t ICU4XCalendar::internalGetMonth(int32_t defaultValue,
UErrorCode& status) const {
if (U_FAILURE(status)) {
return 0;
}
if (resolveFields(kMonthPrecedence) == UCAL_MONTH) {
return internalGet(UCAL_MONTH, defaultValue);
}
if (!hasLeapMonths()) {
return internalGet(UCAL_ORDINAL_MONTH);
}
return internalGetMonth(status);
}
/**
* Return the current month, possibly by computing it from |UCAL_ORDINAL_MONTH|.
*/
int32_t ICU4XCalendar::internalGetMonth(UErrorCode& status) const {
if (U_FAILURE(status)) {
return 0;
}
if (resolveFields(kMonthPrecedence) == UCAL_MONTH) {
return internalGet(UCAL_MONTH);
}
if (!hasLeapMonths()) {
return internalGet(UCAL_ORDINAL_MONTH);
}
int32_t extendedYear = internalGet(UCAL_EXTENDED_YEAR);
int32_t ordinalMonth = internalGet(UCAL_ORDINAL_MONTH);
int32_t month;
int32_t isLeapMonth;
if (requiresFallbackForExtendedYear(extendedYear)) {
// Use the fallback calendar for years outside the range supported by ICU4X.
auto* fallback = getFallbackCalendar(status);
if (U_FAILURE(status)) {
return 0;
}
fallback->clear();
fallback->set(UCAL_EXTENDED_YEAR, extendedYear);
fallback->set(UCAL_ORDINAL_MONTH, ordinalMonth);
fallback->set(UCAL_DAY_OF_MONTH, 1);
month = fallback->get(UCAL_MONTH, status);
isLeapMonth = fallback->get(UCAL_IS_LEAP_MONTH, status);
if (U_FAILURE(status)) {
return 0;
}
} else {
auto* cal = getICU4XCalendar(status);
if (U_FAILURE(status)) {
return 0;
}
UniqueICU4XDate date = CreateDateFrom(cal, eraName(extendedYear),
extendedYear, ordinalMonth + 1, 1);
if (!date) {
status = U_INTERNAL_PROGRAM_ERROR;
return 0;
}
MonthCode monthCode = monthCodeFrom(date.get(), status);
if (U_FAILURE(status)) {
return 0;
}
month = monthCode.ordinal() - 1;
isLeapMonth = monthCode.isLeapMonth();
}
auto* nonConstThis = const_cast<ICU4XCalendar*>(this);
nonConstThis->internalSet(UCAL_IS_LEAP_MONTH, isLeapMonth);
nonConstThis->internalSet(UCAL_MONTH, month);
return month;
}
void ICU4XCalendar::add(UCalendarDateFields field, int32_t amount,
UErrorCode& status) {
switch (field) {
case UCAL_MONTH:
case UCAL_ORDINAL_MONTH:
if (amount != 0) {
// Our implementation doesn't yet support this action.
status = U_ILLEGAL_ARGUMENT_ERROR;
break;
}
break;
default:
Calendar::add(field, amount, status);
break;
}
}
void ICU4XCalendar::add(EDateFields field, int32_t amount, UErrorCode& status) {
add(static_cast<UCalendarDateFields>(field), amount, status);
}
void ICU4XCalendar::roll(UCalendarDateFields field, int32_t amount,
UErrorCode& status) {
switch (field) {
case UCAL_MONTH:
case UCAL_ORDINAL_MONTH:
if (amount != 0) {
// Our implementation doesn't yet support this action.
status = U_ILLEGAL_ARGUMENT_ERROR;
break;
}
break;
default:
Calendar::roll(field, amount, status);
break;
}
}
void ICU4XCalendar::roll(EDateFields field, int32_t amount,
UErrorCode& status) {
roll(static_cast<UCalendarDateFields>(field), amount, status);
}
int32_t ICU4XCalendar::handleGetExtendedYear(UErrorCode& status) {
if (U_FAILURE(status)) {
return 0;
}
if (newerField(UCAL_EXTENDED_YEAR, UCAL_YEAR) == UCAL_EXTENDED_YEAR) {
return internalGet(UCAL_EXTENDED_YEAR, 1);
}
// We don't yet support the case when UCAL_YEAR is newer.
status = U_UNSUPPORTED_ERROR;
return 0;
}
int32_t ICU4XCalendar::handleGetYearLength(int32_t extendedYear,
UErrorCode& status) const {
// Use the (slower) default implementation for years outside the range
// supported by ICU4X.
if (requiresFallbackForExtendedYear(extendedYear)) {
return icu::Calendar::handleGetYearLength(extendedYear, status);
}
auto* cal = getICU4XCalendar(status);
if (U_FAILURE(status)) {
return 0;
}
UniqueICU4XDate date =
CreateDateFrom(cal, eraName(extendedYear), extendedYear, 1, 1);
if (!date) {
status = U_INTERNAL_PROGRAM_ERROR;
return 0;
}
return capi::ICU4XDate_days_in_year(date.get());
}
/**
* Return the number of days in a month.
*/
int32_t ICU4XCalendar::handleGetMonthLength(int32_t extendedYear, int32_t month,
UErrorCode& status) const {
if (U_FAILURE(status)) {
return 0;
}
// ICU4C supports wrap around. We don't support this case.
if (month < 0 || month > 11) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return 0;
}
// Use the fallback calendar for years outside the range supported by ICU4X.
if (requiresFallbackForExtendedYear(extendedYear)) {
auto* fallback = getFallbackCalendar(status);
if (U_FAILURE(status)) {
return 0;
}
fallback->clear();
fallback->set(UCAL_EXTENDED_YEAR, extendedYear);
fallback->set(UCAL_MONTH, month);
fallback->set(UCAL_DAY_OF_MONTH, 1);
return fallback->getActualMaximum(UCAL_DAY_OF_MONTH, status);
}
auto* cal = getICU4XCalendar(status);
if (U_FAILURE(status)) {
return 0;
}
bool isLeapMonth = internalGet(UCAL_IS_LEAP_MONTH) != 0;
auto monthCode = MonthCode{month + 1, isLeapMonth};
UniqueICU4XDate date = CreateDateFromCodes(cal, eraName(extendedYear),
extendedYear, monthCode, 1);
if (!date) {
status = U_INTERNAL_PROGRAM_ERROR;
return 0;
}
return capi::ICU4XDate_days_in_month(date.get());
}
/**
* Return the start of the month as a Julian date.
*/
int64_t ICU4XCalendar::handleComputeMonthStart(int32_t extendedYear,
int32_t month, UBool useMonth,
UErrorCode& status) const {
if (U_FAILURE(status)) {
return 0;
}
// ICU4C supports wrap around. We don't support this case.
if (month < 0 || month > 11) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return 0;
}
// Use the fallback calendar for years outside the range supported by ICU4X.
if (requiresFallbackForExtendedYear(extendedYear)) {
auto* fallback = getFallbackCalendar(status);
if (U_FAILURE(status)) {
return 0;
}
fallback->clear();
fallback->set(UCAL_EXTENDED_YEAR, extendedYear);
if (useMonth) {
fallback->set(UCAL_MONTH, month);
fallback->set(UCAL_IS_LEAP_MONTH, internalGet(UCAL_IS_LEAP_MONTH));
} else {
fallback->set(UCAL_ORDINAL_MONTH, month);
}
fallback->set(UCAL_DAY_OF_MONTH, 1);
int32_t newMoon = fallback->get(UCAL_JULIAN_DAY, status);
if (U_FAILURE(status)) {
return 0;
}
return newMoon - 1;
}
auto* cal = getICU4XCalendar(status);
if (U_FAILURE(status)) {
return 0;
}
UniqueICU4XDate date{};
if (useMonth) {
bool isLeapMonth = internalGet(UCAL_IS_LEAP_MONTH) != 0;
auto monthCode = MonthCode{month + 1, isLeapMonth};
date = CreateDateFromCodes(cal, eraName(extendedYear), extendedYear,
monthCode, 1);
} else {
date =
CreateDateFrom(cal, eraName(extendedYear), extendedYear, month + 1, 1);
}
if (!date) {
status = U_INTERNAL_PROGRAM_ERROR;
return 0;
}
auto isoDate = ToISODate(date.get());
int32_t newMoon = MakeDay(isoDate);
return (newMoon - 1) + kEpochStartAsJulianDay;
}
/**
* Default implementation of handleComputeFields when using the fallback
* calendar.
*/
void ICU4XCalendar::handleComputeFieldsFromFallback(int32_t julianDay,
UErrorCode& status) {
auto* fallback = getFallbackCalendar(status);
if (U_FAILURE(status)) {
return;
}
fallback->clear();
fallback->set(UCAL_JULIAN_DAY, julianDay);
internalSet(UCAL_ERA, fallback->get(UCAL_ERA, status));
internalSet(UCAL_YEAR, fallback->get(UCAL_YEAR, status));
internalSet(UCAL_EXTENDED_YEAR, fallback->get(UCAL_EXTENDED_YEAR, status));
internalSet(UCAL_MONTH, fallback->get(UCAL_MONTH, status));
internalSet(UCAL_ORDINAL_MONTH, fallback->get(UCAL_ORDINAL_MONTH, status));
internalSet(UCAL_IS_LEAP_MONTH, fallback->get(UCAL_IS_LEAP_MONTH, status));
internalSet(UCAL_DAY_OF_MONTH, fallback->get(UCAL_DAY_OF_MONTH, status));
internalSet(UCAL_DAY_OF_YEAR, fallback->get(UCAL_DAY_OF_YEAR, status));
}
} // namespace mozilla::intl::calendar

View File

@@ -0,0 +1,168 @@
/* 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/. */
#ifndef intl_components_calendar_ICU4XCalendar_h_
#define intl_components_calendar_ICU4XCalendar_h_
#include "mozilla/intl/calendar/ICU4XUniquePtr.h"
#include "mozilla/intl/calendar/ISODate.h"
#include "mozilla/intl/calendar/MonthCode.h"
#include <memory>
#include <mutex>
#include <stdint.h>
#include <string_view>
#include "unicode/calendar.h"
#include "unicode/locid.h"
#include "unicode/timezone.h"
#include "unicode/utypes.h"
#include "ICU4XAnyCalendarKind.h"
namespace mozilla::intl::calendar {
/**
* Abstract class to implement icu::Calendar using ICU4X.
*/
class ICU4XCalendar : public icu::Calendar {
mutable UniqueICU4XCalendar calendar_{};
mutable std::unique_ptr<icu::Calendar> fallback_{};
capi::ICU4XAnyCalendarKind kind_;
protected:
ICU4XCalendar(capi::ICU4XAnyCalendarKind kind, const icu::Locale& locale,
UErrorCode& success);
ICU4XCalendar(capi::ICU4XAnyCalendarKind kind, const icu::TimeZone& timeZone,
const icu::Locale& locale, UErrorCode& success);
ICU4XCalendar(const ICU4XCalendar& other);
/**
* Get or create the underlying ICU4X calendar.
*/
capi::ICU4XCalendar* getICU4XCalendar(UErrorCode& status) const;
/**
* Get or create the ICU4C fallback calendar implementation.
*/
icu::Calendar* getFallbackCalendar(UErrorCode& status) const;
protected:
/**
* Return the ICU4X era name for the given extended year.
*/
virtual std::string_view eraName(int32_t extendedYear) const = 0;
/**
* Return true if this calendar contains any leap months.
*/
virtual bool hasLeapMonths() const = 0;
/**
* Return true if this calendar contains the requested month code.
*/
virtual bool hasMonthCode(MonthCode monthCode) const = 0;
/**
* Subclasses can request to use the ICU4C fallback calendar.
*
* Can be removed when <https://github.com/unicode-org/icu4x/issues/4917> is
* fixed.
*/
virtual bool requiresFallbackForExtendedYear(int32_t year) const = 0;
virtual bool requiresFallbackForGregorianYear(int32_t year) const = 0;
protected:
static constexpr int32_t kEpochStartAsJulianDay =
2440588; // January 1, 1970 (Gregorian)
/**
* Return the month code of |date|.
*/
static MonthCode monthCodeFrom(const capi::ICU4XDate* date,
UErrorCode& status);
/**
* Create a new ICU4X date object from an ISO date.
*/
UniqueICU4XDate createICU4XDate(const ISODate& date,
UErrorCode& status) const;
/**
* Create a new ICU4X date object from a calendar date.
*/
UniqueICU4XDate createICU4XDate(const CalendarDate& date,
UErrorCode& status) const;
public:
ICU4XCalendar() = delete;
virtual ~ICU4XCalendar();
const char* getTemporalMonthCode(UErrorCode& status) const override;
void setTemporalMonthCode(const char* code, UErrorCode& status) override;
void add(UCalendarDateFields field, int32_t amount,
UErrorCode& status) override;
void add(EDateFields field, int32_t amount, UErrorCode& status) override;
void roll(UCalendarDateFields field, int32_t amount,
UErrorCode& status) override;
void roll(EDateFields field, int32_t amount, UErrorCode& status) override;
protected:
int32_t internalGetMonth(int32_t defaultValue,
UErrorCode& status) const override;
int32_t internalGetMonth(UErrorCode& status) const override;
int64_t handleComputeMonthStart(int32_t extendedYear, int32_t month,
UBool useMonth,
UErrorCode& status) const override;
int32_t handleGetMonthLength(int32_t extendedYear, int32_t month,
UErrorCode& status) const override;
int32_t handleGetYearLength(int32_t extendedYear,
UErrorCode& status) const override;
int32_t handleGetExtendedYear(UErrorCode& status) override;
protected:
/**
* handleComputeFields implementation using the ICU4C fallback calendar.
*/
void handleComputeFieldsFromFallback(int32_t julianDay, UErrorCode& status);
};
/**
* `IMPL_SYSTEM_DEFAULT_CENTURY` is internal to "i18n/gregoimp.h", so we have
* to provider our own helper class to implement default centuries.
*/
template <class Calendar, class Locale>
class SystemDefaultCentury {
mutable UDate start_ = DBL_MIN;
mutable int32_t startYear_ = -1;
mutable std::once_flag init_{};
void initialize() const {
UErrorCode status = U_ZERO_ERROR;
Calendar calendar(Locale::identifier, status);
if (U_FAILURE(status)) {
return;
}
calendar.setTime(icu::Calendar::getNow(), status);
calendar.add(UCAL_EXTENDED_YEAR, -80, status);
start_ = calendar.getTime(status);
startYear_ = calendar.get(UCAL_YEAR, status);
}
public:
UDate start() const {
std::call_once(init_, [this] { initialize(); });
return start_;
}
int32_t startYear() const {
std::call_once(init_, [this] { initialize(); });
return startYear_;
}
};
} // namespace mozilla::intl::calendar
#endif

View File

@@ -0,0 +1,204 @@
/* 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/intl/calendar/ICU4XChineseBasedCalendar.h"
namespace mozilla::intl::calendar {
ICU4XChineseBasedCalendar::ICU4XChineseBasedCalendar(
capi::ICU4XAnyCalendarKind kind, const icu::Locale& locale,
UErrorCode& success)
: ICU4XCalendar(kind, locale, success) {}
ICU4XChineseBasedCalendar::ICU4XChineseBasedCalendar(
capi::ICU4XAnyCalendarKind kind, const icu::TimeZone& timeZone,
const icu::Locale& locale, UErrorCode& success)
: ICU4XCalendar(kind, timeZone, locale, success) {}
ICU4XChineseBasedCalendar::ICU4XChineseBasedCalendar(
const ICU4XChineseBasedCalendar& other)
: ICU4XCalendar(other) {}
ICU4XChineseBasedCalendar::~ICU4XChineseBasedCalendar() = default;
////////////////////////////////////////////
// ICU4XCalendar implementation overrides //
////////////////////////////////////////////
bool ICU4XChineseBasedCalendar::hasLeapMonths() const { return true; }
bool ICU4XChineseBasedCalendar::hasMonthCode(MonthCode monthCode) const {
return monthCode.ordinal() <= 12;
}
bool ICU4XChineseBasedCalendar::requiresFallbackForExtendedYear(
int32_t year) const {
// Same limits as in js/src/builtin/temporal/Calendar.cpp.
return std::abs(year) > 10'000;
}
bool ICU4XChineseBasedCalendar::requiresFallbackForGregorianYear(
int32_t year) const {
// Same limits as in js/src/builtin/temporal/Calendar.cpp.
return std::abs(year) > 10'000;
}
////////////////////////////////////////////
// icu::Calendar implementation overrides //
////////////////////////////////////////////
bool ICU4XChineseBasedCalendar::inTemporalLeapYear(UErrorCode& status) const {
int32_t days = getActualMaximum(UCAL_DAY_OF_YEAR, status);
if (U_FAILURE(status)) {
return false;
}
constexpr int32_t maxDaysInMonth = 30;
constexpr int32_t monthsInNonLeapYear = 12;
return days > (monthsInNonLeapYear * maxDaysInMonth);
}
int32_t ICU4XChineseBasedCalendar::getRelatedYear(UErrorCode& status) const {
int32_t year = get(UCAL_EXTENDED_YEAR, status);
if (U_FAILURE(status)) {
return 0;
}
return year + relatedYearDifference();
}
void ICU4XChineseBasedCalendar::setRelatedYear(int32_t year) {
set(UCAL_EXTENDED_YEAR, year - relatedYearDifference());
}
void ICU4XChineseBasedCalendar::handleComputeFields(int32_t julianDay,
UErrorCode& status) {
int32_t gyear = getGregorianYear();
// Use the fallback calendar for years outside the range supported by ICU4X.
if (requiresFallbackForGregorianYear(gyear)) {
handleComputeFieldsFromFallback(julianDay, status);
return;
}
int32_t gmonth = getGregorianMonth() + 1;
int32_t gday = getGregorianDayOfMonth();
MOZ_ASSERT(1 <= gmonth && gmonth <= 12);
MOZ_ASSERT(1 <= gday && gday <= 31);
auto date = createICU4XDate(ISODate{gyear, gmonth, gday}, status);
if (U_FAILURE(status)) {
return;
}
MOZ_ASSERT(date);
MonthCode monthCode = monthCodeFrom(date.get(), status);
if (U_FAILURE(status)) {
return;
}
int32_t extendedYear = capi::ICU4XDate_year_in_era(date.get());
int32_t month = capi::ICU4XDate_ordinal_month(date.get());
int32_t dayOfMonth = capi::ICU4XDate_day_of_month(date.get());
int32_t dayOfYear = capi::ICU4XDate_day_of_year(date.get());
MOZ_ASSERT(1 <= month && month <= 13);
MOZ_ASSERT(1 <= dayOfMonth && dayOfMonth <= 30);
MOZ_ASSERT(1 <= dayOfYear && dayOfYear <= (13 * 30));
// Compute the cycle and year of cycle relative to the Chinese calendar, even
// when this is the Dangi calendar.
int32_t chineseExtendedYear =
extendedYear + relatedYearDifference() - chineseRelatedYearDiff;
int32_t cycle_year = chineseExtendedYear - 1;
int32_t cycle = FloorDiv(cycle_year, 60);
int32_t yearOfCycle = cycle_year - (cycle * 60);
internalSet(UCAL_ERA, cycle + 1);
internalSet(UCAL_YEAR, yearOfCycle + 1);
internalSet(UCAL_EXTENDED_YEAR, extendedYear);
internalSet(UCAL_MONTH, monthCode.ordinal() - 1);
internalSet(UCAL_ORDINAL_MONTH, month - 1);
internalSet(UCAL_IS_LEAP_MONTH, monthCode.isLeapMonth() ? 1 : 0);
internalSet(UCAL_DAY_OF_MONTH, dayOfMonth);
internalSet(UCAL_DAY_OF_YEAR, dayOfYear);
}
// Limits table copied from i18n/chnsecal.cpp. Licensed under:
//
// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
static const int32_t CHINESE_CALENDAR_LIMITS[UCAL_FIELD_COUNT][4] = {
// clang-format off
// Minimum Greatest Least Maximum
// Minimum Maximum
{ 1, 1, 83333, 83333}, // ERA
{ 1, 1, 60, 60}, // YEAR
{ 0, 0, 11, 11}, // MONTH
{ 1, 1, 50, 55}, // WEEK_OF_YEAR
{/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // WEEK_OF_MONTH
{ 1, 1, 29, 30}, // DAY_OF_MONTH
{ 1, 1, 353, 385}, // DAY_OF_YEAR
{/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DAY_OF_WEEK
{ -1, -1, 5, 5}, // DAY_OF_WEEK_IN_MONTH
{/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // AM_PM
{/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // HOUR
{/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // HOUR_OF_DAY
{/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MINUTE
{/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // SECOND
{/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECOND
{/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // ZONE_OFFSET
{/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DST_OFFSET
{ -5000000, -5000000, 5000000, 5000000}, // YEAR_WOY
{/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DOW_LOCAL
{ -5000000, -5000000, 5000000, 5000000}, // EXTENDED_YEAR
{/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // JULIAN_DAY
{/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECONDS_IN_DAY
{ 0, 0, 1, 1}, // IS_LEAP_MONTH
{ 0, 0, 11, 12}, // ORDINAL_MONTH
// clang-format on
};
int32_t ICU4XChineseBasedCalendar::handleGetLimit(UCalendarDateFields field,
ELimitType limitType) const {
return CHINESE_CALENDAR_LIMITS[field][limitType];
}
// Field resolution table copied from i18n/chnsecal.cpp. Licensed under:
//
// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
const icu::UFieldResolutionTable
ICU4XChineseBasedCalendar::CHINESE_DATE_PRECEDENCE[] = {
// clang-format off
{
{ UCAL_DAY_OF_MONTH, kResolveSTOP },
{ UCAL_WEEK_OF_YEAR, UCAL_DAY_OF_WEEK, kResolveSTOP },
{ UCAL_WEEK_OF_MONTH, UCAL_DAY_OF_WEEK, kResolveSTOP },
{ UCAL_DAY_OF_WEEK_IN_MONTH, UCAL_DAY_OF_WEEK, kResolveSTOP },
{ UCAL_WEEK_OF_YEAR, UCAL_DOW_LOCAL, kResolveSTOP },
{ UCAL_WEEK_OF_MONTH, UCAL_DOW_LOCAL, kResolveSTOP },
{ UCAL_DAY_OF_WEEK_IN_MONTH, UCAL_DOW_LOCAL, kResolveSTOP },
{ UCAL_DAY_OF_YEAR, kResolveSTOP },
{ kResolveRemap | UCAL_DAY_OF_MONTH, UCAL_IS_LEAP_MONTH, kResolveSTOP },
{ kResolveSTOP }
},
{
{ UCAL_WEEK_OF_YEAR, kResolveSTOP },
{ UCAL_WEEK_OF_MONTH, kResolveSTOP },
{ UCAL_DAY_OF_WEEK_IN_MONTH, kResolveSTOP },
{ kResolveRemap | UCAL_DAY_OF_WEEK_IN_MONTH, UCAL_DAY_OF_WEEK, kResolveSTOP },
{ kResolveRemap | UCAL_DAY_OF_WEEK_IN_MONTH, UCAL_DOW_LOCAL, kResolveSTOP },
{ kResolveSTOP }
},
{{kResolveSTOP}}
// clang-format on
};
const icu::UFieldResolutionTable*
ICU4XChineseBasedCalendar::getFieldResolutionTable() const {
return CHINESE_DATE_PRECEDENCE;
}
} // namespace mozilla::intl::calendar

View File

@@ -0,0 +1,63 @@
/* 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/. */
#ifndef intl_components_calendar_ICU4XChineseBasedCalendar_h_
#define intl_components_calendar_ICU4XChineseBasedCalendar_h_
#include "mozilla/intl/calendar/ICU4XCalendar.h"
#include <stdint.h>
#include <string_view>
namespace mozilla::intl::calendar {
/**
* Abstract base class for Chinese-based calendars.
*
* Overrides the same methods as icu::ChineseCalendar to ensure compatible
* behavior even when using ICU4X as the underlying calendar implementation.
*/
class ICU4XChineseBasedCalendar : public ICU4XCalendar {
protected:
ICU4XChineseBasedCalendar(capi::ICU4XAnyCalendarKind kind,
const icu::Locale& locale, UErrorCode& success);
ICU4XChineseBasedCalendar(capi::ICU4XAnyCalendarKind kind,
const icu::TimeZone& timeZone,
const icu::Locale& locale, UErrorCode& success);
ICU4XChineseBasedCalendar(const ICU4XChineseBasedCalendar& other);
public:
ICU4XChineseBasedCalendar() = delete;
virtual ~ICU4XChineseBasedCalendar();
protected:
bool hasLeapMonths() const override;
bool hasMonthCode(MonthCode monthCode) const override;
bool requiresFallbackForExtendedYear(int32_t year) const override;
bool requiresFallbackForGregorianYear(int32_t year) const override;
/**
* Difference to the related Gregorian year.
*/
virtual int32_t relatedYearDifference() const = 0;
static constexpr int32_t chineseRelatedYearDiff = -2637;
public:
bool inTemporalLeapYear(UErrorCode& status) const override;
int32_t getRelatedYear(UErrorCode& status) const override;
void setRelatedYear(int32_t year) override;
protected:
void handleComputeFields(int32_t julianDay, UErrorCode& status) override;
int32_t handleGetLimit(UCalendarDateFields field,
ELimitType limitType) const override;
const icu::UFieldResolutionTable* getFieldResolutionTable() const override;
private:
static const icu::UFieldResolutionTable CHINESE_DATE_PRECEDENCE[];
};
} // namespace mozilla::intl::calendar
#endif

View File

@@ -0,0 +1,55 @@
/* 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/intl/calendar/ICU4XChineseCalendar.h"
namespace mozilla::intl::calendar {
ICU4XChineseCalendar::ICU4XChineseCalendar(const icu::Locale& locale,
UErrorCode& success)
: ICU4XChineseBasedCalendar(capi::ICU4XAnyCalendarKind_Chinese, locale,
success) {}
ICU4XChineseCalendar::ICU4XChineseCalendar(const icu::TimeZone& timeZone,
const icu::Locale& locale,
UErrorCode& success)
: ICU4XChineseBasedCalendar(capi::ICU4XAnyCalendarKind_Chinese, timeZone,
locale, success) {}
ICU4XChineseCalendar::ICU4XChineseCalendar(const ICU4XChineseCalendar& other)
: ICU4XChineseBasedCalendar(other) {}
ICU4XChineseCalendar::~ICU4XChineseCalendar() = default;
ICU4XChineseCalendar* ICU4XChineseCalendar::clone() const {
return new ICU4XChineseCalendar(*this);
}
const char* ICU4XChineseCalendar::getType() const { return "chinese"; }
////////////////////////////////////////////
// ICU4XCalendar implementation overrides //
////////////////////////////////////////////
std::string_view ICU4XChineseCalendar::eraName(int32_t extendedYear) const {
return "chinese";
}
////////////////////////////////////////////
// icu::Calendar implementation overrides //
////////////////////////////////////////////
UDate ICU4XChineseCalendar::defaultCenturyStart() const {
return defaultCentury_.start();
}
int32_t ICU4XChineseCalendar::defaultCenturyStartYear() const {
return defaultCentury_.startYear();
}
UBool ICU4XChineseCalendar::haveDefaultCentury() const { return true; }
UOBJECT_DEFINE_RTTI_IMPLEMENTATION(ICU4XChineseCalendar)
} // namespace mozilla::intl::calendar

View File

@@ -0,0 +1,60 @@
/* 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/. */
#ifndef intl_components_calendar_ICU4XChineseCalendar_h_
#define intl_components_calendar_ICU4XChineseCalendar_h_
#include "mozilla/intl/calendar/ICU4XChineseBasedCalendar.h"
#include <stdint.h>
#include <string_view>
#include "unicode/uobject.h"
namespace mozilla::intl::calendar {
/**
* Chinese calendar implementation.
*
* Overrides the same methods as icu::ChineseCalendar to ensure compatible
* behavior even when using ICU4X as the underlying calendar implementation.
*/
class ICU4XChineseCalendar : public ICU4XChineseBasedCalendar {
public:
ICU4XChineseCalendar() = delete;
ICU4XChineseCalendar(const icu::Locale& locale, UErrorCode& success);
ICU4XChineseCalendar(const icu::TimeZone& timeZone, const icu::Locale& locale,
UErrorCode& success);
ICU4XChineseCalendar(const ICU4XChineseCalendar& other);
virtual ~ICU4XChineseCalendar();
ICU4XChineseCalendar* clone() const override;
const char* getType() const override;
protected:
std::string_view eraName(int32_t extendedYear) const override;
int32_t relatedYearDifference() const override {
return chineseRelatedYearDiff;
}
public:
UClassID getDynamicClassID() const override;
static UClassID U_EXPORT2 getStaticClassID();
protected:
DECLARE_OVERRIDE_SYSTEM_DEFAULT_CENTURY
struct SystemDefaultCenturyLocale {
static inline const char* identifier = "@calendar=chinese";
};
static inline SystemDefaultCentury<ICU4XChineseCalendar,
SystemDefaultCenturyLocale>
defaultCentury_{};
};
} // namespace mozilla::intl::calendar
#endif

View File

@@ -0,0 +1,55 @@
/* 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/intl/calendar/ICU4XDangiCalendar.h"
namespace mozilla::intl::calendar {
ICU4XDangiCalendar::ICU4XDangiCalendar(const icu::Locale& locale,
UErrorCode& success)
: ICU4XChineseBasedCalendar(capi::ICU4XAnyCalendarKind_Dangi, locale,
success) {}
ICU4XDangiCalendar::ICU4XDangiCalendar(const icu::TimeZone& timeZone,
const icu::Locale& locale,
UErrorCode& success)
: ICU4XChineseBasedCalendar(capi::ICU4XAnyCalendarKind_Dangi, timeZone,
locale, success) {}
ICU4XDangiCalendar::ICU4XDangiCalendar(const ICU4XDangiCalendar& other)
: ICU4XChineseBasedCalendar(other) {}
ICU4XDangiCalendar::~ICU4XDangiCalendar() = default;
ICU4XDangiCalendar* ICU4XDangiCalendar::clone() const {
return new ICU4XDangiCalendar(*this);
}
const char* ICU4XDangiCalendar::getType() const { return "dangi"; }
////////////////////////////////////////////
// ICU4XCalendar implementation overrides //
////////////////////////////////////////////
std::string_view ICU4XDangiCalendar::eraName(int32_t extendedYear) const {
return "dangi";
}
////////////////////////////////////////////
// icu::Calendar implementation overrides //
////////////////////////////////////////////
UDate ICU4XDangiCalendar::defaultCenturyStart() const {
return defaultCentury_.start();
}
int32_t ICU4XDangiCalendar::defaultCenturyStartYear() const {
return defaultCentury_.startYear();
}
UBool ICU4XDangiCalendar::haveDefaultCentury() const { return true; }
UOBJECT_DEFINE_RTTI_IMPLEMENTATION(ICU4XDangiCalendar)
} // namespace mozilla::intl::calendar

View File

@@ -0,0 +1,62 @@
/* 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/. */
#ifndef intl_components_calendar_ICU4XDangiCalendar_h_
#define intl_components_calendar_ICU4XDangiCalendar_h_
#include "mozilla/intl/calendar/ICU4XChineseBasedCalendar.h"
#include <stdint.h>
#include <string_view>
#include "unicode/uobject.h"
namespace mozilla::intl::calendar {
/**
* Dangi (traditional Korean) calendar implementation.
*
* Overrides the same methods as icu::DangiCalendar to ensure compatible
* behavior even when using ICU4X as the underlying calendar implementation.
*/
class ICU4XDangiCalendar : public ICU4XChineseBasedCalendar {
public:
ICU4XDangiCalendar() = delete;
ICU4XDangiCalendar(const icu::Locale& locale, UErrorCode& success);
ICU4XDangiCalendar(const icu::TimeZone& timeZone, const icu::Locale& locale,
UErrorCode& success);
ICU4XDangiCalendar(const ICU4XDangiCalendar& other);
virtual ~ICU4XDangiCalendar();
ICU4XDangiCalendar* clone() const override;
const char* getType() const override;
protected:
std::string_view eraName(int32_t extendedYear) const override;
static constexpr int32_t dangiRelatedYearDiff = -2333;
int32_t relatedYearDifference() const override {
return dangiRelatedYearDiff;
}
public:
UClassID getDynamicClassID() const override;
static UClassID U_EXPORT2 getStaticClassID();
protected:
DECLARE_OVERRIDE_SYSTEM_DEFAULT_CENTURY
struct SystemDefaultCenturyLocale {
static inline const char* identifier = "@calendar=dangi";
};
static inline SystemDefaultCentury<ICU4XDangiCalendar,
SystemDefaultCenturyLocale>
defaultCentury_{};
};
} // namespace mozilla::intl::calendar
#endif

View File

@@ -0,0 +1,42 @@
/* 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/. */
#ifndef intl_components_calendar_ICU4XUniquePtr_h_
#define intl_components_calendar_ICU4XUniquePtr_h_
#include "mozilla/UniquePtr.h"
#include "ICU4XCalendar.h"
#include "ICU4XDate.h"
#include "ICU4XIsoDate.h"
namespace mozilla::intl::calendar {
class ICU4XCalendarDeleter {
public:
void operator()(capi::ICU4XCalendar* ptr) {
capi::ICU4XCalendar_destroy(ptr);
}
};
using UniqueICU4XCalendar =
mozilla::UniquePtr<capi::ICU4XCalendar, ICU4XCalendarDeleter>;
class ICU4XDateDeleter {
public:
void operator()(capi::ICU4XDate* ptr) { capi::ICU4XDate_destroy(ptr); }
};
using UniqueICU4XDate = mozilla::UniquePtr<capi::ICU4XDate, ICU4XDateDeleter>;
class ICU4XIsoDateDeleter {
public:
void operator()(capi::ICU4XIsoDate* ptr) { capi::ICU4XIsoDate_destroy(ptr); }
};
using UniqueICU4XIsoDate =
mozilla::UniquePtr<capi::ICU4XIsoDate, ICU4XIsoDateDeleter>;
} // namespace mozilla::intl::calendar
#endif

View File

@@ -0,0 +1,59 @@
/* 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/intl/calendar/ISODate.h"
#include <array>
#include <stddef.h>
#include <stdint.h>
namespace mozilla::intl::calendar {
// Copied from js/src/builtin/temporal/Calendar.cpp
static int32_t DayFromYear(int32_t year) {
return 365 * (year - 1970) + FloorDiv(year - 1969, 4) -
FloorDiv(year - 1901, 100) + FloorDiv(year - 1601, 400);
}
static constexpr bool IsISOLeapYear(int32_t year) {
return (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0));
}
static constexpr int32_t ISODaysInMonth(int32_t year, int32_t month) {
MOZ_ASSERT(1 <= month && month <= 12);
constexpr uint8_t daysInMonth[2][13] = {
{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}};
return daysInMonth[IsISOLeapYear(year)][month];
}
static constexpr auto FirstDayOfMonth(int32_t year) {
// The following array contains the day of year for the first day of each
// month, where index 0 is January, and day 0 is January 1.
std::array<int32_t, 13> days = {};
for (int32_t month = 1; month <= 12; ++month) {
days[month] = days[month - 1] + ISODaysInMonth(year, month);
}
return days;
}
static int32_t ISODayOfYear(const ISODate& isoDate) {
const auto& [year, month, day] = isoDate;
// First day of month arrays for non-leap and leap years.
constexpr decltype(FirstDayOfMonth(0)) firstDayOfMonth[2] = {
FirstDayOfMonth(1), FirstDayOfMonth(0)};
return firstDayOfMonth[IsISOLeapYear(year)][month - 1] + day;
}
int32_t MakeDay(const ISODate& date) {
return DayFromYear(date.year) + ISODayOfYear(date) - 1;
}
} // namespace mozilla::intl::calendar

View File

@@ -0,0 +1,41 @@
/* 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/. */
#ifndef intl_components_calendar_ISODate_h_
#define intl_components_calendar_ISODate_h_
#include "mozilla/intl/calendar/MonthCode.h"
#include <stdint.h>
namespace mozilla::intl::calendar {
struct ISODate final {
int32_t year = 0;
int32_t month = 0;
int32_t day = 0;
};
struct CalendarDate final {
int32_t year = 0;
MonthCode monthCode = {};
int32_t day = 0;
};
inline int32_t FloorDiv(int32_t dividend, int32_t divisor) {
int32_t quotient = dividend / divisor;
int32_t remainder = dividend % divisor;
if (remainder < 0) {
quotient -= 1;
}
return quotient;
}
/**
* Return the day relative to the Unix epoch, January 1 1970.
*/
int32_t MakeDay(const ISODate& date);
} // namespace mozilla::intl::calendar
#endif

View File

@@ -0,0 +1,102 @@
/* 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/. */
#ifndef intl_components_calendar_MonthCode_h_
#define intl_components_calendar_MonthCode_h_
#include <stddef.h>
#include <stdint.h>
#include <string_view>
namespace mozilla::intl::calendar {
// Copied from js/src/builtin/temporal/MonthCode.h
class MonthCode final {
public:
enum class Code {
Invalid = 0,
// Months 01 - M12.
M01 = 1,
M02,
M03,
M04,
M05,
M06,
M07,
M08,
M09,
M10,
M11,
M12,
// Epagomenal month M13.
M13,
// Leap months M01 - M12.
M01L,
M02L,
M03L,
M04L,
M05L,
M06L,
M07L,
M08L,
M09L,
M10L,
M11L,
M12L,
};
private:
static constexpr int32_t toLeapMonth =
static_cast<int32_t>(Code::M01L) - static_cast<int32_t>(Code::M01);
Code code_ = Code::Invalid;
public:
constexpr MonthCode() = default;
constexpr explicit MonthCode(Code code) : code_(code) {}
constexpr explicit MonthCode(int32_t month, bool isLeapMonth = false) {
code_ = static_cast<Code>(month + (isLeapMonth ? toLeapMonth : 0));
}
constexpr auto code() const { return code_; }
constexpr int32_t ordinal() const {
int32_t ordinal = static_cast<int32_t>(code_);
if (isLeapMonth()) {
ordinal -= toLeapMonth;
}
return ordinal;
}
constexpr bool isLeapMonth() const { return code_ >= Code::M01L; }
constexpr explicit operator std::string_view() const {
constexpr const char* name =
"M01L"
"M02L"
"M03L"
"M04L"
"M05L"
"M06L"
"M07L"
"M08L"
"M09L"
"M10L"
"M11L"
"M12L"
"M13";
size_t index = (ordinal() - 1) * 4;
size_t length = 3 + isLeapMonth();
return {name + index, length};
}
};
} // namespace mozilla::intl::calendar
#endif

View File

@@ -0,0 +1,35 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
FINAL_LIBRARY = "intlcomponents"
EXPORTS.mozilla.intl.calendar = [
"ICU4XCalendar.h",
"ICU4XChineseBasedCalendar.h",
"ICU4XChineseCalendar.h",
"ICU4XDangiCalendar.h",
"ICU4XUniquePtr.h",
"ISODate.h",
"MonthCode.h",
]
LOCAL_INCLUDES += [
"/intl/icu_capi/bindings/c",
]
UNIFIED_SOURCES += [
"ICU4XCalendar.cpp",
"ICU4XChineseBasedCalendar.cpp",
"ICU4XChineseCalendar.cpp",
"ICU4XDangiCalendar.cpp",
"ISODate.cpp",
]
# ICU requires RTTI
if CONFIG["CC_TYPE"] in ("clang", "gcc"):
CXXFLAGS += ["-frtti"]
elif CONFIG["OS_TARGET"] == "WINNT":
CXXFLAGS += ["-GR"]

View File

@@ -5314,6 +5314,10 @@ product.
<li><code>intl/tzdata</code></li>
<li><code>js/src/util</code></li>
</ul>
<p>and to parts of the code in:</p>
<ul>
<li><code>intl/components/src/calendar/ICU4XChineseBasedCalendar.cpp</code></li>
</ul>
</td>
<td>
<pre>