1use crate::error::year_check;
20use crate::{
21 cal::iso::IsoDateInner, calendar_arithmetic::ArithmeticDate, error::DateError, types, Calendar,
22 Date, Iso, RangeError,
23};
24use calendrical_calculations::rata_die::RataDie;
25use tinystr::tinystr;
26
27const ROC_ERA_OFFSET: i32 = 1911;
30
31#[derive(Copy, Clone, Debug, Default)]
50#[allow(clippy::exhaustive_structs)] pub struct Roc;
52
53#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
55pub struct RocDateInner(IsoDateInner);
56
57impl crate::cal::scaffold::UnstableSealed for Roc {}
58impl Calendar for Roc {
59 type DateInner = RocDateInner;
60 type Year = types::EraYear;
61
62 fn from_codes(
63 &self,
64 era: Option<&str>,
65 year: i32,
66 month_code: crate::types::MonthCode,
67 day: u8,
68 ) -> Result<Self::DateInner, DateError> {
69 let year = match era {
70 Some("roc") | None => ROC_ERA_OFFSET + year_check(year, 1..)?,
71 Some("broc") => ROC_ERA_OFFSET + 1 - year_check(year, 1..)?,
72 Some(_) => return Err(DateError::UnknownEra),
73 };
74
75 ArithmeticDate::new_from_codes(self, year, month_code, day)
76 .map(IsoDateInner)
77 .map(RocDateInner)
78 }
79
80 fn from_rata_die(&self, rd: RataDie) -> Self::DateInner {
81 RocDateInner(Iso.from_rata_die(rd))
82 }
83
84 fn to_rata_die(&self, date: &Self::DateInner) -> RataDie {
85 Iso.to_rata_die(&date.0)
86 }
87
88 fn from_iso(&self, iso: IsoDateInner) -> Self::DateInner {
89 RocDateInner(iso)
90 }
91
92 fn to_iso(&self, date: &Self::DateInner) -> IsoDateInner {
93 date.0
94 }
95
96 fn months_in_year(&self, date: &Self::DateInner) -> u8 {
97 Iso.months_in_year(&date.0)
98 }
99
100 fn days_in_year(&self, date: &Self::DateInner) -> u16 {
101 Iso.days_in_year(&date.0)
102 }
103
104 fn days_in_month(&self, date: &Self::DateInner) -> u8 {
105 Iso.days_in_month(&date.0)
106 }
107
108 fn offset_date(&self, date: &mut Self::DateInner, offset: crate::DateDuration<Self>) {
109 Iso.offset_date(&mut date.0, offset.cast_unit())
110 }
111
112 fn until(
113 &self,
114 date1: &Self::DateInner,
115 date2: &Self::DateInner,
116 _calendar2: &Self,
117 largest_unit: crate::DateDurationUnit,
118 smallest_unit: crate::DateDurationUnit,
119 ) -> crate::DateDuration<Self> {
120 Iso.until(&date1.0, &date2.0, &Iso, largest_unit, smallest_unit)
121 .cast_unit()
122 }
123
124 fn debug_name(&self) -> &'static str {
125 "ROC"
126 }
127
128 fn year_info(&self, date: &Self::DateInner) -> Self::Year {
129 let extended_year = self.extended_year(date);
130 if extended_year > ROC_ERA_OFFSET {
131 types::EraYear {
132 era: tinystr!(16, "roc"),
133 era_index: Some(1),
134 year: extended_year.saturating_sub(ROC_ERA_OFFSET),
135 ambiguity: types::YearAmbiguity::CenturyRequired,
136 }
137 } else {
138 types::EraYear {
139 era: tinystr!(16, "broc"),
140 era_index: Some(0),
141 year: (ROC_ERA_OFFSET + 1).saturating_sub(extended_year),
142 ambiguity: types::YearAmbiguity::EraAndCenturyRequired,
143 }
144 }
145 }
146
147 fn extended_year(&self, date: &Self::DateInner) -> i32 {
148 Iso.extended_year(&date.0)
149 }
150
151 fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
152 Iso.is_in_leap_year(&date.0)
153 }
154
155 fn month(&self, date: &Self::DateInner) -> crate::types::MonthInfo {
156 Iso.month(&date.0)
157 }
158
159 fn day_of_month(&self, date: &Self::DateInner) -> crate::types::DayOfMonth {
160 Iso.day_of_month(&date.0)
161 }
162
163 fn day_of_year(&self, date: &Self::DateInner) -> types::DayOfYear {
164 Iso.day_of_year(&date.0)
165 }
166
167 fn calendar_algorithm(&self) -> Option<crate::preferences::CalendarAlgorithm> {
168 Some(crate::preferences::CalendarAlgorithm::Roc)
169 }
170}
171
172impl Date<Roc> {
173 pub fn try_new_roc(year: i32, month: u8, day: u8) -> Result<Date<Roc>, RangeError> {
200 let iso_year = year.saturating_add(ROC_ERA_OFFSET);
201 Date::try_new_iso(iso_year, month, day).map(|d| Date::new_from_iso(d, Roc))
202 }
203}
204
205#[cfg(test)]
206mod test {
207
208 use super::*;
209 use calendrical_calculations::rata_die::RataDie;
210
211 #[derive(Debug)]
212 struct TestCase {
213 rd: RataDie,
214 iso_year: i32,
215 iso_month: u8,
216 iso_day: u8,
217 expected_year: i32,
218 expected_era: &'static str,
219 expected_month: u8,
220 expected_day: u8,
221 }
222
223 fn check_test_case(case: TestCase) {
224 let iso_from_rd = Date::from_rata_die(case.rd, Iso);
225 let roc_from_rd = Date::from_rata_die(case.rd, Roc);
226 assert_eq!(
227 roc_from_rd.era_year().year,
228 case.expected_year,
229 "Failed year check from RD: {case:?}\nISO: {iso_from_rd:?}\nROC: {roc_from_rd:?}"
230 );
231 assert_eq!(
232 roc_from_rd.era_year().era,
233 case.expected_era,
234 "Failed era check from RD: {case:?}\nISO: {iso_from_rd:?}\nROC: {roc_from_rd:?}"
235 );
236 assert_eq!(
237 roc_from_rd.month().ordinal,
238 case.expected_month,
239 "Failed month check from RD: {case:?}\nISO: {iso_from_rd:?}\nROC: {roc_from_rd:?}"
240 );
241 assert_eq!(roc_from_rd.day_of_month().0, case.expected_day,
242 "Failed day_of_month check from RD: {case:?}\nISO: {iso_from_rd:?}\nROC: {roc_from_rd:?}");
243
244 let iso_from_case = Date::try_new_iso(case.iso_year, case.iso_month, case.iso_day)
245 .expect("Failed to initialize ISO date for {case:?}");
246 let roc_from_case = Date::new_from_iso(iso_from_case, Roc);
247 assert_eq!(iso_from_rd, iso_from_case,
248 "ISO from RD not equal to ISO generated from manually-input ymd\nCase: {case:?}\nRD: {iso_from_rd:?}\nManual: {iso_from_case:?}");
249 assert_eq!(roc_from_rd, roc_from_case,
250 "ROC date from RD not equal to ROC generated from manually-input ymd\nCase: {case:?}\nRD: {roc_from_rd:?}\nManual: {roc_from_case:?}");
251 }
252
253 #[test]
254 fn test_roc_current_era() {
255 let cases = [
261 TestCase {
262 rd: RataDie::new(697978),
263 iso_year: 1912,
264 iso_month: 1,
265 iso_day: 1,
266 expected_year: 1,
267 expected_era: "roc",
268 expected_month: 1,
269 expected_day: 1,
270 },
271 TestCase {
272 rd: RataDie::new(698037),
273 iso_year: 1912,
274 iso_month: 2,
275 iso_day: 29,
276 expected_year: 1,
277 expected_era: "roc",
278 expected_month: 2,
279 expected_day: 29,
280 },
281 TestCase {
282 rd: RataDie::new(698524),
283 iso_year: 1913,
284 iso_month: 6,
285 iso_day: 30,
286 expected_year: 2,
287 expected_era: "roc",
288 expected_month: 6,
289 expected_day: 30,
290 },
291 TestCase {
292 rd: RataDie::new(738714),
293 iso_year: 2023,
294 iso_month: 7,
295 iso_day: 13,
296 expected_year: 112,
297 expected_era: "roc",
298 expected_month: 7,
299 expected_day: 13,
300 },
301 ];
302
303 for case in cases {
304 check_test_case(case);
305 }
306 }
307
308 #[test]
309 fn test_roc_prior_era() {
310 let cases = [
315 TestCase {
316 rd: RataDie::new(697977),
317 iso_year: 1911,
318 iso_month: 12,
319 iso_day: 31,
320 expected_year: 1,
321 expected_era: "broc",
322 expected_month: 12,
323 expected_day: 31,
324 },
325 TestCase {
326 rd: RataDie::new(697613),
327 iso_year: 1911,
328 iso_month: 1,
329 iso_day: 1,
330 expected_year: 1,
331 expected_era: "broc",
332 expected_month: 1,
333 expected_day: 1,
334 },
335 TestCase {
336 rd: RataDie::new(697612),
337 iso_year: 1910,
338 iso_month: 12,
339 iso_day: 31,
340 expected_year: 2,
341 expected_era: "broc",
342 expected_month: 12,
343 expected_day: 31,
344 },
345 TestCase {
346 rd: RataDie::new(696576),
347 iso_year: 1908,
348 iso_month: 2,
349 iso_day: 29,
350 expected_year: 4,
351 expected_era: "broc",
352 expected_month: 2,
353 expected_day: 29,
354 },
355 TestCase {
356 rd: RataDie::new(1),
357 iso_year: 1,
358 iso_month: 1,
359 iso_day: 1,
360 expected_year: 1911,
361 expected_era: "broc",
362 expected_month: 1,
363 expected_day: 1,
364 },
365 TestCase {
366 rd: RataDie::new(0),
367 iso_year: 0,
368 iso_month: 12,
369 iso_day: 31,
370 expected_year: 1912,
371 expected_era: "broc",
372 expected_month: 12,
373 expected_day: 31,
374 },
375 ];
376
377 for case in cases {
378 check_test_case(case);
379 }
380 }
381
382 #[test]
383 fn test_roc_directionality_near_epoch() {
384 let rd_epoch_start = 697978;
388 for i in (rd_epoch_start - 100)..=(rd_epoch_start + 100) {
389 for j in (rd_epoch_start - 100)..=(rd_epoch_start + 100) {
390 let iso_i = Date::from_rata_die(RataDie::new(i), Iso);
391 let iso_j = Date::from_rata_die(RataDie::new(j), Iso);
392
393 let roc_i = Date::from_rata_die(RataDie::new(i), Roc);
394 let roc_j = Date::from_rata_die(RataDie::new(j), Roc);
395
396 assert_eq!(
397 i.cmp(&j),
398 iso_i.cmp(&iso_j),
399 "ISO directionality inconsistent with directionality for i: {i}, j: {j}"
400 );
401 assert_eq!(
402 i.cmp(&j),
403 roc_i.cmp(&roc_j),
404 "ROC directionality inconsistent with directionality for i: {i}, j: {j}"
405 );
406 }
407 }
408 }
409
410 #[test]
411 fn test_roc_directionality_near_rd_zero() {
412 for i in -100..=100 {
414 for j in -100..100 {
415 let iso_i = Date::from_rata_die(RataDie::new(i), Iso);
416 let iso_j = Date::from_rata_die(RataDie::new(j), Iso);
417
418 let roc_i = Date::from_rata_die(RataDie::new(i), Roc);
419 let roc_j = Date::from_rata_die(RataDie::new(j), Roc);
420
421 assert_eq!(
422 i.cmp(&j),
423 iso_i.cmp(&iso_j),
424 "ISO directionality inconsistent with directionality for i: {i}, j: {j}"
425 );
426 assert_eq!(
427 i.cmp(&j),
428 roc_i.cmp(&roc_j),
429 "ROC directionality inconsistent with directionality for i: {i}, j: {j}"
430 );
431 }
432 }
433 }
434}