tor-browser

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

ToString.cpp (16819B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sts=2 et sw=2 tw=80:
      3 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "builtin/temporal/ToString.h"
      8 
      9 #include "mozilla/Assertions.h"
     10 
     11 #include <cstdlib>
     12 #include <stddef.h>
     13 #include <stdint.h>
     14 #include <string_view>
     15 
     16 #include "builtin/temporal/Calendar.h"
     17 #include "builtin/temporal/Instant.h"
     18 #include "builtin/temporal/PlainDate.h"
     19 #include "builtin/temporal/PlainDateTime.h"
     20 #include "builtin/temporal/PlainMonthDay.h"
     21 #include "builtin/temporal/PlainYearMonth.h"
     22 #include "builtin/temporal/Temporal.h"
     23 #include "builtin/temporal/TemporalTypes.h"
     24 #include "builtin/temporal/TemporalUnit.h"
     25 #include "builtin/temporal/TimeZone.h"
     26 #include "builtin/temporal/ZonedDateTime.h"
     27 #include "js/RootingAPI.h"
     28 #include "util/StringBuilder.h"
     29 #include "vm/StringType.h"
     30 
     31 using namespace js;
     32 using namespace js::temporal;
     33 
     34 enum class TemporalStringFormat {
     35  None,
     36  Date,
     37  Time,
     38  DateTime,
     39  YearMonth,
     40  MonthDay,
     41  ZonedDateTime,
     42  Instant,
     43 };
     44 
     45 enum class Critical : bool { No, Yes };
     46 
     47 class TemporalStringBuilder {
     48  JSStringBuilder sb_;
     49 
     50  TemporalStringFormat kind_ = TemporalStringFormat::None;
     51 
     52 #ifdef DEBUG
     53  bool reserved_ = false;
     54 #endif
     55 
     56  static constexpr size_t reserveAmount(TemporalStringFormat format) {
     57    // Note: This doesn't reserve too much space, because the string builder
     58    // already internally reserves space for 64 characters.
     59 
     60    constexpr size_t datePart = 1 + 6 + 1 + 2 + 1 + 2;        // 13
     61    constexpr size_t timePart = 2 + 1 + 2 + 1 + 2 + 1 + 9;    // 18
     62    constexpr size_t dateTimePart = datePart + 1 + timePart;  // including 'T'
     63    constexpr size_t timeZoneOffsetPart = 1 + 2 + 1 + 2;      // 6
     64 
     65    switch (format) {
     66      case TemporalStringFormat::Date:
     67      case TemporalStringFormat::YearMonth:
     68      case TemporalStringFormat::MonthDay:
     69        return datePart;
     70      case TemporalStringFormat::Time:
     71        return timePart;
     72      case TemporalStringFormat::DateTime:
     73        return dateTimePart;
     74      case TemporalStringFormat::ZonedDateTime:
     75        return dateTimePart + timeZoneOffsetPart;
     76      case TemporalStringFormat::Instant:
     77        return dateTimePart + timeZoneOffsetPart;
     78      case TemporalStringFormat::None:
     79        break;
     80    }
     81    MOZ_CRASH("invalid reserve amount");
     82  }
     83 
     84 public:
     85  TemporalStringBuilder(JSContext* cx, TemporalStringFormat kind)
     86      : sb_(cx), kind_(kind) {
     87    MOZ_ASSERT(kind != TemporalStringFormat::None);
     88  }
     89 
     90  bool reserve() {
     91    MOZ_ASSERT(!reserved_);
     92 
     93    if (!sb_.reserve(reserveAmount(kind_))) {
     94      return false;
     95    }
     96 
     97 #ifdef DEBUG
     98    reserved_ = true;
     99 #endif
    100    return true;
    101  }
    102 
    103  void append(char value) {
    104    MOZ_ASSERT(reserved_);
    105    sb_.infallibleAppend(value);
    106  }
    107 
    108  void appendTwoDigit(int32_t value) {
    109    MOZ_ASSERT(0 <= value && value <= 99);
    110    MOZ_ASSERT(reserved_);
    111 
    112    sb_.infallibleAppend(char('0' + (value / 10)));
    113    sb_.infallibleAppend(char('0' + (value % 10)));
    114  }
    115 
    116  void appendFourDigit(int32_t value) {
    117    MOZ_ASSERT(0 <= value && value <= 9999);
    118    MOZ_ASSERT(reserved_);
    119 
    120    sb_.infallibleAppend(char('0' + (value / 1000)));
    121    sb_.infallibleAppend(char('0' + (value % 1000) / 100));
    122    sb_.infallibleAppend(char('0' + (value % 100) / 10));
    123    sb_.infallibleAppend(char('0' + (value % 10)));
    124  }
    125 
    126  void appendSixDigit(int32_t value) {
    127    MOZ_ASSERT(0 <= value && value <= 999999);
    128    MOZ_ASSERT(reserved_);
    129 
    130    sb_.infallibleAppend(char('0' + (value / 100000)));
    131    sb_.infallibleAppend(char('0' + (value % 100000) / 10000));
    132    sb_.infallibleAppend(char('0' + (value % 10000) / 1000));
    133    sb_.infallibleAppend(char('0' + (value % 1000) / 100));
    134    sb_.infallibleAppend(char('0' + (value % 100) / 10));
    135    sb_.infallibleAppend(char('0' + (value % 10)));
    136  }
    137 
    138  void appendYear(int32_t year) {
    139    if (0 <= year && year <= 9999) {
    140      appendFourDigit(year);
    141    } else {
    142      append(year < 0 ? '-' : '+');
    143      appendSixDigit(std::abs(year));
    144    }
    145  }
    146 
    147  bool appendCalendarAnnnotation(std::string_view id, Critical critical) {
    148    std::string_view start = bool(critical) ? "[!u-ca=" : "[u-ca=";
    149    return sb_.append(start.data(), start.length()) &&
    150           sb_.append(id.data(), id.length()) && sb_.append(']');
    151  }
    152 
    153  bool appendTimeZoneAnnnotation(const JSLinearString* id, Critical critical) {
    154    std::string_view start = bool(critical) ? "[!" : "[";
    155    return sb_.append(start.data(), start.length()) && sb_.append(id) &&
    156           sb_.append(']');
    157  }
    158 
    159  auto* finishString() { return sb_.finishString(); }
    160 };
    161 
    162 /**
    163 * FormatFractionalSeconds ( subSecondNanoseconds, precision )
    164 */
    165 static void FormatFractionalSeconds(TemporalStringBuilder& result,
    166                                    int32_t subSecondNanoseconds,
    167                                    Precision precision) {
    168  MOZ_ASSERT(0 <= subSecondNanoseconds && subSecondNanoseconds < 1'000'000'000);
    169  MOZ_ASSERT(precision != Precision::Minute());
    170 
    171  // Steps 1-2.
    172  if (precision == Precision::Auto()) {
    173    // Step 1.a.
    174    if (subSecondNanoseconds == 0) {
    175      return;
    176    }
    177 
    178    // Step 3. (Reordered)
    179    result.append('.');
    180 
    181    // Steps 1.b-c.
    182    int32_t k = 100'000'000;
    183    do {
    184      result.append(char('0' + (subSecondNanoseconds / k)));
    185      subSecondNanoseconds %= k;
    186      k /= 10;
    187    } while (subSecondNanoseconds);
    188  } else {
    189    // Step 2.a.
    190    uint8_t p = precision.value();
    191    if (p == 0) {
    192      return;
    193    }
    194 
    195    // Step 3. (Reordered)
    196    result.append('.');
    197 
    198    // Steps 2.b-c.
    199    int32_t k = 100'000'000;
    200    for (uint8_t i = 0; i < p; i++) {
    201      result.append(char('0' + (subSecondNanoseconds / k)));
    202      subSecondNanoseconds %= k;
    203      k /= 10;
    204    }
    205  }
    206 }
    207 
    208 /**
    209 * FormatTimeString ( hour, minute, second, subSecondNanoseconds, precision )
    210 */
    211 static void FormatTimeString(TemporalStringBuilder& result, const Time& time,
    212                             Precision precision) {
    213  // Step 1.
    214  result.appendTwoDigit(time.hour);
    215 
    216  // Step 2.
    217  result.append(':');
    218  result.appendTwoDigit(time.minute);
    219 
    220  // Steps 4-7.
    221  if (precision != Precision::Minute()) {
    222    result.append(':');
    223    result.appendTwoDigit(time.second);
    224 
    225    int32_t subSecondNanoseconds = time.millisecond * 1'000'000 +
    226                                   time.microsecond * 1'000 + time.nanosecond;
    227    FormatFractionalSeconds(result, subSecondNanoseconds, precision);
    228  }
    229 }
    230 
    231 static void FormatDateString(TemporalStringBuilder& result,
    232                             const ISODate& date) {
    233  result.appendYear(date.year);
    234  result.append('-');
    235  result.appendTwoDigit(date.month);
    236  result.append('-');
    237  result.appendTwoDigit(date.day);
    238 }
    239 
    240 static void FormatDateTimeString(TemporalStringBuilder& result,
    241                                 const ISODateTime& dateTime,
    242                                 Precision precision) {
    243  FormatDateString(result, dateTime.date);
    244  result.append('T');
    245  FormatTimeString(result, dateTime.time, precision);
    246 }
    247 
    248 /**
    249 * FormatOffsetTimeZoneIdentifier ( offsetMinutes [ , style ] )
    250 */
    251 static void FormatOffsetTimeZoneIdentifier(TemporalStringBuilder& result,
    252                                           int32_t offsetMinutes) {
    253  MOZ_ASSERT(std::abs(offsetMinutes) < UnitsPerDay(TemporalUnit::Minute),
    254             "time zone offset mustn't exceed 24-hours");
    255 
    256  // Step 1.
    257  char sign = offsetMinutes >= 0 ? '+' : '-';
    258 
    259  // Step 2.
    260  int32_t absoluteMinutes = std::abs(offsetMinutes);
    261 
    262  // Step 3.
    263  int32_t hours = absoluteMinutes / 60;
    264 
    265  // Step 4.
    266  int32_t minutes = absoluteMinutes % 60;
    267 
    268  // Steps 5-6. (Inlined FormatTimeString)
    269  result.append(sign);
    270  result.appendTwoDigit(hours);
    271  result.append(':');
    272  result.appendTwoDigit(minutes);
    273 }
    274 
    275 // Returns |RoundNumberToIncrement(offsetNanoseconds, 60 × 10^9, "halfExpand")|
    276 // divided by |60 × 10^9|.
    277 static int32_t RoundNanosecondsToMinutes(int64_t offsetNanoseconds) {
    278  MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
    279 
    280  constexpr int64_t increment = ToNanoseconds(TemporalUnit::Minute);
    281 
    282  int64_t quotient = offsetNanoseconds / increment;
    283  int64_t remainder = offsetNanoseconds % increment;
    284  if (std::abs(remainder * 2) >= increment) {
    285    quotient += (offsetNanoseconds > 0 ? 1 : -1);
    286  }
    287  return int32_t(quotient);
    288 }
    289 
    290 /**
    291 * FormatDateTimeUTCOffsetRounded ( offsetNanoseconds )
    292 */
    293 static void FormatDateTimeUTCOffsetRounded(TemporalStringBuilder& result,
    294                                           int64_t offsetNanoseconds) {
    295  MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
    296 
    297  // Steps 1-3.
    298  int32_t offsetMinutes = RoundNanosecondsToMinutes(offsetNanoseconds);
    299 
    300  // Step 4.
    301  FormatOffsetTimeZoneIdentifier(result, offsetMinutes);
    302 }
    303 
    304 /**
    305 * FormatCalendarAnnotation ( id, showCalendar )
    306 */
    307 static bool FormatCalendarAnnotation(TemporalStringBuilder& result,
    308                                     const CalendarValue& calendar,
    309                                     ShowCalendar showCalendar) {
    310  switch (showCalendar) {
    311    case ShowCalendar::Never:
    312      return true;
    313 
    314    case ShowCalendar::Auto: {
    315      if (calendar.identifier() == CalendarId::ISO8601) {
    316        return true;
    317      }
    318      [[fallthrough]];
    319    }
    320 
    321    case ShowCalendar::Always: {
    322      auto id = CalendarIdentifier(calendar);
    323      return result.appendCalendarAnnnotation(id, Critical::No);
    324    }
    325 
    326    case ShowCalendar::Critical: {
    327      auto id = CalendarIdentifier(calendar);
    328      return result.appendCalendarAnnnotation(id, Critical::Yes);
    329    }
    330  }
    331  MOZ_CRASH("bad calendar option");
    332 }
    333 
    334 static bool FormatTimeZoneAnnotation(TemporalStringBuilder& result,
    335                                     const TimeZoneValue& timeZone,
    336                                     ShowTimeZoneName showTimeZone) {
    337  switch (showTimeZone) {
    338    case ShowTimeZoneName::Never:
    339      return true;
    340 
    341    case ShowTimeZoneName::Auto:
    342      return result.appendTimeZoneAnnnotation(timeZone.identifier(),
    343                                              Critical::No);
    344 
    345    case ShowTimeZoneName::Critical:
    346      return result.appendTimeZoneAnnnotation(timeZone.identifier(),
    347                                              Critical::Yes);
    348  }
    349  MOZ_CRASH("bad time zone option");
    350 }
    351 
    352 /**
    353 * TemporalInstantToString ( instant, timeZone, precision )
    354 */
    355 JSString* js::temporal::TemporalInstantToString(JSContext* cx,
    356                                                const EpochNanoseconds& epochNs,
    357                                                Handle<TimeZoneValue> timeZone,
    358                                                Precision precision) {
    359  TemporalStringBuilder result(cx, TemporalStringFormat::Instant);
    360  if (!result.reserve()) {
    361    return nullptr;
    362  }
    363 
    364  // Steps 1-2.
    365  int64_t offsetNanoseconds = 0;
    366  if (timeZone) {
    367    if (!GetOffsetNanosecondsFor(cx, timeZone, epochNs, &offsetNanoseconds)) {
    368      return nullptr;
    369    }
    370    MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
    371  }
    372 
    373  // Step 3. (Not applicable in our implementation.)
    374 
    375  // Step 4.
    376  auto dateTime = GetISODateTimeFor(epochNs, offsetNanoseconds);
    377 
    378  // Step 5. (Inlined ISODateTimeToString)
    379  FormatDateTimeString(result, dateTime, precision);
    380 
    381  // Steps 6-7.
    382  Rooted<JSString*> timeZoneString(cx);
    383  if (!timeZone) {
    384    result.append('Z');
    385  } else {
    386    FormatDateTimeUTCOffsetRounded(result, offsetNanoseconds);
    387  }
    388 
    389  // Step 8.
    390  return result.finishString();
    391 }
    392 
    393 /**
    394 * TemporalDateToString ( temporalDate, showCalendar )
    395 */
    396 JSString* js::temporal::TemporalDateToString(
    397    JSContext* cx, Handle<PlainDateObject*> temporalDate,
    398    ShowCalendar showCalendar) {
    399  auto date = temporalDate->date();
    400 
    401  TemporalStringBuilder result(cx, TemporalStringFormat::Date);
    402  if (!result.reserve()) {
    403    return nullptr;
    404  }
    405 
    406  // Steps 1-3.
    407  FormatDateString(result, date);
    408 
    409  // Step 4.
    410  if (!FormatCalendarAnnotation(result, temporalDate->calendar(),
    411                                showCalendar)) {
    412    return nullptr;
    413  }
    414 
    415  // Step 5.
    416  return result.finishString();
    417 }
    418 
    419 /**
    420 * ISODateTimeToString ( isoDateTime, calendar, precision, showCalendar )
    421 */
    422 JSString* js::temporal::ISODateTimeToString(JSContext* cx,
    423                                            const ISODateTime& isoDateTime,
    424                                            Handle<CalendarValue> calendar,
    425                                            Precision precision,
    426                                            ShowCalendar showCalendar) {
    427  MOZ_ASSERT(IsValidISODateTime(isoDateTime));
    428 
    429  TemporalStringBuilder result(cx, TemporalStringFormat::DateTime);
    430  if (!result.reserve()) {
    431    return nullptr;
    432  }
    433 
    434  // Steps 1-5.
    435  FormatDateTimeString(result, isoDateTime, precision);
    436 
    437  // Step 6.
    438  if (!FormatCalendarAnnotation(result, calendar, showCalendar)) {
    439    return nullptr;
    440  }
    441 
    442  // Step 7.
    443  return result.finishString();
    444 }
    445 
    446 /**
    447 * TimeRecordToString ( time, precision )
    448 */
    449 JSString* js::temporal::TimeRecordToString(JSContext* cx, const Time& time,
    450                                           Precision precision) {
    451  TemporalStringBuilder result(cx, TemporalStringFormat::Time);
    452  if (!result.reserve()) {
    453    return nullptr;
    454  }
    455 
    456  // Steps 1-2.
    457  FormatTimeString(result, time, precision);
    458 
    459  return result.finishString();
    460 }
    461 
    462 /**
    463 * TemporalMonthDayToString ( monthDay, showCalendar )
    464 */
    465 JSString* js::temporal::TemporalMonthDayToString(
    466    JSContext* cx, Handle<PlainMonthDayObject*> monthDay,
    467    ShowCalendar showCalendar) {
    468  TemporalStringBuilder result(cx, TemporalStringFormat::MonthDay);
    469  if (!result.reserve()) {
    470    return nullptr;
    471  }
    472 
    473  // Steps 1-4.
    474  auto date = monthDay->date();
    475  if (showCalendar == ShowCalendar::Always ||
    476      showCalendar == ShowCalendar::Critical ||
    477      monthDay->calendar().identifier() != CalendarId::ISO8601) {
    478    FormatDateString(result, date);
    479  } else {
    480    result.appendTwoDigit(date.month);
    481    result.append('-');
    482    result.appendTwoDigit(date.day);
    483  }
    484 
    485  // Steps 5-6.
    486  if (!FormatCalendarAnnotation(result, monthDay->calendar(), showCalendar)) {
    487    return nullptr;
    488  }
    489 
    490  // Step 7.
    491  return result.finishString();
    492 }
    493 
    494 /**
    495 * TemporalYearMonthToString ( yearMonth, showCalendar )
    496 */
    497 JSString* js::temporal::TemporalYearMonthToString(
    498    JSContext* cx, Handle<PlainYearMonthObject*> yearMonth,
    499    ShowCalendar showCalendar) {
    500  TemporalStringBuilder result(cx, TemporalStringFormat::YearMonth);
    501  if (!result.reserve()) {
    502    return nullptr;
    503  }
    504 
    505  // Steps 1-4.
    506  auto date = yearMonth->date();
    507  if (showCalendar == ShowCalendar::Always ||
    508      showCalendar == ShowCalendar::Critical ||
    509      yearMonth->calendar().identifier() != CalendarId::ISO8601) {
    510    FormatDateString(result, date);
    511  } else {
    512    result.appendYear(date.year);
    513    result.append('-');
    514    result.appendTwoDigit(date.month);
    515  }
    516 
    517  // Steps 5-6.
    518  if (!FormatCalendarAnnotation(result, yearMonth->calendar(), showCalendar)) {
    519    return nullptr;
    520  }
    521 
    522  // Step 7.
    523  return result.finishString();
    524 }
    525 
    526 /**
    527 * TemporalZonedDateTimeToString ( zonedDateTime, precision, showCalendar,
    528 * showTimeZone, showOffset [ , increment, unit, roundingMode ] )
    529 */
    530 JSString* js::temporal::TemporalZonedDateTimeToString(
    531    JSContext* cx, Handle<ZonedDateTime> zonedDateTime, Precision precision,
    532    ShowCalendar showCalendar, ShowTimeZoneName showTimeZone,
    533    ShowOffset showOffset, Increment increment, TemporalUnit unit,
    534    TemporalRoundingMode roundingMode) {
    535  TemporalStringBuilder result(cx, TemporalStringFormat::ZonedDateTime);
    536  if (!result.reserve()) {
    537    return nullptr;
    538  }
    539 
    540  // Steps 1-3. (Not applicable in our implementation.)
    541 
    542  // Steps 4-5.
    543  auto epochNs = RoundTemporalInstant(zonedDateTime.epochNanoseconds(),
    544                                      increment, unit, roundingMode);
    545 
    546  // Step 6.
    547  auto timeZone = zonedDateTime.timeZone();
    548 
    549  // Step 7.
    550  int64_t offsetNanoseconds;
    551  if (!GetOffsetNanosecondsFor(cx, timeZone, epochNs, &offsetNanoseconds)) {
    552    return nullptr;
    553  }
    554  MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
    555 
    556  // Step 8.
    557  auto isoDateTime = GetISODateTimeFor(epochNs, offsetNanoseconds);
    558 
    559  // Step 9. (Inlined ISODateTimeToString)
    560  FormatDateTimeString(result, isoDateTime, precision);
    561 
    562  // Steps 10-11.
    563  if (showOffset != ShowOffset::Never) {
    564    FormatDateTimeUTCOffsetRounded(result, offsetNanoseconds);
    565  }
    566 
    567  // Steps 12-13.
    568  if (!FormatTimeZoneAnnotation(result, timeZone, showTimeZone)) {
    569    return nullptr;
    570  }
    571 
    572  // Step 14.
    573  if (!FormatCalendarAnnotation(result, zonedDateTime.calendar(),
    574                                showCalendar)) {
    575    return nullptr;
    576  }
    577 
    578  // Step 15.
    579  return result.finishString();
    580 }