icu_calendar/cal/east_asian_traditional/
simple.rs1use super::EastAsianTraditionalYearData;
6use calendrical_calculations::{gregorian::DAYS_IN_400_YEAR_CYCLE, rata_die::RataDie};
7
8macro_rules! day_fraction_to_ms {
9 ($n:tt $(/ $d:tt)+) => {{
10 Milliseconds((MILLISECONDS_IN_EPHEMERIS_DAY as i128 * $n as i128 $( / $d as i128)+) as i64)
11 }};
12 ($n:tt $(/ $d:tt)+, exact) => {{
13 let d = day_fraction_to_ms!($n $(/ $d)+);
14 assert!((d.0 as i128 $(* $d as i128)+) % MILLISECONDS_IN_EPHEMERIS_DAY as i128 == 0, "inexact");
15 d
16 }};
17}
18
19pub(super) const UTC_PLUS_8: Milliseconds = day_fraction_to_ms!(8 / 24);
20pub(super) const UTC_PLUS_9: Milliseconds = day_fraction_to_ms!(9 / 24);
21pub(super) const BEIJING_UTC_OFFSET: Milliseconds = day_fraction_to_ms!(1397 / 180 / 24);
23
24const MEAN_GREGORIAN_YEAR_LENGTH: Milliseconds =
26 day_fraction_to_ms!(DAYS_IN_400_YEAR_CYCLE / 400, exact);
27
28const MEAN_GREGORIAN_SOLAR_TERM_LENGTH: Milliseconds =
30 day_fraction_to_ms!(DAYS_IN_400_YEAR_CYCLE / 400 / 12, exact);
31
32const MEAN_SYNODIC_MONTH_LENGTH: Milliseconds = day_fraction_to_ms!(295305888531 / 10000000000i64);
36
37const MILLISECONDS_IN_EPHEMERIS_DAY: i64 = 24 * 60 * 60 * 1000;
39
40const UTC_SOLSTICE: LocalMoment = LocalMoment {
42 rata_die: calendrical_calculations::gregorian::fixed_from_gregorian(1999, 12, 22),
43 local_milliseconds: ((7 * 60) + 44) * 60 * 1000,
44};
45
46const UTC_NEW_MOON: LocalMoment = LocalMoment {
48 rata_die: calendrical_calculations::gregorian::fixed_from_gregorian(2000, 1, 6),
49 local_milliseconds: ((18 * 60) + 14) * 60 * 1000,
50};
51
52#[derive(Debug, Copy, Clone, Default)]
53pub(super) struct Milliseconds(i64);
54
55#[derive(Debug, Copy, Clone)]
56struct LocalMoment {
57 rata_die: RataDie,
58 local_milliseconds: u32,
59}
60
61impl core::ops::Add<Milliseconds> for LocalMoment {
62 type Output = Self;
63
64 fn add(self, Milliseconds(duration): Milliseconds) -> Self::Output {
65 let temp = self.local_milliseconds as i64 + duration;
66 Self {
67 rata_die: self.rata_die + temp.div_euclid(MILLISECONDS_IN_EPHEMERIS_DAY),
68 local_milliseconds: temp.rem_euclid(MILLISECONDS_IN_EPHEMERIS_DAY) as u32,
69 }
70 }
71}
72
73impl super::EastAsianTraditionalYearData {
74 pub(super) fn simple(
80 utc_offset: Milliseconds,
81 related_iso: i32,
82 ) -> EastAsianTraditionalYearData {
83 fn periodic_duration_on_or_before(
85 rata_die: RataDie,
86 base_moment: LocalMoment,
87 duration: Milliseconds,
88 ) -> LocalMoment {
89 let diff_millis = (rata_die - base_moment.rata_die) * MILLISECONDS_IN_EPHEMERIS_DAY
90 - base_moment.local_milliseconds as i64;
91
92 let num_periods =
93 (diff_millis + MILLISECONDS_IN_EPHEMERIS_DAY - 1).div_euclid(duration.0);
94
95 let millis = base_moment.rata_die.to_i64_date() * MILLISECONDS_IN_EPHEMERIS_DAY
96 + base_moment.local_milliseconds as i64
97 + num_periods * duration.0;
98
99 let rata_die = millis / MILLISECONDS_IN_EPHEMERIS_DAY - (millis < 0) as i64;
102 let local_milliseconds = millis % MILLISECONDS_IN_EPHEMERIS_DAY
103 + (millis < 0) as i64 * MILLISECONDS_IN_EPHEMERIS_DAY;
104
105 LocalMoment {
106 rata_die: RataDie::new(rata_die),
107 local_milliseconds: local_milliseconds as u32,
108 }
109 }
110
111 let mut major_solar_term = periodic_duration_on_or_before(
112 calendrical_calculations::iso::day_before_year(related_iso),
113 UTC_SOLSTICE + utc_offset,
114 MEAN_GREGORIAN_YEAR_LENGTH,
115 );
116
117 let mut new_moon = periodic_duration_on_or_before(
118 major_solar_term.rata_die,
119 UTC_NEW_MOON + utc_offset,
120 MEAN_SYNODIC_MONTH_LENGTH,
121 );
122
123 let mut next_new_moon = new_moon + MEAN_SYNODIC_MONTH_LENGTH;
124
125 let mut solar_term = -2;
127 let mut had_leap_in_sui = false;
128
129 while solar_term < 0
131 || (next_new_moon.rata_die <= major_solar_term.rata_die && !had_leap_in_sui)
132 {
133 if next_new_moon.rata_die <= major_solar_term.rata_die && !had_leap_in_sui {
134 had_leap_in_sui = true;
135 } else {
136 solar_term += 1;
137 major_solar_term = major_solar_term + MEAN_GREGORIAN_SOLAR_TERM_LENGTH;
138 }
139
140 (new_moon, next_new_moon) = (next_new_moon, next_new_moon + MEAN_SYNODIC_MONTH_LENGTH);
141 }
142
143 debug_assert_eq!(solar_term, 0);
144
145 let start_day = new_moon.rata_die;
146 let mut month_lengths = [false; 13];
147 let mut leap_month = None;
148
149 while solar_term < 12
151 || (next_new_moon.rata_die <= major_solar_term.rata_die && !had_leap_in_sui)
152 {
153 *month_lengths
154 .get_mut(solar_term as usize + leap_month.is_some() as usize)
155 .unwrap_or(&mut false) = next_new_moon.rata_die - new_moon.rata_die == 30;
156
157 if next_new_moon.rata_die <= major_solar_term.rata_die && !had_leap_in_sui {
158 had_leap_in_sui = true;
159 leap_month = Some(solar_term as u8 + 1);
160 } else {
161 solar_term += 1;
162 major_solar_term = major_solar_term + MEAN_GREGORIAN_SOLAR_TERM_LENGTH;
163 }
164
165 (new_moon, next_new_moon) = (next_new_moon, next_new_moon + MEAN_SYNODIC_MONTH_LENGTH);
166 }
167
168 debug_assert_eq!(solar_term, 12);
169
170 EastAsianTraditionalYearData::new(related_iso, start_day, month_lengths, leap_month)
171 }
172}
173
174#[test]
175fn bounds() {
176 EastAsianTraditionalYearData::simple(UTC_PLUS_9, 292_277_025);
177 assert!(
178 std::panic::catch_unwind(|| EastAsianTraditionalYearData::simple(UTC_PLUS_9, 292_277_026))
179 .is_err()
180 );
181
182 EastAsianTraditionalYearData::simple(BEIJING_UTC_OFFSET, -292_275_024);
183 assert!(
184 std::panic::catch_unwind(|| EastAsianTraditionalYearData::simple(
185 BEIJING_UTC_OFFSET,
186 -292_275_025
187 ))
188 .is_err()
189 );
190}