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 */