tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

DateIntervalFormat.cpp (10671B)


      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 "DateTimeFormatUtils.h"
      6 #include "ScopedICUObject.h"
      7 
      8 #include "mozilla/intl/Calendar.h"
      9 #include "mozilla/intl/DateIntervalFormat.h"
     10 #include "mozilla/intl/DateTimeFormat.h"
     11 
     12 #if !MOZ_SYSTEM_ICU
     13 #  include "unicode/calendar.h"
     14 #  include "unicode/datefmt.h"
     15 #  include "unicode/dtitvfmt.h"
     16 #endif
     17 
     18 namespace mozilla::intl {
     19 
     20 /**
     21 * PartitionDateTimeRangePattern ( dateTimeFormat, x, y ), steps 9-11.
     22 *
     23 * Examine the formatted value to see if any interval span field is present.
     24 *
     25 * https://tc39.es/ecma402/#sec-partitiondatetimerangepattern
     26 */
     27 static ICUResult DateFieldsPracticallyEqual(
     28    const UFormattedValue* aFormattedValue, bool* aEqual) {
     29  if (!aFormattedValue) {
     30    return Err(ICUError::InternalError);
     31  }
     32 
     33  MOZ_ASSERT(aEqual);
     34  *aEqual = false;
     35  UErrorCode status = U_ZERO_ERROR;
     36  UConstrainedFieldPosition* fpos = ucfpos_open(&status);
     37  if (U_FAILURE(status)) {
     38    return Err(ToICUError(status));
     39  }
     40  ScopedICUObject<UConstrainedFieldPosition, ucfpos_close> toCloseFpos(fpos);
     41 
     42  // We're only interested in UFIELD_CATEGORY_DATE_INTERVAL_SPAN fields.
     43  ucfpos_constrainCategory(fpos, UFIELD_CATEGORY_DATE_INTERVAL_SPAN, &status);
     44  if (U_FAILURE(status)) {
     45    return Err(ToICUError(status));
     46  }
     47 
     48  bool hasSpan = ufmtval_nextPosition(aFormattedValue, fpos, &status);
     49  if (U_FAILURE(status)) {
     50    return Err(ToICUError(status));
     51  }
     52 
     53  // When no date interval span field was found, both dates are "practically
     54  // equal" per PartitionDateTimeRangePattern.
     55  *aEqual = !hasSpan;
     56  return Ok();
     57 }
     58 
     59 /* static */
     60 Result<UniquePtr<DateIntervalFormat>, ICUError> DateIntervalFormat::TryCreate(
     61    Span<const char> aLocale, Span<const char16_t> aSkeleton,
     62    Span<const char16_t> aTimeZone) {
     63  UErrorCode status = U_ZERO_ERROR;
     64  UDateIntervalFormat* dif =
     65      udtitvfmt_open(IcuLocale(aLocale), aSkeleton.data(),
     66                     AssertedCast<int32_t>(aSkeleton.size()), aTimeZone.data(),
     67                     AssertedCast<int32_t>(aTimeZone.size()), &status);
     68  if (U_FAILURE(status)) {
     69    return Err(ToICUError(status));
     70  }
     71 
     72  auto result = UniquePtr<DateIntervalFormat>(new DateIntervalFormat(dif));
     73 
     74 #if !MOZ_SYSTEM_ICU
     75  auto* dtif = reinterpret_cast<icu::DateIntervalFormat*>(dif);
     76  const icu::Calendar* calendar = dtif->getDateFormat()->getCalendar();
     77 
     78  auto replacement = CreateCalendarOverride(calendar);
     79  if (replacement.isErr()) {
     80    return replacement.propagateErr();
     81  }
     82 
     83  if (auto newCalendar = replacement.unwrap()) {
     84    dtif->adoptCalendar(newCalendar.release());
     85  }
     86 #endif
     87 
     88  return result;
     89 }
     90 
     91 DateIntervalFormat::~DateIntervalFormat() {
     92  MOZ_ASSERT(mDateIntervalFormat);
     93  udtitvfmt_close(mDateIntervalFormat.GetMut());
     94 }
     95 
     96 #if DATE_TIME_FORMAT_REPLACE_SPECIAL_SPACES
     97 // We reach inside the UFormattedValue and modify its internal string. (It's
     98 // crucial that this is just an in-place replacement that doesn't alter any
     99 // field positions, etc., )
    100 static void ReplaceSpecialSpaces(const UFormattedValue* aValue) {
    101  UErrorCode status = U_ZERO_ERROR;
    102  int32_t len;
    103  const UChar* str = ufmtval_getString(aValue, &len, &status);
    104  if (U_FAILURE(status)) {
    105    return;
    106  }
    107 
    108  for (const auto& c : Span(str, len)) {
    109    if (IsSpecialSpace(c)) {
    110      const_cast<UChar&>(c) = ' ';
    111    }
    112  }
    113 }
    114 #endif
    115 
    116 ICUResult DateIntervalFormat::TryFormatCalendar(
    117    const Calendar& aStart, const Calendar& aEnd,
    118    AutoFormattedDateInterval& aFormatted, bool* aPracticallyEqual) const {
    119  MOZ_ASSERT(aFormatted.IsValid());
    120 
    121  UErrorCode status = U_ZERO_ERROR;
    122  udtitvfmt_formatCalendarToResult(mDateIntervalFormat.GetConst(),
    123                                   aStart.GetUCalendar(), aEnd.GetUCalendar(),
    124                                   aFormatted.GetFormatted(), &status);
    125 
    126  if (U_FAILURE(status)) {
    127    return Err(ToICUError(status));
    128  }
    129 
    130 #if DATE_TIME_FORMAT_REPLACE_SPECIAL_SPACES
    131  ReplaceSpecialSpaces(aFormatted.Value());
    132 #endif
    133 
    134  MOZ_TRY(DateFieldsPracticallyEqual(aFormatted.Value(), aPracticallyEqual));
    135  return Ok();
    136 }
    137 
    138 ICUResult DateIntervalFormat::TryFormatDateTime(
    139    double aStart, double aEnd, AutoFormattedDateInterval& aFormatted,
    140    bool* aPracticallyEqual) const {
    141  MOZ_ASSERT(aFormatted.IsValid());
    142 
    143  UErrorCode status = U_ZERO_ERROR;
    144  udtitvfmt_formatToResult(mDateIntervalFormat.GetConst(), aStart, aEnd,
    145                           aFormatted.GetFormatted(), &status);
    146  if (U_FAILURE(status)) {
    147    return Err(ToICUError(status));
    148  }
    149 
    150 #if DATE_TIME_FORMAT_REPLACE_SPECIAL_SPACES
    151  ReplaceSpecialSpaces(aFormatted.Value());
    152 #endif
    153 
    154  MOZ_TRY(DateFieldsPracticallyEqual(aFormatted.Value(), aPracticallyEqual));
    155  return Ok();
    156 }
    157 
    158 ICUResult DateIntervalFormat::TryFormatDateTime(
    159    double aStart, double aEnd, const DateTimeFormat* aDateTimeFormat,
    160    AutoFormattedDateInterval& aFormatted, bool* aPracticallyEqual) const {
    161 #if MOZ_SYSTEM_ICU
    162  // We can't access the calendar used by UDateIntervalFormat to change it to a
    163  // proleptic Gregorian calendar. Instead we need to call a different formatter
    164  // function which accepts UCalendar instead of UDate.
    165  // But creating new UCalendar objects for each call is slow, so when we can
    166  // ensure that the input dates are later than the Gregorian change date,
    167  // directly call the formatter functions taking UDate.
    168 
    169  constexpr int32_t msPerDay = 24 * 60 * 60 * 1000;
    170 
    171  // The Gregorian change date "1582-10-15T00:00:00.000Z".
    172  constexpr double GregorianChangeDate = -12219292800000.0;
    173 
    174  // Add a full day to account for time zone offsets.
    175  constexpr double GregorianChangeDatePlusOneDay =
    176      GregorianChangeDate + msPerDay;
    177 
    178  if (aStart < GregorianChangeDatePlusOneDay ||
    179      aEnd < GregorianChangeDatePlusOneDay) {
    180    // Create calendar objects for the start and end date by cloning the date
    181    // formatter calendar. The date formatter calendar already has the correct
    182    // time zone set and was changed to use a proleptic Gregorian calendar.
    183    auto startCal = aDateTimeFormat->CloneCalendar(aStart);
    184    if (startCal.isErr()) {
    185      return startCal.propagateErr();
    186    }
    187 
    188    auto endCal = aDateTimeFormat->CloneCalendar(aEnd);
    189    if (endCal.isErr()) {
    190      return endCal.propagateErr();
    191    }
    192 
    193    return TryFormatCalendar(*startCal.unwrap(), *endCal.unwrap(), aFormatted,
    194                             aPracticallyEqual);
    195  }
    196 #endif
    197 
    198  // The common fast path which doesn't require creating calendar objects.
    199  return TryFormatDateTime(aStart, aEnd, aFormatted, aPracticallyEqual);
    200 }
    201 
    202 ICUResult DateIntervalFormat::TryFormattedToParts(
    203    const AutoFormattedDateInterval& aFormatted,
    204    DateTimePartVector& aParts) const {
    205  MOZ_ASSERT(aFormatted.IsValid());
    206  const UFormattedValue* value = aFormatted.Value();
    207  if (!value) {
    208    return Err(ICUError::InternalError);
    209  }
    210 
    211  size_t lastEndIndex = 0;
    212  auto AppendPart = [&](DateTimePartType type, size_t endIndex,
    213                        DateTimePartSource source) {
    214    if (!aParts.emplaceBack(type, endIndex, source)) {
    215      return false;
    216    }
    217 
    218    lastEndIndex = endIndex;
    219    return true;
    220  };
    221 
    222  UErrorCode status = U_ZERO_ERROR;
    223  UConstrainedFieldPosition* fpos = ucfpos_open(&status);
    224  if (U_FAILURE(status)) {
    225    return Err(ToICUError(status));
    226  }
    227  ScopedICUObject<UConstrainedFieldPosition, ucfpos_close> toCloseFpos(fpos);
    228 
    229  size_t categoryEndIndex = 0;
    230  DateTimePartSource source = DateTimePartSource::Shared;
    231 
    232  while (true) {
    233    bool hasMore = ufmtval_nextPosition(value, fpos, &status);
    234    if (U_FAILURE(status)) {
    235      return Err(ToICUError(status));
    236    }
    237    if (!hasMore) {
    238      break;
    239    }
    240 
    241    int32_t category = ucfpos_getCategory(fpos, &status);
    242    if (U_FAILURE(status)) {
    243      return Err(ToICUError(status));
    244    }
    245 
    246    int32_t field = ucfpos_getField(fpos, &status);
    247    if (U_FAILURE(status)) {
    248      return Err(ToICUError(status));
    249    }
    250 
    251    int32_t beginIndexInt, endIndexInt;
    252    ucfpos_getIndexes(fpos, &beginIndexInt, &endIndexInt, &status);
    253    if (U_FAILURE(status)) {
    254      return Err(ToICUError(status));
    255    }
    256 
    257    MOZ_ASSERT(beginIndexInt <= endIndexInt,
    258               "field iterator returning invalid range");
    259 
    260    size_t beginIndex = AssertedCast<size_t>(beginIndexInt);
    261    size_t endIndex = AssertedCast<size_t>(endIndexInt);
    262 
    263    // Indices are guaranteed to be returned in order (from left to right).
    264    MOZ_ASSERT(lastEndIndex <= beginIndex,
    265               "field iteration didn't return fields in order start to "
    266               "finish as expected");
    267 
    268    if (category == UFIELD_CATEGORY_DATE_INTERVAL_SPAN) {
    269      // Append any remaining literal parts before changing the source kind.
    270      if (lastEndIndex < beginIndex) {
    271        if (!AppendPart(DateTimePartType::Literal, beginIndex, source)) {
    272          return Err(ICUError::InternalError);
    273        }
    274      }
    275 
    276      // The special field category UFIELD_CATEGORY_DATE_INTERVAL_SPAN has only
    277      // two allowed values (0 or 1), indicating the begin of the start- resp.
    278      // end-date.
    279      MOZ_ASSERT(field == 0 || field == 1,
    280                 "span category has unexpected value");
    281 
    282      source = field == 0 ? DateTimePartSource::StartRange
    283                          : DateTimePartSource::EndRange;
    284      categoryEndIndex = endIndex;
    285      continue;
    286    }
    287 
    288    // Ignore categories other than UFIELD_CATEGORY_DATE.
    289    if (category != UFIELD_CATEGORY_DATE) {
    290      continue;
    291    }
    292 
    293    DateTimePartType type =
    294        ConvertUFormatFieldToPartType(static_cast<UDateFormatField>(field));
    295    if (lastEndIndex < beginIndex) {
    296      if (!AppendPart(DateTimePartType::Literal, beginIndex, source)) {
    297        return Err(ICUError::InternalError);
    298      }
    299    }
    300 
    301    if (!AppendPart(type, endIndex, source)) {
    302      return Err(ICUError::InternalError);
    303    }
    304 
    305    if (endIndex == categoryEndIndex) {
    306      // Append any remaining literal parts before changing the source kind.
    307      if (lastEndIndex < endIndex) {
    308        if (!AppendPart(DateTimePartType::Literal, endIndex, source)) {
    309          return Err(ICUError::InternalError);
    310        }
    311      }
    312 
    313      source = DateTimePartSource::Shared;
    314    }
    315  }
    316 
    317  // Append any final literal.
    318  auto spanResult = aFormatted.ToSpan();
    319  if (spanResult.isErr()) {
    320    return spanResult.propagateErr();
    321  }
    322  size_t formattedSize = spanResult.unwrap().size();
    323  if (lastEndIndex < formattedSize) {
    324    if (!AppendPart(DateTimePartType::Literal, formattedSize, source)) {
    325      return Err(ICUError::InternalError);
    326    }
    327  }
    328 
    329  return Ok();
    330 }
    331 
    332 }  // namespace mozilla::intl