mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: 新增希伯来历法核心与 PlainDate 历法面
This commit is contained in:
@@ -0,0 +1,547 @@
|
||||
//! 日历核心层:非 ISO 历法与 ISO 日期(proleptic Gregorian 纪元日)的双向换算、
|
||||
//! 年长/闰年/月长/纪元/月码等字段查询。
|
||||
//!
|
||||
//! 本层只服务纯算术历法(无数据表)。hebrew 采用 keviyah(四门表)算法,
|
||||
//! 与 ICU4X `calendrical_calculations::hebrew_keviyah` 同源;后续历法(偏移族、
|
||||
//! 数据表族)按历法族逐步补入。
|
||||
//!
|
||||
//! 对象存储约定:PlainDate 等对象槽位恒存 ISO 年/月/日,日历 ID 决定 getter
|
||||
//! 如何解释该 ISO 日期;from 路径把历法字段换算成 ISO 后入槽。
|
||||
|
||||
use super::common::{civil_from_days, days_from_civil};
|
||||
|
||||
/// hebrew 历法纪元(Tishrei 1 年 1)对应的 ISO 纪元日(1970-01-01 为 0)。
|
||||
/// 由 RataDie -1373427(0001-01-01 为 1)换算:-1373427 - 719163。
|
||||
const HEBREW_EPOCH_DAYS: i128 = -2_092_590;
|
||||
|
||||
/// Molad Beharad(纪元首朔)相对周首(周六 18:00)的 ḥalakim 偏移。
|
||||
const MOLAD_BEHERAD_OFFSET: i64 = 31_524;
|
||||
/// 一个朔望月的 ḥalakim 时长(29 日 12 时 793 分)。
|
||||
const HEBREW_LUNATION_TIME: i64 = 765_433;
|
||||
/// 一周的 ḥalakim 数(1080 × 24 × 7)。
|
||||
const HALAKIM_IN_WEEK: i64 = 181_440;
|
||||
|
||||
/// d-h-p 记法转 ḥalakim(d 为 1 起周几,h 为 0-23 时,p 为 0-1079 分)。
|
||||
const fn hal(d: i32, h: i32, p: i32) -> i64 {
|
||||
((d - 1) * 24 + h) as i64 * 1080 + p as i64
|
||||
}
|
||||
|
||||
/// 四门表:按 Metonic 周期年型分四组,每组 7 个 ḥalakim 分界,
|
||||
/// 决定该年 molad 落在哪个 keviyah。
|
||||
const FOUR_GATES_LMINUSONE: [i64; 7] = [
|
||||
hal(7, 18, 0),
|
||||
hal(1, 9, 204),
|
||||
hal(2, 18, 0),
|
||||
hal(3, 9, 204),
|
||||
hal(5, 9, 204),
|
||||
hal(5, 18, 0),
|
||||
hal(6, 9, 204),
|
||||
];
|
||||
const FOUR_GATES_LPLUSONE: [i64; 7] = [
|
||||
hal(7, 18, 0),
|
||||
hal(1, 9, 204),
|
||||
hal(2, 15, 589),
|
||||
hal(3, 9, 204),
|
||||
hal(5, 9, 204),
|
||||
hal(5, 18, 0),
|
||||
hal(6, 0, 408),
|
||||
];
|
||||
const FOUR_GATES_LPLUSMINUSONE: [i64; 7] = [
|
||||
hal(7, 18, 0),
|
||||
hal(1, 9, 204),
|
||||
hal(2, 15, 589),
|
||||
hal(3, 9, 204),
|
||||
hal(5, 9, 204),
|
||||
hal(5, 18, 0),
|
||||
hal(6, 9, 204),
|
||||
];
|
||||
const FOUR_GATES_LEAP: [i64; 7] = [
|
||||
hal(7, 18, 0),
|
||||
hal(1, 20, 491),
|
||||
hal(2, 18, 0),
|
||||
hal(3, 18, 0),
|
||||
hal(4, 11, 695),
|
||||
hal(5, 18, 0),
|
||||
hal(6, 20, 491),
|
||||
];
|
||||
|
||||
/// 年型:定 Heshvan 与 Kislev 月长(缺 29/29、常 29/30、全 30/30)。
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
|
||||
enum YearType {
|
||||
Deficient = -1,
|
||||
Regular = 0,
|
||||
Complete = 1,
|
||||
}
|
||||
|
||||
/// 新年首日允许的周几(Rosh Hashanah 推迟规则后只剩四种)。
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
|
||||
enum StartOfYear {
|
||||
Monday = 2,
|
||||
Tuesday = 3,
|
||||
Thursday = 5,
|
||||
Saturday = 7,
|
||||
}
|
||||
|
||||
/// 一年的 keviyah(14 种):新年周几 + 年型 + 逾越节周几。
|
||||
/// 判别值按四门表位置排列,闰年 keviyah 排在平年之后(+7)。
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
|
||||
enum Keviyah {
|
||||
// 平年
|
||||
BetHetGimel = 0, // 2D3
|
||||
BetShinHe = 1, // 2C5
|
||||
GimelKafHe = 2, // 3R5
|
||||
HeKafZayin = 3, // 5R7
|
||||
HeShinAlef = 4, // 5C1
|
||||
ZayinHetAlef = 5, // 7D1
|
||||
ZayinShinGimel = 6, // 7C3
|
||||
// 闰年
|
||||
BetHetHe = 7, // 2D5
|
||||
BetShinZayin = 8, // 2C7
|
||||
GimelKafZayin = 9, // 3R7
|
||||
HeHetAlef = 10, // 5D1
|
||||
HeShinGimel = 11, // 5C3
|
||||
ZayinHetGimel = 12, // 7D3
|
||||
ZayinShinHe = 13, // 7C5
|
||||
}
|
||||
|
||||
impl Keviyah {
|
||||
/// 年型(keviyah 第二字母:ח 缺 / כ 常 / ש 全)。
|
||||
fn year_type(self) -> YearType {
|
||||
match self {
|
||||
Keviyah::BetHetGimel => YearType::Deficient,
|
||||
Keviyah::BetShinHe => YearType::Complete,
|
||||
Keviyah::GimelKafHe => YearType::Regular,
|
||||
Keviyah::HeKafZayin => YearType::Regular,
|
||||
Keviyah::HeShinAlef => YearType::Complete,
|
||||
Keviyah::ZayinHetAlef => YearType::Deficient,
|
||||
Keviyah::ZayinShinGimel => YearType::Complete,
|
||||
Keviyah::BetHetHe => YearType::Deficient,
|
||||
Keviyah::BetShinZayin => YearType::Complete,
|
||||
Keviyah::GimelKafZayin => YearType::Regular,
|
||||
Keviyah::HeHetAlef => YearType::Deficient,
|
||||
Keviyah::HeShinGimel => YearType::Complete,
|
||||
Keviyah::ZayinHetGimel => YearType::Deficient,
|
||||
Keviyah::ZayinShinHe => YearType::Complete,
|
||||
}
|
||||
}
|
||||
|
||||
/// 新年首日周几(keviyah 首字母:ב 一 / ג 二 / ה 五 / ז 七)。
|
||||
fn start_of_year(self) -> StartOfYear {
|
||||
match self {
|
||||
Keviyah::BetHetGimel => StartOfYear::Monday,
|
||||
Keviyah::BetShinHe => StartOfYear::Monday,
|
||||
Keviyah::GimelKafHe => StartOfYear::Tuesday,
|
||||
Keviyah::HeKafZayin => StartOfYear::Thursday,
|
||||
Keviyah::HeShinAlef => StartOfYear::Thursday,
|
||||
Keviyah::ZayinHetAlef => StartOfYear::Saturday,
|
||||
Keviyah::ZayinShinGimel => StartOfYear::Saturday,
|
||||
Keviyah::BetHetHe => StartOfYear::Monday,
|
||||
Keviyah::BetShinZayin => StartOfYear::Monday,
|
||||
Keviyah::GimelKafZayin => StartOfYear::Tuesday,
|
||||
Keviyah::HeHetAlef => StartOfYear::Thursday,
|
||||
Keviyah::HeShinGimel => StartOfYear::Thursday,
|
||||
Keviyah::ZayinHetGimel => StartOfYear::Saturday,
|
||||
Keviyah::ZayinShinHe => StartOfYear::Saturday,
|
||||
}
|
||||
}
|
||||
|
||||
/// 闰年 keviyah(判别值 ≥ 7)。
|
||||
fn is_leap(self) -> bool {
|
||||
self >= Keviyah::BetHetHe
|
||||
}
|
||||
|
||||
/// 第 ordinal_month 个序月(1 起,Tishrei 为 1)的月长。
|
||||
fn month_len(self, ordinal_month: u8) -> u8 {
|
||||
let year_type = self.year_type();
|
||||
let is_leap = self.is_leap();
|
||||
// 闰年中 Adar 之后的序月对应上一个月号。
|
||||
let month_number = ordinal_month - (is_leap && ordinal_month >= 6) as u8;
|
||||
29
|
||||
+ (
|
||||
month_number % 2
|
||||
// Heshvan 在全年型为长月
|
||||
+ (ordinal_month == 2 && year_type == YearType::Complete) as u8
|
||||
// Kislev 在缺年型为短月
|
||||
- (ordinal_month == 3 && year_type == YearType::Deficient) as u8
|
||||
)
|
||||
}
|
||||
|
||||
/// 第 ordinal_month 个序月之前的累计天数。
|
||||
fn days_preceding(self, ordinal_month: u8) -> u16 {
|
||||
let year_type = self.year_type();
|
||||
let is_leap = self.is_leap();
|
||||
let month_number = ordinal_month - (is_leap && ordinal_month > 6) as u8;
|
||||
29 * (ordinal_month as u16 - 1)
|
||||
+ (
|
||||
month_number / 2
|
||||
// Adar I 为长月
|
||||
+ (is_leap && ordinal_month > 6) as u8
|
||||
// Heshvan 在全年型为长月
|
||||
+ (ordinal_month > 2 && year_type == YearType::Complete) as u8
|
||||
// Kislev 在缺年型为短月
|
||||
- (ordinal_month > 3 && year_type == YearType::Deficient) as u8
|
||||
) as u16
|
||||
}
|
||||
|
||||
/// 年内第 day_of_year 天(1 起)折算为(序月, 月内日)。
|
||||
fn month_day_for(self, day_of_year: u16) -> (u8, u8) {
|
||||
// 以 30 除(而非 29)以覆盖年初所有前月皆 30 天的情形。
|
||||
let mut month = ((day_of_year - 1) / 30) as u8 + 1;
|
||||
let mut days_before_month = self.days_preceding(month);
|
||||
let mut last_day_of_month = self.days_preceding(month + 1);
|
||||
while day_of_year > last_day_of_month {
|
||||
month += 1;
|
||||
days_before_month = last_day_of_month;
|
||||
last_day_of_month = self.days_preceding(month + 1);
|
||||
}
|
||||
(month, (day_of_year - days_before_month) as u8)
|
||||
}
|
||||
|
||||
/// 年长(平年 353/354/355,闰年 383/384/385)。
|
||||
fn year_length(self) -> u16 {
|
||||
let base = if self.is_leap() { 384 } else { 354 };
|
||||
(base + self.year_type() as i16) as u16
|
||||
}
|
||||
}
|
||||
|
||||
/// Metonic 周期年型(19 年 7 闰:3、6、8、11、14、17、19 闰)。
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
enum MetonicCycleType {
|
||||
LMinusOne,
|
||||
LPlusOne,
|
||||
LPlusMinusOne,
|
||||
Leap,
|
||||
}
|
||||
|
||||
impl MetonicCycleType {
|
||||
fn for_h_year(h_year: i128) -> Self {
|
||||
let remainder = h_year.rem_euclid(19);
|
||||
match remainder {
|
||||
2 | 5 | 10 | 13 | 16 => Self::LMinusOne,
|
||||
1 | 4 | 9 | 12 | 15 => Self::LPlusOne,
|
||||
7 | 18 => Self::LPlusMinusOne,
|
||||
_ => Self::Leap,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 年 h_year 的 Tishrei 朔前累计月数(Metonic 周期:235 月 / 19 年)。
|
||||
fn months_preceding_molad(h_year: i128) -> i128 {
|
||||
(235 * (h_year - 1) + 1).div_euclid(19)
|
||||
}
|
||||
|
||||
/// 年 h_year 的 Tishrei 朔:(自 Beharad 周起的周数, 周内 ḥalakim)。
|
||||
fn molad_details(h_year: i128) -> (i128, i64) {
|
||||
let months_preceding = months_preceding_molad(h_year);
|
||||
let molad = i128::from(MOLAD_BEHERAD_OFFSET) + months_preceding * i128::from(HEBREW_LUNATION_TIME);
|
||||
(
|
||||
molad.div_euclid(i128::from(HALAKIM_IN_WEEK)),
|
||||
molad.rem_euclid(i128::from(HALAKIM_IN_WEEK)) as i64,
|
||||
)
|
||||
}
|
||||
|
||||
/// 四门表查询:按年型与周内 ḥalakim 定 keviyah。
|
||||
fn keviyah_for(cycle_type: MetonicCycleType, in_week: i64) -> Keviyah {
|
||||
let gate = match cycle_type {
|
||||
MetonicCycleType::LMinusOne => &FOUR_GATES_LMINUSONE,
|
||||
MetonicCycleType::LPlusOne => &FOUR_GATES_LPLUSONE,
|
||||
MetonicCycleType::LPlusMinusOne => &FOUR_GATES_LPLUSMINUSONE,
|
||||
MetonicCycleType::Leap => &FOUR_GATES_LEAP,
|
||||
};
|
||||
// 首项跨周(周尾与周首同属一个 keviyah),其余按上界切分。
|
||||
let keviyot = if in_week >= gate[0] || in_week < gate[1] {
|
||||
(Keviyah::BetHetGimel, Keviyah::BetHetHe)
|
||||
} else if in_week < gate[2] {
|
||||
(Keviyah::BetShinHe, Keviyah::BetShinZayin)
|
||||
} else if in_week < gate[3] {
|
||||
(Keviyah::GimelKafHe, Keviyah::GimelKafZayin)
|
||||
} else if in_week < gate[4] {
|
||||
(Keviyah::HeKafZayin, Keviyah::HeHetAlef)
|
||||
} else if in_week < gate[5] {
|
||||
(Keviyah::HeShinAlef, Keviyah::HeShinGimel)
|
||||
} else if in_week < gate[6] {
|
||||
(Keviyah::ZayinHetAlef, Keviyah::ZayinHetGimel)
|
||||
} else {
|
||||
(Keviyah::ZayinShinGimel, Keviyah::ZayinShinHe)
|
||||
};
|
||||
if cycle_type == MetonicCycleType::Leap {
|
||||
keviyot.1
|
||||
} else {
|
||||
keviyot.0
|
||||
}
|
||||
}
|
||||
|
||||
/// 一年的全部信息:keviyah + 自 Beharad 周起的周数。
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
struct YearInfo {
|
||||
keviyah: Keviyah,
|
||||
weeks_since_beharad: i128,
|
||||
}
|
||||
|
||||
impl YearInfo {
|
||||
/// 计算年 h_year 的 YearInfo。
|
||||
fn compute_for(h_year: i128) -> Self {
|
||||
let (mut weeks_since_beharad, in_week) = molad_details(h_year);
|
||||
let cycle_type = MetonicCycleType::for_h_year(h_year);
|
||||
let keviyah = keviyah_for(cycle_type, in_week);
|
||||
// 周六(Hebrew Saturday)后六小时无条件推迟到周一,跨入新周。
|
||||
if in_week >= hal(7, 18, 0) {
|
||||
weeks_since_beharad += 1;
|
||||
}
|
||||
Self {
|
||||
keviyah,
|
||||
weeks_since_beharad,
|
||||
}
|
||||
}
|
||||
|
||||
/// 新年首日(Tishrei 1)的 ISO 纪元日。
|
||||
fn new_year(self) -> i128 {
|
||||
// Beharad 周从周一开始。
|
||||
const BEHARAD_START_OF_YEAR: i128 = StartOfYear::Monday as i128;
|
||||
let days_since_beharad = self.weeks_since_beharad * 7
|
||||
+ self.keviyah.start_of_year() as i128
|
||||
- BEHARAD_START_OF_YEAR;
|
||||
HEBREW_EPOCH_DAYS + days_since_beharad
|
||||
}
|
||||
|
||||
/// 含日 rd 的年份:先以平均年长(35975351/98496)近似,再校正。
|
||||
fn year_containing_rd(rd: i128) -> (Self, i128) {
|
||||
let mut h_year = (rd - HEBREW_EPOCH_DAYS) * 98496 / 35975351 + (rd >= HEBREW_EPOCH_DAYS) as i128;
|
||||
let mut year = Self::compute_for(h_year);
|
||||
if rd < year.new_year() {
|
||||
h_year -= 1;
|
||||
year = Self::compute_for(h_year);
|
||||
} else if rd >= year.new_year() + year.keviyah.year_length() as i128 {
|
||||
h_year += 1;
|
||||
year = Self::compute_for(h_year);
|
||||
}
|
||||
(year, h_year)
|
||||
}
|
||||
}
|
||||
|
||||
// ───────────────────────── hebrew 对外接口 ─────────────────────────
|
||||
|
||||
/// hebrew 是否闰年(19 年周期 3、6、8、11、14、17、19 闰)。
|
||||
pub(crate) fn hebrew_is_leap(h_year: i128) -> bool {
|
||||
YearInfo::compute_for(h_year).keviyah.is_leap()
|
||||
}
|
||||
|
||||
/// hebrew 年长(353/354/355/383/384/385)。
|
||||
pub(crate) fn hebrew_year_length(h_year: i128) -> i128 {
|
||||
YearInfo::compute_for(h_year).keviyah.year_length() as i128
|
||||
}
|
||||
|
||||
/// hebrew 某序月的月长。
|
||||
pub(crate) fn hebrew_days_in_month(h_year: i128, ordinal_month: u32) -> i128 {
|
||||
YearInfo::compute_for(h_year).keviyah.month_len(ordinal_month as u8) as i128
|
||||
}
|
||||
|
||||
/// hebrew 年内的月数(平年 12、闰年 13)。
|
||||
pub(crate) fn hebrew_months_in_year(h_year: i128) -> i128 {
|
||||
12 + YearInfo::compute_for(h_year).keviyah.is_leap() as i128
|
||||
}
|
||||
|
||||
/// hebrew 纪元恒为 "am"(Anno Mundi),eraYear 即年号。
|
||||
pub(crate) const HEBREW_ERA: &str = "am";
|
||||
|
||||
/// hebrew 日期(年, 序月, 日)→ ISO 纪元日。
|
||||
pub(crate) fn hebrew_to_iso_days(h_year: i128, ordinal_month: u32, day: u32) -> i128 {
|
||||
let year_info = YearInfo::compute_for(h_year);
|
||||
year_info.new_year()
|
||||
+ year_info.keviyah.days_preceding(ordinal_month as u8) as i128
|
||||
+ (day - 1) as i128
|
||||
}
|
||||
|
||||
/// ISO 纪元日 → hebrew 日期(年, 序月, 日)。
|
||||
pub(crate) fn iso_days_to_hebrew(iso_days: i128) -> (i128, u32, u32) {
|
||||
let (year_info, h_year) = YearInfo::year_containing_rd(iso_days);
|
||||
let day_of_year = (iso_days - year_info.new_year()) as u16 + 1;
|
||||
let (month, day) = year_info.keviyah.month_day_for(day_of_year);
|
||||
(h_year, month as u32, day as u32)
|
||||
}
|
||||
|
||||
/// hebrew 日期(年, 序月, 日)→ ISO 日期分量。
|
||||
pub(crate) fn hebrew_to_iso(h_year: i128, ordinal_month: u32, day: u32) -> (i32, u32, u32) {
|
||||
let (y, m, d) = civil_from_days(hebrew_to_iso_days(h_year, ordinal_month, day));
|
||||
(y as i32, m as u32, d as u32)
|
||||
}
|
||||
|
||||
/// ISO 日期分量 → hebrew 日期(年, 序月, 日)。
|
||||
pub(crate) fn iso_to_hebrew(iso_year: i32, iso_month: u32, iso_day: u32) -> (i128, u32, u32) {
|
||||
iso_days_to_hebrew(days_from_civil(
|
||||
i128::from(iso_year),
|
||||
i128::from(iso_month),
|
||||
i128::from(iso_day),
|
||||
))
|
||||
}
|
||||
|
||||
/// 数值月(平年 1-12、闰年 1-13)→ 序月:hebrew 数值月即序月(闰年 Adar I 为 6、
|
||||
/// Adar II 为 7、Elul 为 13),恒等映射。越界返回 None。
|
||||
pub(crate) fn hebrew_ordinal_from_number(h_year: i128, number: u32) -> Option<u32> {
|
||||
let max = if hebrew_is_leap(h_year) { 13 } else { 12 };
|
||||
(1..=max).contains(&number).then_some(number)
|
||||
}
|
||||
|
||||
/// 序月 → 数值月:hebrew 数值月即序月(平年 1-12、闰年 1-13),恒等映射。
|
||||
pub(crate) fn hebrew_number_from_ordinal(ordinal_month: u32) -> Option<u32> {
|
||||
(1..=13).contains(&ordinal_month).then_some(ordinal_month)
|
||||
}
|
||||
|
||||
/// 月码 → 序月:M05L 仅闰年合法(序月 6);M06-M12 闰年 +1。
|
||||
/// 非法(含 M13、非闰年的 M05L、M02L 等)返回 None。
|
||||
pub(crate) fn hebrew_ordinal_from_month_code(h_year: i128, code: &str) -> Option<u32> {
|
||||
let is_leap = hebrew_is_leap(h_year);
|
||||
if code.ends_with('L') {
|
||||
return (code == "M05L" && is_leap).then_some(6);
|
||||
}
|
||||
let number: u32 = code[1..3].parse().ok()?;
|
||||
if !(1..=12).contains(&number) {
|
||||
return None;
|
||||
}
|
||||
let ordinal = number + (is_leap && number >= 6) as u32;
|
||||
Some(ordinal)
|
||||
}
|
||||
|
||||
/// 序月 → 月码:闰年序月 6 为 M05L,序月 7 及以上 -1。
|
||||
pub(crate) fn hebrew_month_code(h_year: i128, ordinal_month: u32) -> String {
|
||||
let is_leap = hebrew_is_leap(h_year);
|
||||
if is_leap && ordinal_month == 6 {
|
||||
return "M05L".to_string();
|
||||
}
|
||||
let number = ordinal_month - (is_leap && ordinal_month >= 6) as u32;
|
||||
format!("M{number:02}")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// 锚点:Tishrei 1 年 3760 应为周六(ICU4X 同款测试向量)。
|
||||
#[test]
|
||||
fn test_epoch_3760_saturday() {
|
||||
let (y, m, d) = hebrew_to_iso(3760, 1, 1);
|
||||
// 1 年 8 月 28 日(proleptic Gregorian)
|
||||
assert_eq!((y, m, d), (-1, 8, 28));
|
||||
}
|
||||
|
||||
// 锚点:Rosh Hashanah 5785 = 2024-10-03(周四),5784 = 2023-09-16(周六),
|
||||
// 5786 = 2025-09-23(周二)。新年首日不可能是周日/周三/周五,可自查锚点。
|
||||
#[test]
|
||||
fn test_rosh_hashanah_anchors() {
|
||||
let (y, m, d) = hebrew_to_iso(5784, 1, 1);
|
||||
assert_eq!((y, m, d), (2023, 9, 16));
|
||||
let (y, m, d) = hebrew_to_iso(5785, 1, 1);
|
||||
assert_eq!((y, m, d), (2024, 10, 3));
|
||||
let (y, m, d) = hebrew_to_iso(5786, 1, 1);
|
||||
assert_eq!((y, m, d), (2025, 9, 23));
|
||||
}
|
||||
|
||||
// 双向换算自洽(语料年份区间)。
|
||||
#[test]
|
||||
fn test_roundtrip() {
|
||||
for h_year in [0, 1, 5, 10, 3700, 5781, 5782, 5783, 5784, 5785, 5800] {
|
||||
let months = if hebrew_is_leap(h_year) { 13 } else { 12 };
|
||||
for month in 1..=months {
|
||||
let dim = hebrew_days_in_month(h_year, month) as u32;
|
||||
let (y, m, d) = hebrew_to_iso(h_year, month, dim);
|
||||
let (hy, hm, hd) = iso_to_hebrew(y, m, d);
|
||||
assert_eq!((hy, hm, hd), (h_year, month, dim), "year {h_year} month {month}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// keviyah 年长取值合法(语料 validKeviahSymbols 的 6 种年长)。
|
||||
#[test]
|
||||
fn test_year_lengths_valid() {
|
||||
for year in 3700..=5800 {
|
||||
let len = hebrew_year_length(year);
|
||||
assert!(
|
||||
matches!(len, 353 | 354 | 355 | 383 | 384 | 385),
|
||||
"year {year} -> {len}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 语料 basic-hebrew 5783(平年)逐月长度。
|
||||
#[test]
|
||||
fn test_month_lengths_5783() {
|
||||
let expected = [30, 30, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29];
|
||||
for (index, &len) in expected.iter().enumerate() {
|
||||
let month = (index + 1) as u32;
|
||||
assert_eq!(hebrew_days_in_month(5783, month), len as i128, "month {month}");
|
||||
}
|
||||
assert_eq!(hebrew_months_in_year(5783), 12);
|
||||
assert!(!hebrew_is_leap(5783));
|
||||
}
|
||||
|
||||
// 语料 leap-year-hebrew 5784(闰年)逐月长度。
|
||||
#[test]
|
||||
fn test_month_lengths_5784() {
|
||||
let expected = [30, 29, 29, 29, 30, 30, 29, 30, 29, 30, 29, 30, 29];
|
||||
for (index, &len) in expected.iter().enumerate() {
|
||||
let month = (index + 1) as u32;
|
||||
assert_eq!(hebrew_days_in_month(5784, month), len as i128, "month {month}");
|
||||
}
|
||||
assert_eq!(hebrew_months_in_year(5784), 13);
|
||||
assert!(hebrew_is_leap(5784));
|
||||
}
|
||||
|
||||
// 语料 calc-epoch-year-hebrew:年 0..10 的 Cheshvan / Kislev 月长
|
||||
// (含 ICU4C ICU-23007 的纪元年推迟修正)。
|
||||
#[test]
|
||||
fn test_epoch_years_cheshvan_kislev() {
|
||||
let cheshvan = [29, 30, 30, 29, 29, 30, 30, 29, 29, 30, 29];
|
||||
let kislev = [30, 30, 30, 29, 30, 30, 30, 30, 29, 30, 30];
|
||||
for (year, (c, k)) in cheshvan.iter().zip(kislev.iter()).enumerate() {
|
||||
assert_eq!(hebrew_days_in_month(year as i128, 2), *c as i128, "year {year}");
|
||||
assert_eq!(hebrew_days_in_month(year as i128, 3), *k as i128, "year {year}");
|
||||
}
|
||||
}
|
||||
|
||||
// 月码映射(闰年 5784 / 平年 5783)。
|
||||
#[test]
|
||||
fn test_month_code_mapping() {
|
||||
// 闰年
|
||||
assert_eq!(hebrew_month_code(5784, 6), "M05L");
|
||||
assert_eq!(hebrew_month_code(5784, 7), "M06");
|
||||
assert_eq!(hebrew_month_code(5784, 13), "M12");
|
||||
assert_eq!(hebrew_ordinal_from_month_code(5784, "M05L"), Some(6));
|
||||
assert_eq!(hebrew_ordinal_from_month_code(5784, "M06"), Some(7));
|
||||
assert_eq!(hebrew_ordinal_from_month_code(5784, "M12"), Some(13));
|
||||
assert_eq!(hebrew_ordinal_from_month_code(5784, "M13"), None);
|
||||
// 平年
|
||||
assert_eq!(hebrew_month_code(5783, 6), "M06");
|
||||
assert_eq!(hebrew_month_code(5783, 12), "M12");
|
||||
assert_eq!(hebrew_ordinal_from_month_code(5783, "M06"), Some(6));
|
||||
assert_eq!(hebrew_ordinal_from_month_code(5783, "M05L"), None);
|
||||
assert_eq!(hebrew_ordinal_from_month_code(5783, "M12"), Some(12));
|
||||
}
|
||||
|
||||
// ICU4X 测试向量:ISO ↔ hebrew 双向换算。
|
||||
#[derive(Clone, Copy)]
|
||||
struct Vector4x {
|
||||
iso: (i32, u32, u32),
|
||||
heb: (i32, u32, u32),
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_icu4x_vectors() {
|
||||
let cases: [Vector4x; 6] = [
|
||||
// iso 为公历年月日,heb 为希伯来年与序数月与日
|
||||
Vector4x { iso: (2021, 1, 10), heb: (5781, 4, 26) },
|
||||
Vector4x { iso: (2021, 9, 10), heb: (5782, 1, 4) },
|
||||
Vector4x { iso: (2022, 2, 10), heb: (5782, 6, 9) }, // Adar I(闰年)
|
||||
Vector4x { iso: (2022, 3, 10), heb: (5782, 7, 7) }, // Adar II(闰年)
|
||||
Vector4x { iso: (2022, 10, 10), heb: (5783, 1, 15) },
|
||||
Vector4x { iso: (2022, 12, 25), heb: (5783, 4, 1) },
|
||||
];
|
||||
for v in cases {
|
||||
let (iy, im, id) = v.iso;
|
||||
let (hy, hm, hd) = v.heb;
|
||||
let (y, m, d) = hebrew_to_iso(hy as i128, hm, hd);
|
||||
assert_eq!((y, m, d), (iy, im, id), "hebrew {hy}-{hm}-{hd}");
|
||||
let (ry, rm, rd) = iso_to_hebrew(iy, im, id);
|
||||
assert_eq!((ry, rm, rd), (hy as i128, hm, hd), "iso {iy}-{im}-{id}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
mod calendar;
|
||||
mod common;
|
||||
mod difference;
|
||||
mod duration;
|
||||
|
||||
@@ -5,14 +5,20 @@ use oxide_runtime_api::{to_string, NativeResult, ProtoKind, VmHost};
|
||||
use oxide_types::object::JsObject;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
use super::calendar::{
|
||||
hebrew_days_in_month, hebrew_is_leap, hebrew_month_code, hebrew_months_in_year,
|
||||
hebrew_number_from_ordinal, hebrew_ordinal_from_month_code, hebrew_ordinal_from_number, hebrew_to_iso,
|
||||
hebrew_year_length, iso_to_hebrew, HEBREW_ERA,
|
||||
};
|
||||
use super::common::native_try;
|
||||
use super::common::{
|
||||
calendar_annotation, civil_from_days, days_from_civil, days_in_month, ensure_plain_date, format_iso_year,
|
||||
get_calendar_id, get_double_prop, initialize_temporal_receiver, is_ctor_call, parse_plain_date_string,
|
||||
parse_round_options, plain_date_time_object_parts, receiver_obj, reject_partial_object_with_calendar_or_time_zone,
|
||||
resolve_date_time_parts, temporal_calendar_id, temporal_calendar_id_strict, temporal_number_component,
|
||||
temporal_option_string, temporal_option_value, temporal_overflow, temporal_to_show_calendar,
|
||||
to_temporal_time_zone_identifier, zoned_date_time_plain_parts, DateTimeParts, ShowCalendar,
|
||||
get_calendar_id, get_double_prop, initialize_temporal_receiver, is_ctor_call, month_code_well_formed,
|
||||
parse_plain_date_string, parse_round_options, plain_date_time_object_parts, receiver_obj,
|
||||
reject_partial_object_with_calendar_or_time_zone, resolve_date_time_parts, temporal_calendar_id,
|
||||
temporal_calendar_id_strict, temporal_number_component, temporal_option_string, temporal_option_value,
|
||||
temporal_overflow, temporal_to_show_calendar, to_temporal_time_zone_identifier, zoned_date_time_plain_parts,
|
||||
DateTimeParts, ShowCalendar,
|
||||
};
|
||||
use super::difference::{difference_core, parse_difference_settings, IsoDateTime};
|
||||
use super::duration::duration_like_values;
|
||||
@@ -249,22 +255,54 @@ fn plain_date_ymd<H: VmHost>(vm: &mut H, args: &[u8]) -> Result<(f64, f64, f64),
|
||||
Ok((get_double_prop(obj, 0), get_double_prop(obj, 1), get_double_prop(obj, 2)))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.year` getter。
|
||||
/// 读 PlainDate 的 ISO 分量(槽 0-2)与日历 ID(槽 3),含 receiver 校验。
|
||||
/// getter 按日历 ID 分发:hebrew 换算为历法字段,其余历法直返 ISO 分量。
|
||||
fn plain_date_iso_ymd_calendar<H: VmHost>(
|
||||
vm: &mut H, args: &[u8],
|
||||
) -> Result<(i32, u32, u32, String), JsValue> {
|
||||
let ptr = receiver_obj(vm, args)?;
|
||||
let obj = unsafe { &*ptr };
|
||||
ensure_plain_date(vm, obj)?;
|
||||
let y = get_double_prop(obj, 0);
|
||||
let m = get_double_prop(obj, 1);
|
||||
let d = get_double_prop(obj, 2);
|
||||
if y.is_nan() || m.is_nan() || d.is_nan() {
|
||||
return Err(crate::error::create_type_error(vm, "called on incompatible receiver"));
|
||||
}
|
||||
let calendar = get_calendar_id(obj, 3);
|
||||
Ok((y.trunc() as i32, m.trunc() as u32, d.trunc() as u32, calendar))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.year` getter:hebrew 历返历法年,其余历法返 ISO 年。
|
||||
pub fn plain_date_year<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let (y, _, _) = native_try!(plain_date_ymd(vm, args));
|
||||
NativeResult::Ok(JsValue::float(y))
|
||||
let (y, m, d, calendar) = native_try!(plain_date_iso_ymd_calendar(vm, args));
|
||||
if calendar == "hebrew" {
|
||||
let (hy, _, _) = iso_to_hebrew(y, m, d);
|
||||
return NativeResult::Ok(JsValue::float(hy as f64));
|
||||
}
|
||||
NativeResult::Ok(JsValue::float(y as f64))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.month` getter。
|
||||
/// `Temporal.PlainDate.prototype.month` getter:hebrew 历返数值月(序月),其余历法返 ISO 月。
|
||||
pub fn plain_date_month<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let (_, m, _) = native_try!(plain_date_ymd(vm, args));
|
||||
NativeResult::Ok(JsValue::float(m))
|
||||
let (y, m, d, calendar) = native_try!(plain_date_iso_ymd_calendar(vm, args));
|
||||
if calendar == "hebrew" {
|
||||
let (hy, ordinal, _) = iso_to_hebrew(y, m, d);
|
||||
let _ = hy;
|
||||
let number = hebrew_number_from_ordinal(ordinal).unwrap_or(0);
|
||||
return NativeResult::Ok(JsValue::float(number as f64));
|
||||
}
|
||||
NativeResult::Ok(JsValue::float(m as f64))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.day` getter。
|
||||
/// `Temporal.PlainDate.prototype.day` getter:hebrew 历返历法日,其余历法返 ISO 日。
|
||||
pub fn plain_date_day<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let (_, _, d) = native_try!(plain_date_ymd(vm, args));
|
||||
NativeResult::Ok(JsValue::float(d))
|
||||
let (y, m, d, calendar) = native_try!(plain_date_iso_ymd_calendar(vm, args));
|
||||
if calendar == "hebrew" {
|
||||
let (_hy, _ordinal, hebrew_day) = iso_to_hebrew(y, m, d);
|
||||
return NativeResult::Ok(JsValue::float(hebrew_day as f64));
|
||||
}
|
||||
NativeResult::Ok(JsValue::float(d as f64))
|
||||
}
|
||||
|
||||
/// PlainDate ISO 串:auto/never 加 iso8601 日历保持裸 `±YYYY-MM-DD`;
|
||||
@@ -350,10 +388,130 @@ fn object_date_ymd<H: VmHost>(
|
||||
let (year, month, day, _) = zoned_date_time_plain_parts(vm, obj)?;
|
||||
return Ok((year, month, day, Some(get_calendar_id(obj, 2))));
|
||||
}
|
||||
// 先读日历:hebrew 走专用解析(era/eraYear 校验 + hebrew→ISO 换算),
|
||||
// 其余历法走 ISO 路径。
|
||||
let calendar_raw = temporal_option_value(vm, obj, val, "calendar")?;
|
||||
let calendar = temporal_calendar_id(vm, calendar_raw)?;
|
||||
if calendar.as_deref() == Some("hebrew") {
|
||||
let (year, month, day) = hebrew_object_date_ymd(vm, obj, val, constrain)?;
|
||||
return Ok((year, month, day, calendar));
|
||||
}
|
||||
let (year, month, day, _, calendar) = plain_date_time_object_parts(vm, val, obj, constrain, true)?;
|
||||
Ok((year, month, day, calendar))
|
||||
}
|
||||
|
||||
/// hebrew property bag 解析:读 era/eraYear/year/month/monthCode/day,定 hebrew
|
||||
/// 年/序月/日,换算为 ISO 年/月/日后入槽。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 读序(字典序):day → era → eraYear → month → monthCode → year。
|
||||
/// 2. day 必填(TypeError)。
|
||||
/// 3. era 族:恰一个现 → TypeError;双现 → era 须为 "am"(RangeError),eraYear 定年;
|
||||
/// 双缺 → year 必填(TypeError)。
|
||||
/// 4. month/monthCode 定序月(冲突 → RangeError)。
|
||||
/// 5. day 校验(1..=月长;constrain 钳制、reject 抛 RangeError)。
|
||||
/// 6. hebrew → ISO 换算。
|
||||
fn hebrew_object_date_ymd<H: VmHost>(
|
||||
vm: &mut H, obj: &JsObject, value: JsValue, constrain: bool,
|
||||
) -> Result<(i32, u32, u32), JsValue> {
|
||||
let day_raw = temporal_option_value(vm, obj, value, "day")?;
|
||||
let era_raw = temporal_option_value(vm, obj, value, "era")?;
|
||||
let era_year_raw = temporal_option_value(vm, obj, value, "eraYear")?;
|
||||
let month_raw = temporal_option_value(vm, obj, value, "month")?;
|
||||
let month_code_raw = temporal_option_value(vm, obj, value, "monthCode")?;
|
||||
let year_raw = temporal_option_value(vm, obj, value, "year")?;
|
||||
|
||||
if day_raw.is_undefined() {
|
||||
return Err(crate::error::create_type_error(vm, "day is required"));
|
||||
}
|
||||
|
||||
// era 族:恰一个现 → TypeError;双现 → era 须为 "am"、eraYear 定年;双缺 → year 必填。
|
||||
let era_present = !era_raw.is_undefined();
|
||||
let era_year_present = !era_year_raw.is_undefined();
|
||||
if era_present != era_year_present {
|
||||
return Err(crate::error::create_type_error(
|
||||
vm, "era and eraYear must both be present or both absent",
|
||||
));
|
||||
}
|
||||
let hebrew_year = if era_year_present {
|
||||
let era = temporal_option_string(vm, era_raw)?;
|
||||
if era != HEBREW_ERA {
|
||||
return Err(crate::error::create_range_error(vm, "invalid era"));
|
||||
}
|
||||
let era_year = temporal_number_component(vm, era_year_raw)?;
|
||||
if era_year < i32::MIN as f64 || era_year > i32::MAX as f64 {
|
||||
return Err(crate::error::create_range_error(vm, "invalid eraYear"));
|
||||
}
|
||||
era_year as i32
|
||||
} else {
|
||||
if year_raw.is_undefined() {
|
||||
return Err(crate::error::create_type_error(vm, "year is required"));
|
||||
}
|
||||
let year = temporal_number_component(vm, year_raw)?;
|
||||
if year < i32::MIN as f64 || year > i32::MAX as f64 {
|
||||
return Err(crate::error::create_range_error(vm, "invalid year"));
|
||||
}
|
||||
year as i32
|
||||
};
|
||||
|
||||
// month / monthCode 定序月。
|
||||
let month_code = if month_code_raw.is_undefined() {
|
||||
None
|
||||
} else {
|
||||
if !month_code_raw.is_string() {
|
||||
return Err(crate::error::create_type_error(vm, "monthCode must be a string"));
|
||||
}
|
||||
let code = to_string(month_code_raw);
|
||||
if !month_code_well_formed(&code) {
|
||||
return Err(crate::error::create_range_error(vm, "invalid monthCode"));
|
||||
}
|
||||
Some(code)
|
||||
};
|
||||
let ordinal_month = match (month_raw.is_undefined(), month_code) {
|
||||
(true, None) => {
|
||||
return Err(crate::error::create_type_error(vm, "month is required"));
|
||||
}
|
||||
(true, Some(code)) => hebrew_ordinal_from_month_code(hebrew_year as i128, &code)
|
||||
.ok_or_else(|| crate::error::create_range_error(vm, "invalid monthCode"))?,
|
||||
(false, Some(code)) => {
|
||||
let ordinal_from_code = hebrew_ordinal_from_month_code(hebrew_year as i128, &code)
|
||||
.ok_or_else(|| crate::error::create_range_error(vm, "invalid monthCode"))?;
|
||||
let month_num = temporal_number_component(vm, month_raw)?;
|
||||
let ordinal_from_num = hebrew_ordinal_from_number(hebrew_year as i128, month_num as u32)
|
||||
.ok_or_else(|| crate::error::create_range_error(vm, "invalid month"))?;
|
||||
if ordinal_from_code != ordinal_from_num {
|
||||
return Err(crate::error::create_range_error(vm, "month and monthCode disagree"));
|
||||
}
|
||||
ordinal_from_num
|
||||
}
|
||||
(false, None) => {
|
||||
let month_num = temporal_number_component(vm, month_raw)?;
|
||||
hebrew_ordinal_from_number(hebrew_year as i128, month_num as u32)
|
||||
.ok_or_else(|| crate::error::create_range_error(vm, "invalid month"))?
|
||||
}
|
||||
};
|
||||
|
||||
// day 校验(1..=月长;constrain 钳制、reject 抛 RangeError)。
|
||||
let day = temporal_number_component(vm, day_raw)?;
|
||||
if day < 1.0 {
|
||||
return Err(crate::error::create_range_error(vm, "invalid day"));
|
||||
}
|
||||
let month_len = hebrew_days_in_month(hebrew_year as i128, ordinal_month) as u32;
|
||||
let day = if day as u32 > month_len {
|
||||
if constrain {
|
||||
month_len
|
||||
} else {
|
||||
return Err(crate::error::create_range_error(vm, "invalid day"));
|
||||
}
|
||||
} else {
|
||||
day as u32
|
||||
};
|
||||
|
||||
// hebrew → ISO 换算。
|
||||
let (iso_year, iso_month, iso_day) = hebrew_to_iso(hebrew_year as i128, ordinal_month, day);
|
||||
Ok((iso_year, iso_month, iso_day))
|
||||
}
|
||||
|
||||
fn date_like_ymd<H: VmHost>(vm: &mut H, val: JsValue) -> Result<(i32, u32, u32, Option<String>), JsValue> {
|
||||
let ymd = if val.is_string() {
|
||||
let (y, m, d) = parse_plain_date_string(&to_string(val))
|
||||
@@ -414,13 +572,14 @@ pub(crate) fn days_in_month_iso(year: i32, month: u32) -> u32 {
|
||||
}
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.daysInMonth` getter。
|
||||
/// `Temporal.PlainDate.prototype.daysInMonth` getter:hebrew 历返历法月长,其余历法返 ISO 月长。
|
||||
pub fn plain_date_days_in_month<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let date = match plain_date_naive(vm, args) {
|
||||
Ok(d) => d,
|
||||
Err(e) => return NativeResult::Err(e),
|
||||
};
|
||||
NativeResult::Ok(JsValue::float(days_in_month_iso(date.year(), date.month()) as f64))
|
||||
let (y, m, d, calendar) = native_try!(plain_date_iso_ymd_calendar(vm, args));
|
||||
if calendar == "hebrew" {
|
||||
let (hy, ordinal, _) = iso_to_hebrew(y, m, d);
|
||||
return NativeResult::Ok(JsValue::float(hebrew_days_in_month(hy, ordinal) as f64));
|
||||
}
|
||||
NativeResult::Ok(JsValue::float(days_in_month_iso(y, m) as f64))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.daysInWeek` getter:恒 7。
|
||||
@@ -433,28 +592,34 @@ pub(crate) fn is_leap_year_iso(year: i32) -> bool {
|
||||
NaiveDate::from_ymd_opt(year, 2, 29).is_some()
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.daysInYear` getter:366(闰年)或 365。
|
||||
/// `Temporal.PlainDate.prototype.daysInYear` getter:hebrew 历返历法年长,其余历法 366/365。
|
||||
pub fn plain_date_days_in_year<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let date = match plain_date_naive(vm, args) {
|
||||
Ok(d) => d,
|
||||
Err(e) => return NativeResult::Err(e),
|
||||
};
|
||||
NativeResult::Ok(JsValue::float(if is_leap_year_iso(date.year()) { 366.0 } else { 365.0 }))
|
||||
let (y, m, d, calendar) = native_try!(plain_date_iso_ymd_calendar(vm, args));
|
||||
if calendar == "hebrew" {
|
||||
let (hy, _, _) = iso_to_hebrew(y, m, d);
|
||||
return NativeResult::Ok(JsValue::float(hebrew_year_length(hy) as f64));
|
||||
}
|
||||
NativeResult::Ok(JsValue::float(if is_leap_year_iso(y) { 366.0 } else { 365.0 }))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.monthsInYear` getter:恒 12(ISO 日历)。
|
||||
/// `Temporal.PlainDate.prototype.monthsInYear` getter:hebrew 历 12/13,其余历法恒 12。
|
||||
pub fn plain_date_months_in_year<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let _ = native_try!(plain_date_ymd(vm, args));
|
||||
let (y, m, d, calendar) = native_try!(plain_date_iso_ymd_calendar(vm, args));
|
||||
if calendar == "hebrew" {
|
||||
let (hy, _, _) = iso_to_hebrew(y, m, d);
|
||||
return NativeResult::Ok(JsValue::float(hebrew_months_in_year(hy) as f64));
|
||||
}
|
||||
NativeResult::Ok(JsValue::float(12.0))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.inLeapYear` getter。
|
||||
/// `Temporal.PlainDate.prototype.inLeapYear` getter:hebrew 历按历法闰年判定。
|
||||
pub fn plain_date_in_leap_year<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let date = match plain_date_naive(vm, args) {
|
||||
Ok(d) => d,
|
||||
Err(e) => return NativeResult::Err(e),
|
||||
};
|
||||
NativeResult::Ok(JsValue::bool(is_leap_year_iso(date.year())))
|
||||
let (y, m, d, calendar) = native_try!(plain_date_iso_ymd_calendar(vm, args));
|
||||
if calendar == "hebrew" {
|
||||
let (hy, _, _) = iso_to_hebrew(y, m, d);
|
||||
return NativeResult::Ok(JsValue::bool(hebrew_is_leap(hy)));
|
||||
}
|
||||
NativeResult::Ok(JsValue::bool(is_leap_year_iso(y)))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.weekOfYear` getter:ISO 周数。
|
||||
@@ -475,24 +640,33 @@ pub fn plain_date_year_of_week<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResu
|
||||
NativeResult::Ok(JsValue::float(date.iso_week().year() as f64))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.monthCode` getter:`M01`..`M12`。
|
||||
/// `Temporal.PlainDate.prototype.monthCode` getter:ISO 历 `M01`..`M12`;
|
||||
/// hebrew 历含闰月 `M05L`。
|
||||
pub fn plain_date_month_code<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let (_, m, _) = match plain_date_ymd(vm, args) {
|
||||
Ok(x) => x,
|
||||
Err(e) => return NativeResult::Err(e),
|
||||
};
|
||||
let (y, m, d, calendar) = native_try!(plain_date_iso_ymd_calendar(vm, args));
|
||||
if calendar == "hebrew" {
|
||||
let (hy, ordinal, _) = iso_to_hebrew(y, m, d);
|
||||
return NativeResult::Ok(vm.new_string_owned(hebrew_month_code(hy, ordinal)));
|
||||
}
|
||||
NativeResult::Ok(vm.new_string(&format!("M{m:02}")))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.era` getter:ISO 日历无纪元,恒 undefined。
|
||||
/// `Temporal.PlainDate.prototype.era` getter:hebrew 历恒 "am",其余历法无纪元恒 undefined。
|
||||
pub fn plain_date_era<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let _ = native_try!(plain_date_ymd(vm, args));
|
||||
let (_y, _m, _d, calendar) = native_try!(plain_date_iso_ymd_calendar(vm, args));
|
||||
if calendar == "hebrew" {
|
||||
return NativeResult::Ok(vm.new_string(HEBREW_ERA));
|
||||
}
|
||||
NativeResult::Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.eraYear` getter:ISO 日历无纪元,恒 undefined。
|
||||
/// `Temporal.PlainDate.prototype.eraYear` getter:hebrew 历返历法年,其余历法恒 undefined。
|
||||
pub fn plain_date_era_year<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let _ = native_try!(plain_date_ymd(vm, args));
|
||||
let (y, m, d, calendar) = native_try!(plain_date_iso_ymd_calendar(vm, args));
|
||||
if calendar == "hebrew" {
|
||||
let (hy, _, _) = iso_to_hebrew(y, m, d);
|
||||
return NativeResult::Ok(JsValue::float(hy as f64));
|
||||
}
|
||||
NativeResult::Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ use oxide_runtime_api::{to_string, NativeResult, ProtoKind, VmHost};
|
||||
use oxide_types::object::JsObject;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
use super::calendar::HEBREW_ERA;
|
||||
use super::common::native_try;
|
||||
use super::{
|
||||
calendar_annotation, compare_iso_date, days_from_civil, days_in_month_iso, difference_core, duration_like_values,
|
||||
@@ -351,12 +352,27 @@ pub fn plain_year_month_from<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
|
||||
let era_raw = native_try!(temporal_option_value(vm, obj, value, "era"));
|
||||
let era_year_raw = native_try!(temporal_option_value(vm, obj, value, "eraYear"));
|
||||
let constrain = native_try!(temporal_overflow(vm, args));
|
||||
// era 族:双现 → RangeError;恰一个 → 忽略(ISO 无纪元体系)。
|
||||
if !era_raw.is_undefined() && !era_year_raw.is_undefined() {
|
||||
// era 族:hebrew(有纪元)恰一个现 → TypeError、双现校验 era 为 "am";
|
||||
// 其余历法沿用既有处理(双现 RangeError、恰一个忽略)。
|
||||
let era_present = !era_raw.is_undefined();
|
||||
let era_year_present = !era_year_raw.is_undefined();
|
||||
if calendar == "hebrew" {
|
||||
if era_present != era_year_present {
|
||||
return NativeResult::Err(crate::error::create_type_error(
|
||||
vm, "era and eraYear must both be present or both absent",
|
||||
));
|
||||
}
|
||||
if era_present {
|
||||
let era = native_try!(temporal_option_string(vm, era_raw));
|
||||
if era != HEBREW_ERA {
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "invalid era"));
|
||||
}
|
||||
}
|
||||
} else if era_present && era_year_present {
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "era and eraYear cannot both be present"));
|
||||
}
|
||||
// 存在性:year 必填(先于 monthCode 语法);month|monthCode 至少其一。
|
||||
if year_raw.is_undefined() {
|
||||
// 存在性:year 必填(hebrew 由 eraYear 定年时豁免);month|monthCode 至少其一。
|
||||
if year_raw.is_undefined() && !(calendar == "hebrew" && era_year_present) {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "year is required"));
|
||||
}
|
||||
if month_raw.is_undefined() && month_code_raw.is_undefined() {
|
||||
@@ -397,12 +413,20 @@ pub fn plain_year_month_from<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
|
||||
}
|
||||
None => (None, false),
|
||||
};
|
||||
// year 转换(TypeError/RangeError)在 monthCode 语法之后。
|
||||
let year_raw = native_try!(temporal_number_component(vm, year_raw));
|
||||
let year = if year_raw >= i32::MIN as f64 && year_raw <= i32::MAX as f64 {
|
||||
year_raw as i32
|
||||
// year 转换(TypeError/RangeError)在 monthCode 语法之后;hebrew 由 eraYear 定年。
|
||||
let year = if calendar == "hebrew" && era_year_present {
|
||||
let era_year = native_try!(temporal_number_component(vm, era_year_raw));
|
||||
if era_year < i32::MIN as f64 || era_year > i32::MAX as f64 {
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "invalid eraYear"));
|
||||
}
|
||||
era_year as i32
|
||||
} else {
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "invalid year"));
|
||||
let year_raw = native_try!(temporal_number_component(vm, year_raw));
|
||||
if year_raw >= i32::MIN as f64 && year_raw <= i32::MAX as f64 {
|
||||
year_raw as i32
|
||||
} else {
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "invalid year"));
|
||||
}
|
||||
};
|
||||
let month_f = match month_raw.is_undefined() {
|
||||
true => None,
|
||||
|
||||
Reference in New Issue
Block a user