tor-browser

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

MonthCode.h (13262B)


      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 #ifndef builtin_temporal_MonthCode_h
      8 #define builtin_temporal_MonthCode_h
      9 
     10 #include "mozilla/Assertions.h"
     11 #include "mozilla/EnumSet.h"
     12 
     13 #include <initializer_list>
     14 #include <stddef.h>
     15 #include <stdint.h>
     16 #include <string_view>
     17 #include <utility>
     18 
     19 #include "jstypes.h"
     20 
     21 #include "builtin/temporal/Calendar.h"
     22 
     23 namespace js::temporal {
     24 
     25 class MonthCode final {
     26 public:
     27  enum class Code {
     28    Invalid = 0,
     29 
     30    // Months 01 - M12.
     31    M01 = 1,
     32    M02,
     33    M03,
     34    M04,
     35    M05,
     36    M06,
     37    M07,
     38    M08,
     39    M09,
     40    M10,
     41    M11,
     42    M12,
     43 
     44    // Epagomenal month M13.
     45    M13,
     46 
     47    // Leap months M01 - M12.
     48    M01L,
     49    M02L,
     50    M03L,
     51    M04L,
     52    M05L,
     53    M06L,
     54    M07L,
     55    M08L,
     56    M09L,
     57    M10L,
     58    M11L,
     59    M12L,
     60  };
     61 
     62 private:
     63  static constexpr int32_t toLeapMonth =
     64      static_cast<int32_t>(Code::M01L) - static_cast<int32_t>(Code::M01);
     65 
     66  Code code_ = Code::Invalid;
     67 
     68 public:
     69  constexpr MonthCode() = default;
     70 
     71  constexpr explicit MonthCode(Code code) : code_(code) {}
     72 
     73  constexpr explicit MonthCode(int32_t month, bool isLeapMonth = false) {
     74    MOZ_ASSERT(1 <= month && month <= 13);
     75    MOZ_ASSERT_IF(isLeapMonth, 1 <= month && month <= 12);
     76 
     77    code_ = static_cast<Code>(month + (isLeapMonth ? toLeapMonth : 0));
     78  }
     79 
     80  constexpr auto code() const { return code_; }
     81 
     82  constexpr int32_t ordinal() const {
     83    int32_t ordinal = static_cast<int32_t>(code_);
     84    if (isLeapMonth()) {
     85      ordinal -= toLeapMonth;
     86    }
     87    return ordinal;
     88  }
     89 
     90  constexpr bool isLeapMonth() const { return code_ >= Code::M01L; }
     91 
     92  constexpr bool operator==(const MonthCode& other) const {
     93    return other.code_ == code_;
     94  }
     95 
     96  constexpr bool operator!=(const MonthCode& other) const {
     97    return !(*this == other);
     98  }
     99 
    100  constexpr bool operator<(const MonthCode& other) const {
    101    if (ordinal() != other.ordinal()) {
    102      return ordinal() < other.ordinal();
    103    }
    104    return code_ < other.code_;
    105  }
    106 
    107  constexpr bool operator>(const MonthCode& other) const {
    108    return other < *this;
    109  }
    110 
    111  constexpr bool operator<=(const MonthCode& other) const {
    112    return !(other < *this);
    113  }
    114 
    115  constexpr bool operator>=(const MonthCode& other) const {
    116    return !(*this < other);
    117  }
    118 
    119  constexpr explicit operator std::string_view() const {
    120    constexpr const char* name =
    121        "M01L"
    122        "M02L"
    123        "M03L"
    124        "M04L"
    125        "M05L"
    126        "M06L"
    127        "M07L"
    128        "M08L"
    129        "M09L"
    130        "M10L"
    131        "M11L"
    132        "M12L"
    133        "M13";
    134    size_t index = (ordinal() - 1) * 4;
    135    size_t length = 3 + isLeapMonth();
    136    return {name + index, length};
    137  }
    138 
    139  /**
    140   * Returns the maximum non-leap month. This is the epagomenal month "M13".
    141   */
    142  constexpr static auto maxNonLeapMonth() { return MonthCode{Code::M13}; }
    143 
    144  /**
    145   * Returns the maximum leap month.
    146   */
    147  constexpr static auto maxLeapMonth() { return MonthCode{Code::M12L}; }
    148 };
    149 
    150 class MonthCodes final {
    151  // Common month codes supported by all calendars.
    152  //
    153  // See IsValidMonthCodeForCalendar, step 1.
    154  mozilla::EnumSet<MonthCode::Code> monthCodes_{
    155      MonthCode::Code::M01, MonthCode::Code::M02, MonthCode::Code::M03,
    156      MonthCode::Code::M04, MonthCode::Code::M05, MonthCode::Code::M06,
    157      MonthCode::Code::M07, MonthCode::Code::M08, MonthCode::Code::M09,
    158      MonthCode::Code::M10, MonthCode::Code::M11, MonthCode::Code::M12,
    159  };
    160 
    161 public:
    162  constexpr MOZ_IMPLICIT MonthCodes(std::initializer_list<MonthCode> list) {
    163    for (auto value : list) {
    164      monthCodes_ += value.code();
    165    }
    166  }
    167 
    168  constexpr bool contains(MonthCode monthCode) const {
    169    return monthCodes_.contains(monthCode.code());
    170  }
    171 
    172  constexpr bool contains(const MonthCodes& monthCodes) const {
    173    return monthCodes_.contains(monthCodes.monthCodes_);
    174  }
    175 };
    176 
    177 // static variables in constexpr functions requires C++23 support, so we can't
    178 // declare the month codes directly in CalendarMonthCodes.
    179 //
    180 // https://tc39.es/proposal-intl-era-monthcode/#table-additional-month-codes
    181 //
    182 // https://docs.rs/icu/latest/icu/calendar/cal/struct.Buddhist.html#month-codes
    183 // https://docs.rs/icu/latest/icu/calendar/cal/struct.Indian.html#month-codes
    184 // https://docs.rs/icu/latest/icu/calendar/cal/struct.Hijri.html#months-and-days
    185 // https://docs.rs/icu/latest/icu/calendar/cal/struct.Japanese.html#month-codes
    186 // https://docs.rs/icu/latest/icu/calendar/cal/struct.Persian.html#month-codes
    187 // https://docs.rs/icu/latest/icu/calendar/cal/struct.Roc.html#month-codes
    188 //
    189 // https://docs.rs/icu/latest/icu/calendar/cal/east_asian_traditional/struct.EastAsianTraditional.html#months-and-days
    190 //
    191 // https://docs.rs/icu/latest/icu/calendar/cal/struct.Coptic.html#month-codes
    192 // https://docs.rs/icu/latest/icu/calendar/cal/struct.Ethiopian.html#month-codes
    193 //
    194 // https://docs.rs/icu/latest/icu/calendar/cal/struct.Hebrew.html#month-codes
    195 namespace monthcodes {
    196 // The ISO8601 calendar doesn't have any additional month codes.
    197 inline constexpr MonthCodes ISO8601 = {};
    198 
    199 // The Chinese/Dangi calendars can have a leap month inserted after every month.
    200 inline constexpr MonthCodes ChineseOrDangi = {
    201    // Leap months.
    202    MonthCode{1, /* isLeapMonth = */ true},
    203    MonthCode{2, /* isLeapMonth = */ true},
    204    MonthCode{3, /* isLeapMonth = */ true},
    205    MonthCode{4, /* isLeapMonth = */ true},
    206    MonthCode{5, /* isLeapMonth = */ true},
    207    MonthCode{6, /* isLeapMonth = */ true},
    208    MonthCode{7, /* isLeapMonth = */ true},
    209    MonthCode{8, /* isLeapMonth = */ true},
    210    MonthCode{9, /* isLeapMonth = */ true},
    211    MonthCode{10, /* isLeapMonth = */ true},
    212    MonthCode{11, /* isLeapMonth = */ true},
    213    MonthCode{12, /* isLeapMonth = */ true},
    214 };
    215 
    216 // The Coptic/Ethiopian calendars has a thirteenth month.
    217 inline constexpr MonthCodes CopticOrEthiopian = {
    218    // Short epagomenal month.
    219    MonthCode{13},
    220 };
    221 
    222 // The Hebrew calendar has a single leap month.
    223 inline constexpr MonthCodes Hebrew = {
    224    // Leap month Adar I.
    225    MonthCode{5, /* isLeapMonth = */ true},
    226 };
    227 }  // namespace monthcodes
    228 
    229 constexpr auto& CalendarMonthCodes(CalendarId id) {
    230  switch (id) {
    231    case CalendarId::ISO8601:
    232    case CalendarId::Buddhist:
    233    case CalendarId::Gregorian:
    234    case CalendarId::Indian:
    235    case CalendarId::IslamicCivil:
    236    case CalendarId::IslamicTabular:
    237    case CalendarId::IslamicUmmAlQura:
    238    case CalendarId::Persian:
    239    case CalendarId::Japanese:
    240    case CalendarId::ROC:
    241      return monthcodes::ISO8601;
    242 
    243    case CalendarId::Chinese:
    244    case CalendarId::Dangi:
    245      return monthcodes::ChineseOrDangi;
    246 
    247    case CalendarId::Coptic:
    248    case CalendarId::Ethiopian:
    249    case CalendarId::EthiopianAmeteAlem:
    250      return monthcodes::CopticOrEthiopian;
    251 
    252    case CalendarId::Hebrew:
    253      return monthcodes::Hebrew;
    254  }
    255  MOZ_CRASH("invalid calendar id");
    256 }
    257 
    258 /**
    259 * IsValidMonthCodeForCalendar ( calendar, monthCode )
    260 */
    261 constexpr bool IsValidMonthCodeForCalendar(CalendarId id, MonthCode monthCode) {
    262  return CalendarMonthCodes(id).contains(monthCode);
    263 }
    264 
    265 constexpr bool CalendarHasLeapMonths(CalendarId id) {
    266  switch (id) {
    267    case CalendarId::ISO8601:
    268    case CalendarId::Buddhist:
    269    case CalendarId::Coptic:
    270    case CalendarId::Ethiopian:
    271    case CalendarId::EthiopianAmeteAlem:
    272    case CalendarId::Gregorian:
    273    case CalendarId::Indian:
    274    case CalendarId::IslamicCivil:
    275    case CalendarId::IslamicTabular:
    276    case CalendarId::IslamicUmmAlQura:
    277    case CalendarId::Japanese:
    278    case CalendarId::Persian:
    279    case CalendarId::ROC:
    280      return false;
    281 
    282    case CalendarId::Chinese:
    283    case CalendarId::Dangi:
    284    case CalendarId::Hebrew:
    285      return true;
    286  }
    287  MOZ_CRASH("invalid calendar id");
    288 }
    289 
    290 constexpr bool CalendarHasEpagomenalMonths(CalendarId id) {
    291  switch (id) {
    292    case CalendarId::ISO8601:
    293    case CalendarId::Buddhist:
    294    case CalendarId::Chinese:
    295    case CalendarId::Dangi:
    296    case CalendarId::Gregorian:
    297    case CalendarId::Hebrew:
    298    case CalendarId::Indian:
    299    case CalendarId::IslamicCivil:
    300    case CalendarId::IslamicTabular:
    301    case CalendarId::IslamicUmmAlQura:
    302    case CalendarId::Japanese:
    303    case CalendarId::Persian:
    304    case CalendarId::ROC:
    305      return false;
    306 
    307    case CalendarId::Coptic:
    308    case CalendarId::Ethiopian:
    309    case CalendarId::EthiopianAmeteAlem:
    310      return true;
    311  }
    312  MOZ_CRASH("invalid calendar id");
    313 }
    314 
    315 constexpr int32_t CalendarMonthsPerYear(CalendarId id) {
    316  if (CalendarHasLeapMonths(id) || CalendarHasEpagomenalMonths(id)) {
    317    return 13;
    318  }
    319  return 12;
    320 }
    321 
    322 constexpr std::pair<int32_t, int32_t> CalendarDaysInMonth(CalendarId id) {
    323  switch (id) {
    324    // ISO8601 calendar.
    325    // M02: 28-29 days
    326    // M04, M06, M09, M11: 30 days
    327    // M01, M03, M05, M07, M08, M10, M12: 31 days
    328    case CalendarId::ISO8601:
    329    case CalendarId::Buddhist:
    330    case CalendarId::Gregorian:
    331    case CalendarId::Japanese:
    332    case CalendarId::ROC:
    333      return {28, 31};
    334 
    335    // Chinese/Dangi calendars have 29-30 days per month.
    336    //
    337    // Hebrew:
    338    // M01, M05, M07, M09, M11: 30 days.
    339    // M02, M03: 29-30 days.
    340    // M04, M06, M08, M10, M12: 29 days.
    341    // M05L: 30 days
    342    //
    343    // Islamic calendars have 29-30 days.
    344    //
    345    // IslamicCivil, IslamicTabular:
    346    // M01, M03, M05, M07, M09, M11: 30 days
    347    // M02, M04, M06, M08, M10: 29 days
    348    // M12: 29-30 days.
    349    case CalendarId::Chinese:
    350    case CalendarId::Dangi:
    351    case CalendarId::Hebrew:
    352    case CalendarId::IslamicCivil:
    353    case CalendarId::IslamicTabular:
    354    case CalendarId::IslamicUmmAlQura:
    355      return {29, 30};
    356 
    357    // Coptic, Ethiopian, EthiopianAmeteAlem:
    358    // M01..M12: 30 days.
    359    // M13: 5-6 days.
    360    case CalendarId::Coptic:
    361    case CalendarId::Ethiopian:
    362    case CalendarId::EthiopianAmeteAlem:
    363      return {5, 30};
    364 
    365    // Indian:
    366    // M1: 30-31 days.
    367    // M02..M06: 31 days
    368    // M07..M12: 30 days
    369    case CalendarId::Indian:
    370      return {30, 31};
    371 
    372    // Persian:
    373    // M01..M06: 31 days
    374    // M07..M11: 30 days
    375    // M12: 29-30 days
    376    case CalendarId::Persian:
    377      return {29, 31};
    378  }
    379  MOZ_CRASH("invalid calendar id");
    380 }
    381 
    382 // ISO8601 calendar.
    383 // M02: 28-29 days
    384 // M04, M06, M09, M11: 30 days
    385 // M01, M03, M05, M07, M08, M10, M12: 31 days
    386 constexpr std::pair<int32_t, int32_t> ISODaysInMonth(MonthCode monthCode) {
    387  int32_t ordinal = monthCode.ordinal();
    388  if (ordinal == 2) {
    389    return {28, 29};
    390  }
    391  if (ordinal == 4 || ordinal == 6 || ordinal == 9 || ordinal == 11) {
    392    return {30, 30};
    393  }
    394  return {31, 31};
    395 }
    396 
    397 constexpr std::pair<int32_t, int32_t> CalendarDaysInMonth(CalendarId id,
    398                                                          MonthCode monthCode) {
    399  switch (id) {
    400    case CalendarId::ISO8601:
    401    case CalendarId::Buddhist:
    402    case CalendarId::Gregorian:
    403    case CalendarId::Japanese:
    404    case CalendarId::ROC:
    405      return ISODaysInMonth(monthCode);
    406 
    407    // Chinese/Dangi calendars have 29-30 days per month.
    408    case CalendarId::Chinese:
    409    case CalendarId::Dangi:
    410      return {29, 30};
    411 
    412    // Coptic, Ethiopian, EthiopianAmeteAlem:
    413    // M01..M12: 30 days.
    414    // M13: 5-6 days.
    415    case CalendarId::Coptic:
    416    case CalendarId::Ethiopian:
    417    case CalendarId::EthiopianAmeteAlem: {
    418      if (monthCode.ordinal() <= 12) {
    419        return {30, 30};
    420      }
    421      return {5, 6};
    422    }
    423 
    424    // Hebrew:
    425    // M01, M05, M07, M09, M11: 30 days.
    426    // M02, M03: 29-30 days.
    427    // M04, M06, M08, M10, M12: 29 days.
    428    // M05L: 30 days
    429    case CalendarId::Hebrew: {
    430      int32_t ordinal = monthCode.ordinal();
    431      if (ordinal == 2 || ordinal == 3) {
    432        return {29, 30};
    433      }
    434      if ((ordinal & 1) == 1 || monthCode.isLeapMonth()) {
    435        return {30, 30};
    436      }
    437      return {29, 29};
    438    }
    439 
    440    // Indian:
    441    // M1: 30-31 days.
    442    // M02..M06: 31 days
    443    // M07..M12: 30 days
    444    case CalendarId::Indian: {
    445      int32_t ordinal = monthCode.ordinal();
    446      if (ordinal == 1) {
    447        return {30, 31};
    448      }
    449      if (ordinal <= 6) {
    450        return {31, 31};
    451      }
    452      return {30, 30};
    453    }
    454 
    455    // IslamicUmmAlQura calendar has 29-30 days per month.
    456    case CalendarId::IslamicUmmAlQura:
    457      return {29, 30};
    458 
    459    // IslamicCivil, IslamicTabular:
    460    // M01, M03, M05, M07, M09, M11: 30 days
    461    // M02, M04, M06, M08, M10: 29 days
    462    // M12: 29-30 days.
    463    case CalendarId::IslamicCivil:
    464    case CalendarId::IslamicTabular: {
    465      int32_t ordinal = monthCode.ordinal();
    466      if ((ordinal & 1) == 1) {
    467        return {30, 30};
    468      }
    469      if (ordinal < 12) {
    470        return {29, 29};
    471      }
    472      return {29, 30};
    473    }
    474 
    475    // Persian:
    476    // M01..M06: 31 days
    477    // M07..M11: 30 days
    478    // M12: 29-30 days
    479    case CalendarId::Persian: {
    480      int32_t ordinal = monthCode.ordinal();
    481      if (ordinal <= 6) {
    482        return {31, 31};
    483      }
    484      if (ordinal <= 11) {
    485        return {30, 30};
    486      }
    487      return {29, 30};
    488    }
    489  }
    490  MOZ_CRASH("invalid calendar id");
    491 }
    492 
    493 }  // namespace js::temporal
    494 
    495 #endif /* builtin_temporal_MonthCode_h */