Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* 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 "mozilla/Assertions.h"
#include "mozilla/EnumSet.h"
#include <initializer_list>
#include <stddef.h>
#include <stdint.h>
#include <string_view>
#include <utility>
#include "jstypes.h"
#include "builtin/temporal/Calendar.h"
#include "builtin/temporal/Crash.h"
namespace js::temporal {
class MonthCode final {
public:
enum class Code {
Invalid = 0,
// Months 01 - M12.
M01 = 1,
M02,
M03,
M04,
M05,
M06,
M07,
M08,
M09,
M10,
M11,
M12,
// Epagomenal month M13.
M13,
// Leap months M01 - M12.
M01L,
M02L,
M03L,
M04L,
M05L,
M06L,
M07L,
M08L,
M09L,
M10L,
M11L,
M12L,
};
private:
static constexpr int32_t toLeapMonth =
static_cast<int32_t>(Code::M01L) - static_cast<int32_t>(Code::M01);
Code code_ = Code::Invalid;
public:
constexpr MonthCode() = default;
constexpr explicit MonthCode(Code code) : code_(code) {}
constexpr explicit MonthCode(int32_t month, bool isLeapMonth = false) {
MOZ_ASSERT(1 <= month && month <= 13);
MOZ_ASSERT_IF(isLeapMonth, 1 <= month && month <= 12);
code_ = static_cast<Code>(month + (isLeapMonth ? toLeapMonth : 0));
}
constexpr bool operator==(const MonthCode& other) const {
return other.code_ == code_;
}
constexpr bool operator!=(const MonthCode& other) const {
return !(*this == other);
}
constexpr auto code() const { return code_; }
constexpr int32_t ordinal() const {
int32_t ordinal = static_cast<int32_t>(code_);
if (isLeapMonth()) {
ordinal -= toLeapMonth;
}
return ordinal;
}
constexpr bool isLeapMonth() const { return code_ >= Code::M01L; }
constexpr explicit operator std::string_view() const {
constexpr const char* name =
"M01L"
"M02L"
"M03L"
"M04L"
"M05L"
"M06L"
"M07L"
"M08L"
"M09L"
"M10L"
"M11L"
"M12L"
"M13";
size_t index = (ordinal() - 1) * 4;
size_t length = 3 + isLeapMonth();
return {name + index, length};
}
/**
* Returns the maximum non-leap month. This is the epagomenal month "M13".
*/
constexpr static auto maxNonLeapMonth() { return MonthCode{Code::M13}; }
/**
* Returns the maximum leap month.
*/
constexpr static auto maxLeapMonth() { return MonthCode{Code::M12L}; }
};
class MonthCodes final {
mozilla::EnumSet<MonthCode::Code> monthCodes_{};
public:
constexpr explicit MonthCodes(std::initializer_list<MonthCode> list) {
for (auto value : {
MonthCode::Code::M01,
MonthCode::Code::M02,
MonthCode::Code::M03,
MonthCode::Code::M04,
MonthCode::Code::M05,
MonthCode::Code::M06,
MonthCode::Code::M07,
MonthCode::Code::M08,
MonthCode::Code::M09,
MonthCode::Code::M10,
MonthCode::Code::M11,
MonthCode::Code::M12,
}) {
monthCodes_ += value;
}
for (auto value : list) {
monthCodes_ += value.code();
}
}
bool contains(MonthCode monthCode) const {
return monthCodes_.contains(monthCode.code());
}
bool contains(const MonthCodes& monthCodes) const {
return monthCodes_.contains(monthCodes.monthCodes_);
}
};
// static variables in constexpr functions requires C++23 support, so we can't
// declare the month codes directly in CalendarMonthCodes.
//
//
//
//
//
namespace monthcodes {
inline constexpr auto ISO8601 = MonthCodes{};
inline constexpr auto ChineseOrDangi = MonthCodes{
// Leap months.
MonthCode{1, /* isLeapMonth = */ true},
MonthCode{2, /* isLeapMonth = */ true},
MonthCode{3, /* isLeapMonth = */ true},
MonthCode{4, /* isLeapMonth = */ true},
MonthCode{5, /* isLeapMonth = */ true},
MonthCode{6, /* isLeapMonth = */ true},
MonthCode{7, /* isLeapMonth = */ true},
MonthCode{8, /* isLeapMonth = */ true},
MonthCode{9, /* isLeapMonth = */ true},
MonthCode{10, /* isLeapMonth = */ true},
MonthCode{11, /* isLeapMonth = */ true},
MonthCode{12, /* isLeapMonth = */ true},
};
inline constexpr auto CopticOrEthiopian = MonthCodes{
// Short epagomenal month.
MonthCode{13},
};
inline constexpr auto Hebrew = MonthCodes{
// Leap month Adar I.
MonthCode{5, /* isLeapMonth = */ true},
};
} // namespace monthcodes
constexpr auto& CalendarMonthCodes(CalendarId id) {
switch (id) {
case CalendarId::ISO8601:
case CalendarId::Buddhist:
case CalendarId::Gregorian:
case CalendarId::Indian:
case CalendarId::Islamic:
case CalendarId::IslamicCivil:
case CalendarId::IslamicRGSA:
case CalendarId::IslamicTabular:
case CalendarId::IslamicUmmAlQura:
case CalendarId::Persian:
case CalendarId::Japanese:
case CalendarId::ROC:
return monthcodes::ISO8601;
case CalendarId::Chinese:
case CalendarId::Dangi:
return monthcodes::ChineseOrDangi;
case CalendarId::Coptic:
case CalendarId::Ethiopian:
case CalendarId::EthiopianAmeteAlem:
return monthcodes::CopticOrEthiopian;
case CalendarId::Hebrew:
return monthcodes::Hebrew;
}
JS_CONSTEXPR_CRASH("invalid calendar id");
}
constexpr bool CalendarHasLeapMonths(CalendarId id) {
switch (id) {
case CalendarId::ISO8601:
case CalendarId::Buddhist:
case CalendarId::Coptic:
case CalendarId::Ethiopian:
case CalendarId::EthiopianAmeteAlem:
case CalendarId::Gregorian:
case CalendarId::Indian:
case CalendarId::Islamic:
case CalendarId::IslamicCivil:
case CalendarId::IslamicRGSA:
case CalendarId::IslamicTabular:
case CalendarId::IslamicUmmAlQura:
case CalendarId::Japanese:
case CalendarId::Persian:
case CalendarId::ROC:
return false;
case CalendarId::Chinese:
case CalendarId::Dangi:
case CalendarId::Hebrew:
return true;
}
JS_CONSTEXPR_CRASH("invalid calendar id");
}
constexpr bool CalendarHasEpagomenalMonths(CalendarId id) {
switch (id) {
case CalendarId::ISO8601:
case CalendarId::Buddhist:
case CalendarId::Chinese:
case CalendarId::Dangi:
case CalendarId::Gregorian:
case CalendarId::Hebrew:
case CalendarId::Indian:
case CalendarId::Islamic:
case CalendarId::IslamicCivil:
case CalendarId::IslamicRGSA:
case CalendarId::IslamicTabular:
case CalendarId::IslamicUmmAlQura:
case CalendarId::Japanese:
case CalendarId::Persian:
case CalendarId::ROC:
return false;
case CalendarId::Coptic:
case CalendarId::Ethiopian:
case CalendarId::EthiopianAmeteAlem:
return true;
}
JS_CONSTEXPR_CRASH("invalid calendar id");
}
constexpr int32_t CalendarMonthsPerYear(CalendarId id) {
if (CalendarHasLeapMonths(id) || CalendarHasEpagomenalMonths(id)) {
return 13;
}
return 12;
}
constexpr std::pair<int32_t, int32_t> CalendarDaysInMonth(CalendarId id) {
switch (id) {
// ISO8601 calendar.
// M02: 28-29 days
// M04, M06, M09, M11: 30 days
// M01, M03, M05, M07, M08, M10, M12: 31 days
case CalendarId::ISO8601:
case CalendarId::Buddhist:
case CalendarId::Gregorian:
case CalendarId::Japanese:
case CalendarId::ROC:
return {28, 31};
// Chinese/Dangi calendars have 29-30 days per month.
//
// Hebrew:
// M01, M05, M07, M09, M11: 30 days.
// M02, M03: 29-30 days.
// M04, M06, M08, M10, M12: 29 days.
// M05L: 30 days
//
// Islamic calendars have 29-30 days.
//
// IslamicCivil, IslamicTabular:
// M01, M03, M05, M07, M09, M11: 30 days
// M02, M04, M06, M08, M10: 29 days
// M12: 29-30 days.
case CalendarId::Chinese:
case CalendarId::Dangi:
case CalendarId::Hebrew:
case CalendarId::Islamic:
case CalendarId::IslamicCivil:
case CalendarId::IslamicRGSA:
case CalendarId::IslamicTabular:
case CalendarId::IslamicUmmAlQura:
return {29, 30};
// Coptic, Ethiopian, EthiopianAmeteAlem:
// M01..M12: 30 days.
// M13: 5-6 days.
case CalendarId::Coptic:
case CalendarId::Ethiopian:
case CalendarId::EthiopianAmeteAlem:
return {5, 30};
// Indian:
// M1: 30-31 days.
// M02..M06: 31 days
// M07..M12: 30 days
case CalendarId::Indian:
return {30, 31};
// Persian:
// M01..M06: 31 days
// M07..M11: 30 days
// M12: 29-30 days
case CalendarId::Persian:
return {29, 31};
}
JS_CONSTEXPR_CRASH("invalid calendar id");
}
// ISO8601 calendar.
// M02: 28-29 days
// M04, M06, M09, M11: 30 days
// M01, M03, M05, M07, M08, M10, M12: 31 days
constexpr std::pair<int32_t, int32_t> ISODaysInMonth(MonthCode monthCode) {
int32_t ordinal = monthCode.ordinal();
if (ordinal == 2) {
return {28, 29};
}
if (ordinal == 4 || ordinal == 6 || ordinal == 9 || ordinal == 11) {
return {30, 30};
}
return {31, 31};
}
constexpr std::pair<int32_t, int32_t> CalendarDaysInMonth(CalendarId id,
MonthCode monthCode) {
switch (id) {
case CalendarId::ISO8601:
case CalendarId::Buddhist:
case CalendarId::Gregorian:
case CalendarId::Japanese:
case CalendarId::ROC:
return ISODaysInMonth(monthCode);
// Chinese/Dangi calendars have 29-30 days per month.
case CalendarId::Chinese:
case CalendarId::Dangi:
return {29, 30};
// Coptic, Ethiopian, EthiopianAmeteAlem:
// M01..M12: 30 days.
// M13: 5-6 days.
case CalendarId::Coptic:
case CalendarId::Ethiopian:
case CalendarId::EthiopianAmeteAlem: {
if (monthCode.ordinal() <= 12) {
return {30, 30};
}
return {5, 6};
}
// Hebrew:
// M01, M05, M07, M09, M11: 30 days.
// M02, M03: 29-30 days.
// M04, M06, M08, M10, M12: 29 days.
// M05L: 30 days
case CalendarId::Hebrew: {
int32_t ordinal = monthCode.ordinal();
if (ordinal == 2 || ordinal == 3) {
return {29, 30};
}
if ((ordinal & 1) == 1 || monthCode.isLeapMonth()) {
return {30, 30};
}
return {29, 29};
}
// Indian:
// M1: 30-31 days.
// M02..M06: 31 days
// M07..M12: 30 days
case CalendarId::Indian: {
int32_t ordinal = monthCode.ordinal();
if (ordinal == 1) {
return {30, 31};
}
if (ordinal <= 6) {
return {31, 31};
}
return {30, 30};
}
// Islamic calendars have 29-30 days per month.
case CalendarId::Islamic:
case CalendarId::IslamicRGSA:
case CalendarId::IslamicUmmAlQura:
return {29, 30};
// IslamicCivil, IslamicTabular:
// M01, M03, M05, M07, M09, M11: 30 days
// M02, M04, M06, M08, M10: 29 days
// M12: 29-30 days.
case CalendarId::IslamicCivil:
case CalendarId::IslamicTabular: {
int32_t ordinal = monthCode.ordinal();
if ((ordinal & 1) == 1) {
return {30, 30};
}
if (ordinal < 12) {
return {29, 29};
}
return {29, 30};
}
// Persian:
// M01..M06: 31 days
// M07..M11: 30 days
// M12: 29-30 days
case CalendarId::Persian: {
int32_t ordinal = monthCode.ordinal();
if (ordinal <= 6) {
return {31, 31};
}
if (ordinal <= 11) {
return {30, 30};
}
return {29, 30};
}
}
JS_CONSTEXPR_CRASH("invalid calendar id");
}
} // namespace js::temporal