Bug 1954138 - Part 2: Set Gregorian change date when constructing formatters. r=dminor
Always reset the Gregorian change date, because all uses either require the Gregorian change date to be set to `-8.64e15` or don't care about dates far into the past. The slow `DateIntervalFormat::TryFormatCalendar` is now only used when `MOZ_SYSTEM_ICU` is defined. And also resets the Gregorian change date for "buddhist", "japanese", and "roc" to get consistent results when compared to the ICU4X-based Temporal code. Differential Revision: https://phabricator.services.mozilla.com/D241644
This commit is contained in:
@@ -574,9 +574,6 @@ TEST(IntlDateTimeFormat, SetStartTimeIfGregorian)
|
||||
|
||||
auto timeZone = Some(MakeStringSpan(u"UTC"));
|
||||
|
||||
// Beginning of ECMAScript time.
|
||||
constexpr double StartOfTime = -8.64e15;
|
||||
|
||||
// Gregorian change date defaults to October 15, 1582 in ICU. Test with a date
|
||||
// before the default change date, in this case January 1, 1582.
|
||||
constexpr double FirstJanuary1582 = -12244089600000.0;
|
||||
@@ -597,38 +594,25 @@ TEST(IntlDateTimeFormat, SetStartTimeIfGregorian)
|
||||
MakeStringSpan(locale), style, gen.get(), timeZone)
|
||||
.unwrap();
|
||||
|
||||
const char* Dec22_1581;
|
||||
const char* Jan01_1582;
|
||||
const char* Jan01_1583;
|
||||
if (locale == "en-US-u-ca-iso8601"sv) {
|
||||
Dec22_1581 = "1581 December 22";
|
||||
Jan01_1582 = "1582 January 1";
|
||||
Jan01_1583 = "1583 January 1";
|
||||
} else {
|
||||
Dec22_1581 = "December 22, 1581";
|
||||
Jan01_1582 = "January 1, 1582";
|
||||
Jan01_1583 = "January 1, 1583";
|
||||
}
|
||||
|
||||
TestBuffer<char> buffer;
|
||||
|
||||
// Before the default Gregorian change date, so interpreted in the Julian
|
||||
// calendar, which is December 22, 1581.
|
||||
dtFormat->TryFormat(FirstJanuary1582, buffer).unwrap();
|
||||
ASSERT_TRUE(buffer.verboseMatches(Dec22_1581));
|
||||
|
||||
// After default Gregorian change date, so January 1, 1583.
|
||||
dtFormat->TryFormat(FirstJanuary1582 + oneYear, buffer).unwrap();
|
||||
ASSERT_TRUE(buffer.verboseMatches(Jan01_1583));
|
||||
|
||||
// Adjust the start time to use a proleptic Gregorian calendar.
|
||||
dtFormat->SetStartTimeIfGregorian(StartOfTime);
|
||||
|
||||
// Now interpreted in proleptic Gregorian calendar at January 1, 1582.
|
||||
// Before the default Gregorian change date, but not interpreted in the
|
||||
// Julian calendar, which is December 22, 1581. Instead interpreted in
|
||||
// proleptic Gregorian calendar at January 1, 1582.
|
||||
dtFormat->TryFormat(FirstJanuary1582, buffer).unwrap();
|
||||
ASSERT_TRUE(buffer.verboseMatches(Jan01_1582));
|
||||
|
||||
// Still January 1, 1583.
|
||||
// After default Gregorian change date, so January 1, 1583.
|
||||
dtFormat->TryFormat(FirstJanuary1582 + oneYear, buffer).unwrap();
|
||||
ASSERT_TRUE(buffer.verboseMatches(Jan01_1583));
|
||||
}
|
||||
|
||||
@@ -2,12 +2,18 @@
|
||||
* 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 "DateTimeFormat.h" // for DATE_TIME_FORMAT_REPLACE_SPECIAL_SPACES
|
||||
#include "DateTimeFormatUtils.h"
|
||||
#include "ScopedICUObject.h"
|
||||
|
||||
#include "mozilla/intl/Calendar.h"
|
||||
#include "mozilla/intl/DateIntervalFormat.h"
|
||||
#include "mozilla/intl/DateTimeFormat.h"
|
||||
|
||||
#if !MOZ_SYSTEM_ICU
|
||||
# include "unicode/calendar.h"
|
||||
# include "unicode/datefmt.h"
|
||||
# include "unicode/dtitvfmt.h"
|
||||
#endif
|
||||
|
||||
namespace mozilla::intl {
|
||||
|
||||
@@ -63,7 +69,23 @@ Result<UniquePtr<DateIntervalFormat>, ICUError> DateIntervalFormat::TryCreate(
|
||||
return Err(ToICUError(status));
|
||||
}
|
||||
|
||||
return UniquePtr<DateIntervalFormat>(new DateIntervalFormat(dif));
|
||||
auto result = UniquePtr<DateIntervalFormat>(new DateIntervalFormat(dif));
|
||||
|
||||
#if !MOZ_SYSTEM_ICU
|
||||
auto* dtif = reinterpret_cast<icu::DateIntervalFormat*>(dif);
|
||||
const icu::Calendar* calendar = dtif->getDateFormat()->getCalendar();
|
||||
|
||||
auto replacement = CreateCalendarOverride(calendar);
|
||||
if (replacement.isErr()) {
|
||||
return replacement.propagateErr();
|
||||
}
|
||||
|
||||
if (auto newCalendar = replacement.unwrap()) {
|
||||
dtif->adoptCalendar(newCalendar.release());
|
||||
}
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DateIntervalFormat::~DateIntervalFormat() {
|
||||
@@ -133,6 +155,50 @@ ICUResult DateIntervalFormat::TryFormatDateTime(
|
||||
return Ok();
|
||||
}
|
||||
|
||||
ICUResult DateIntervalFormat::TryFormatDateTime(
|
||||
double aStart, double aEnd, const DateTimeFormat* aDateTimeFormat,
|
||||
AutoFormattedDateInterval& aFormatted, bool* aPracticallyEqual) const {
|
||||
#if MOZ_SYSTEM_ICU
|
||||
// We can't access the calendar used by UDateIntervalFormat to change it to a
|
||||
// proleptic Gregorian calendar. Instead we need to call a different formatter
|
||||
// function which accepts UCalendar instead of UDate.
|
||||
// But creating new UCalendar objects for each call is slow, so when we can
|
||||
// ensure that the input dates are later than the Gregorian change date,
|
||||
// directly call the formatter functions taking UDate.
|
||||
|
||||
constexpr int32_t msPerDay = 24 * 60 * 60 * 1000;
|
||||
|
||||
// The Gregorian change date "1582-10-15T00:00:00.000Z".
|
||||
constexpr double GregorianChangeDate = -12219292800000.0;
|
||||
|
||||
// Add a full day to account for time zone offsets.
|
||||
constexpr double GregorianChangeDatePlusOneDay =
|
||||
GregorianChangeDate + msPerDay;
|
||||
|
||||
if (aStart < GregorianChangeDatePlusOneDay ||
|
||||
aEnd < GregorianChangeDatePlusOneDay) {
|
||||
// Create calendar objects for the start and end date by cloning the date
|
||||
// formatter calendar. The date formatter calendar already has the correct
|
||||
// time zone set and was changed to use a proleptic Gregorian calendar.
|
||||
auto startCal = aDateTimeFormat->CloneCalendar(aStart);
|
||||
if (startCal.isErr()) {
|
||||
return startCal.propagateErr();
|
||||
}
|
||||
|
||||
auto endCal = aDateTimeFormat->CloneCalendar(aEnd);
|
||||
if (endCal.isErr()) {
|
||||
return endCal.propagateErr();
|
||||
}
|
||||
|
||||
return TryFormatCalendar(*startCal.unwrap(), *endCal.unwrap(), aFormatted,
|
||||
aPracticallyEqual);
|
||||
}
|
||||
#endif
|
||||
|
||||
// The common fast path which doesn't require creating calendar objects.
|
||||
return TryFormatDateTime(aStart, aEnd, aFormatted, aPracticallyEqual);
|
||||
}
|
||||
|
||||
ICUResult DateIntervalFormat::TryFormattedToParts(
|
||||
const AutoFormattedDateInterval& aFormatted,
|
||||
DateTimePartVector& aParts) const {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
namespace mozilla::intl {
|
||||
class Calendar;
|
||||
class DateTimeFormat;
|
||||
|
||||
using AutoFormattedDateInterval =
|
||||
AutoFormattedResult<UFormattedDateInterval, udtitvfmt_openResult,
|
||||
@@ -79,6 +80,21 @@ class DateIntervalFormat final {
|
||||
AutoFormattedDateInterval& aFormatted,
|
||||
bool* aPracticallyEqual) const;
|
||||
|
||||
/**
|
||||
* Format a date-time range between two Unix epoch times in milliseconds.
|
||||
*
|
||||
* The result will be stored in aFormatted, caller can use
|
||||
* AutoFormattedDateInterval::ToSpan() to get the formatted string, or pass
|
||||
* the aFormatted to TryFormattedToParts to get the parts vector.
|
||||
*
|
||||
* aPracticallyEqual will be set to true if the date times of the two
|
||||
* Unix epoch times are equal.
|
||||
*/
|
||||
ICUResult TryFormatDateTime(double aStart, double aEnd,
|
||||
const DateTimeFormat* aDateTimeFormat,
|
||||
AutoFormattedDateInterval& aFormatted,
|
||||
bool* aPracticallyEqual) const;
|
||||
|
||||
/**
|
||||
* Convert the formatted DateIntervalFormat into several parts.
|
||||
*
|
||||
|
||||
@@ -279,6 +279,8 @@ Result<UniquePtr<DateTimeFormat>, ICUError> DateTimeFormat::TryCreateFromStyle(
|
||||
return Err(ToICUError(status));
|
||||
}
|
||||
|
||||
MOZ_TRY(ApplyCalendarOverride(dateFormat));
|
||||
|
||||
auto df = UniquePtr<DateTimeFormat>(new DateTimeFormat(dateFormat));
|
||||
|
||||
if (aStyleBag.time && (aStyleBag.hour12 || aStyleBag.hourCycle)) {
|
||||
@@ -564,11 +566,12 @@ DateTimeFormat::TryCreateFromPattern(
|
||||
UDateFormat* dateFormat = udat_open(
|
||||
UDAT_PATTERN, UDAT_PATTERN, IcuLocale(aLocale), tzID, tzIDLength,
|
||||
aPattern.data(), static_cast<int32_t>(aPattern.size()), &status);
|
||||
|
||||
if (U_FAILURE(status)) {
|
||||
return Err(ToICUError(status));
|
||||
}
|
||||
|
||||
MOZ_TRY(ApplyCalendarOverride(dateFormat));
|
||||
|
||||
// The DateTimeFormat wrapper will control the life cycle of the ICU
|
||||
// dateFormat object.
|
||||
return UniquePtr<DateTimeFormat>(new DateTimeFormat(dateFormat));
|
||||
@@ -612,13 +615,6 @@ ICUResult DateTimeFormat::CacheSkeleton(Span<const char16_t> aSkeleton) {
|
||||
return Err(ICUError::OutOfMemory);
|
||||
}
|
||||
|
||||
void DateTimeFormat::SetStartTimeIfGregorian(double aTime) {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
UCalendar* cal = const_cast<UCalendar*>(udat_getCalendar(mDateFormat));
|
||||
ucal_setGregorianChange(cal, aTime, &status);
|
||||
// An error here means the calendar is not Gregorian, and can be ignored.
|
||||
}
|
||||
|
||||
/* static */
|
||||
Result<UniquePtr<Calendar>, ICUError> DateTimeFormat::CloneCalendar(
|
||||
double aUnixEpoch) const {
|
||||
|
||||
@@ -477,12 +477,6 @@ class DateTimeFormat final {
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
void SetStartTimeIfGregorian(double aTime);
|
||||
|
||||
/**
|
||||
* Determines the resolved components for the current DateTimeFormat.
|
||||
|
||||
@@ -3,8 +3,17 @@
|
||||
* 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 "unicode/datefmt.h"
|
||||
# include "unicode/gregocal.h"
|
||||
#endif
|
||||
|
||||
namespace mozilla::intl {
|
||||
|
||||
@@ -101,4 +110,74 @@ DateTimePartType ConvertUFormatFieldToPartType(UDateFormatField fieldName) {
|
||||
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<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{};
|
||||
}
|
||||
#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
|
||||
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()};
|
||||
}
|
||||
|
||||
return UniquePtr<icu::Calendar>{};
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace mozilla::intl
|
||||
|
||||
@@ -5,10 +5,24 @@
|
||||
#define intl_components_DateTimeFormatUtils_h_
|
||||
#include "unicode/udat.h"
|
||||
|
||||
#if !MOZ_SYSTEM_ICU
|
||||
# include "unicode/calendar.h"
|
||||
#endif
|
||||
|
||||
#include "mozilla/Result.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/intl/DateTimePart.h"
|
||||
#include "mozilla/intl/ICUError.h"
|
||||
|
||||
namespace mozilla::intl {
|
||||
DateTimePartType ConvertUFormatFieldToPartType(UDateFormatField fieldName);
|
||||
|
||||
Result<Ok, ICUError> ApplyCalendarOverride(UDateFormat* aDateFormat);
|
||||
|
||||
#if !MOZ_SYSTEM_ICU
|
||||
Result<UniquePtr<icu::Calendar>, ICUError> CreateCalendarOverride(
|
||||
const icu::Calendar* calendar);
|
||||
#endif
|
||||
} // namespace mozilla::intl
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1638,10 +1638,6 @@ static mozilla::intl::DateTimeFormat* NewDateTimeFormat(
|
||||
df = dfResult.unwrap();
|
||||
}
|
||||
|
||||
// ECMAScript requires the Gregorian calendar to be used from the beginning
|
||||
// of ECMAScript time.
|
||||
df->SetStartTimeIfGregorian(StartOfTime);
|
||||
|
||||
return df.release();
|
||||
}
|
||||
|
||||
@@ -2578,51 +2574,12 @@ static bool PartitionDateTimeRangePattern(
|
||||
MOZ_ASSERT(x.isValid());
|
||||
MOZ_ASSERT(y.isValid());
|
||||
|
||||
// We can't access the calendar used by UDateIntervalFormat to change it to a
|
||||
// proleptic Gregorian calendar. Instead we need to call a different formatter
|
||||
// function which accepts UCalendar instead of UDate.
|
||||
// But creating new UCalendar objects for each call is slow, so when we can
|
||||
// ensure that the input dates are later than the Gregorian change date,
|
||||
// directly call the formatter functions taking UDate.
|
||||
|
||||
// The Gregorian change date "1582-10-15T00:00:00.000Z".
|
||||
constexpr double GregorianChangeDate = -12219292800000.0;
|
||||
|
||||
// Add a full day to account for time zone offsets.
|
||||
constexpr double GregorianChangeDatePlusOneDay =
|
||||
GregorianChangeDate + msPerDay;
|
||||
|
||||
mozilla::intl::ICUResult result = Ok();
|
||||
if (x.toDouble() < GregorianChangeDatePlusOneDay ||
|
||||
y.toDouble() < GregorianChangeDatePlusOneDay) {
|
||||
// Create calendar objects for the start and end date by cloning the date
|
||||
// formatter calendar. The date formatter calendar already has the correct
|
||||
// time zone set and was changed to use a proleptic Gregorian calendar.
|
||||
auto startCal = df->CloneCalendar(x.toDouble());
|
||||
if (startCal.isErr()) {
|
||||
intl::ReportInternalError(cx, startCal.unwrapErr());
|
||||
return false;
|
||||
}
|
||||
|
||||
auto endCal = df->CloneCalendar(y.toDouble());
|
||||
if (endCal.isErr()) {
|
||||
intl::ReportInternalError(cx, endCal.unwrapErr());
|
||||
return false;
|
||||
}
|
||||
|
||||
result = dif->TryFormatCalendar(*startCal.unwrap(), *endCal.unwrap(),
|
||||
formatted, equal);
|
||||
} else {
|
||||
// The common fast path which doesn't require creating calendar objects.
|
||||
result =
|
||||
dif->TryFormatDateTime(x.toDouble(), y.toDouble(), formatted, equal);
|
||||
}
|
||||
|
||||
auto result =
|
||||
dif->TryFormatDateTime(x.toDouble(), y.toDouble(), df, formatted, equal);
|
||||
if (result.isErr()) {
|
||||
intl::ReportInternalError(cx, result.unwrapErr());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user