1use crate::{
35 calendar_arithmetic::ArithmeticDate, iso::IsoDateInner, types, AnyCalendarKind, Calendar,
36 CalendarError, Date, DateTime, Iso, Time,
37};
38use calendrical_calculations::helpers::i64_to_saturated_i32;
39use tinystr::tinystr;
40
41const ROC_ERA_OFFSET: i32 = 1911;
44
45#[derive(Copy, Clone, Debug, Default)]
69#[allow(clippy::exhaustive_structs)] pub struct Roc;
71
72#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
74pub struct RocDateInner(IsoDateInner);
75
76impl Calendar for Roc {
77 type DateInner = RocDateInner;
78
79 fn date_from_codes(
80 &self,
81 era: crate::types::Era,
82 year: i32,
83 month_code: crate::types::MonthCode,
84 day: u8,
85 ) -> Result<Self::DateInner, crate::Error> {
86 let year = if era.0 == tinystr!(16, "roc") {
87 if year <= 0 {
88 return Err(CalendarError::OutOfRange);
89 }
90 year + ROC_ERA_OFFSET
91 } else if era.0 == tinystr!(16, "roc-inverse") {
92 if year <= 0 {
93 return Err(CalendarError::OutOfRange);
94 }
95 1 - year + ROC_ERA_OFFSET
96 } else {
97 return Err(CalendarError::UnknownEra(era.0, self.debug_name()));
98 };
99
100 ArithmeticDate::new_from_codes(self, year, month_code, day)
101 .map(IsoDateInner)
102 .map(RocDateInner)
103 }
104
105 fn date_from_iso(&self, iso: crate::Date<crate::Iso>) -> Self::DateInner {
106 RocDateInner(*iso.inner())
107 }
108
109 fn date_to_iso(&self, date: &Self::DateInner) -> crate::Date<crate::Iso> {
110 Date::from_raw(date.0, Iso)
111 }
112
113 fn months_in_year(&self, date: &Self::DateInner) -> u8 {
114 Iso.months_in_year(&date.0)
115 }
116
117 fn days_in_year(&self, date: &Self::DateInner) -> u16 {
118 Iso.days_in_year(&date.0)
119 }
120
121 fn days_in_month(&self, date: &Self::DateInner) -> u8 {
122 Iso.days_in_month(&date.0)
123 }
124
125 fn offset_date(&self, date: &mut Self::DateInner, offset: crate::DateDuration<Self>) {
126 Iso.offset_date(&mut date.0, offset.cast_unit())
127 }
128
129 fn until(
130 &self,
131 date1: &Self::DateInner,
132 date2: &Self::DateInner,
133 _calendar2: &Self,
134 largest_unit: crate::DateDurationUnit,
135 smallest_unit: crate::DateDurationUnit,
136 ) -> crate::DateDuration<Self> {
137 Iso.until(&date1.0, &date2.0, &Iso, largest_unit, smallest_unit)
138 .cast_unit()
139 }
140
141 fn debug_name(&self) -> &'static str {
142 "ROC"
143 }
144
145 fn year(&self, date: &Self::DateInner) -> crate::types::FormattableYear {
146 year_as_roc(date.0 .0.year as i64)
147 }
148
149 fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
150 Iso.is_in_leap_year(&date.0)
151 }
152
153 fn month(&self, date: &Self::DateInner) -> crate::types::FormattableMonth {
154 Iso.month(&date.0)
155 }
156
157 fn day_of_month(&self, date: &Self::DateInner) -> crate::types::DayOfMonth {
158 Iso.day_of_month(&date.0)
159 }
160
161 fn day_of_year_info(&self, date: &Self::DateInner) -> crate::types::DayOfYearInfo {
162 let prev_year = date.0 .0.year.saturating_sub(1);
163 let next_year = date.0 .0.year.saturating_add(1);
164 types::DayOfYearInfo {
165 day_of_year: Iso::day_of_year(date.0),
166 days_in_year: Iso::days_in_year_direct(date.0 .0.year),
167 prev_year: year_as_roc(prev_year as i64),
168 days_in_prev_year: Iso::days_in_year_direct(prev_year),
169 next_year: year_as_roc(next_year as i64),
170 }
171 }
172
173 fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
175 Some(AnyCalendarKind::Roc)
176 }
177}
178
179impl Date<Roc> {
180 pub fn try_new_roc_date(year: i32, month: u8, day: u8) -> Result<Date<Roc>, CalendarError> {
207 let iso_year = year.saturating_add(ROC_ERA_OFFSET);
208 Date::try_new_iso_date(iso_year, month, day).map(|d| Date::new_from_iso(d, Roc))
209 }
210}
211
212impl DateTime<Roc> {
213 pub fn try_new_roc_datetime(
242 year: i32,
243 month: u8,
244 day: u8,
245 hour: u8,
246 minute: u8,
247 second: u8,
248 ) -> Result<DateTime<Roc>, CalendarError> {
249 Ok(DateTime {
250 date: Date::try_new_roc_date(year, month, day)?,
251 time: Time::try_new(hour, minute, second, 0)?,
252 })
253 }
254}
255
256pub(crate) fn year_as_roc(year: i64) -> types::FormattableYear {
257 let year_i32 = i64_to_saturated_i32(year);
258 let offset_i64 = ROC_ERA_OFFSET as i64;
259 if year > offset_i64 {
260 types::FormattableYear {
261 era: types::Era(tinystr!(16, "roc")),
262 number: year_i32.saturating_sub(ROC_ERA_OFFSET),
263 cyclic: None,
264 related_iso: Some(year_i32),
265 }
266 } else {
267 types::FormattableYear {
268 era: types::Era(tinystr!(16, "roc-inverse")),
269 number: (ROC_ERA_OFFSET + 1).saturating_sub(year_i32),
270 cyclic: None,
271 related_iso: Some(year_i32),
272 }
273 }
274}
275
276#[cfg(test)]
277mod test {
278
279 use super::*;
280 use crate::types::Era;
281 use calendrical_calculations::rata_die::RataDie;
282
283 #[derive(Debug)]
284 struct TestCase {
285 fixed_date: RataDie,
286 iso_year: i32,
287 iso_month: u8,
288 iso_day: u8,
289 expected_year: i32,
290 expected_era: Era,
291 expected_month: u32,
292 expected_day: u32,
293 }
294
295 fn check_test_case(case: TestCase) {
296 let iso_from_fixed = Iso::iso_from_fixed(case.fixed_date);
297 let roc_from_fixed = Date::new_from_iso(iso_from_fixed, Roc);
298 assert_eq!(roc_from_fixed.year().number, case.expected_year,
299 "Failed year check from fixed: {case:?}\nISO: {iso_from_fixed:?}\nROC: {roc_from_fixed:?}");
300 assert_eq!(roc_from_fixed.year().era, case.expected_era,
301 "Failed era check from fixed: {case:?}\nISO: {iso_from_fixed:?}\nROC: {roc_from_fixed:?}");
302 assert_eq!(roc_from_fixed.month().ordinal, case.expected_month,
303 "Failed month check from fixed: {case:?}\nISO: {iso_from_fixed:?}\nROC: {roc_from_fixed:?}");
304 assert_eq!(roc_from_fixed.day_of_month().0, case.expected_day,
305 "Failed day_of_month check from fixed: {case:?}\nISO: {iso_from_fixed:?}\nROC: {roc_from_fixed:?}");
306
307 let iso_from_case = Date::try_new_iso_date(case.iso_year, case.iso_month, case.iso_day)
308 .expect("Failed to initialize ISO date for {case:?}");
309 let roc_from_case = Date::new_from_iso(iso_from_case, Roc);
310 assert_eq!(iso_from_fixed, iso_from_case,
311 "ISO from fixed not equal to ISO generated from manually-input ymd\nCase: {case:?}\nFixed: {iso_from_fixed:?}\nManual: {iso_from_case:?}");
312 assert_eq!(roc_from_fixed, roc_from_case,
313 "ROC date from fixed not equal to ROC generated from manually-input ymd\nCase: {case:?}\nFixed: {roc_from_fixed:?}\nManual: {roc_from_case:?}");
314 }
315
316 #[test]
317 fn test_roc_current_era() {
318 let cases = [
324 TestCase {
325 fixed_date: RataDie::new(697978),
326 iso_year: 1912,
327 iso_month: 1,
328 iso_day: 1,
329 expected_year: 1,
330 expected_era: Era(tinystr!(16, "roc")),
331 expected_month: 1,
332 expected_day: 1,
333 },
334 TestCase {
335 fixed_date: RataDie::new(698037),
336 iso_year: 1912,
337 iso_month: 2,
338 iso_day: 29,
339 expected_year: 1,
340 expected_era: Era(tinystr!(16, "roc")),
341 expected_month: 2,
342 expected_day: 29,
343 },
344 TestCase {
345 fixed_date: RataDie::new(698524),
346 iso_year: 1913,
347 iso_month: 6,
348 iso_day: 30,
349 expected_year: 2,
350 expected_era: Era(tinystr!(16, "roc")),
351 expected_month: 6,
352 expected_day: 30,
353 },
354 TestCase {
355 fixed_date: RataDie::new(738714),
356 iso_year: 2023,
357 iso_month: 7,
358 iso_day: 13,
359 expected_year: 112,
360 expected_era: Era(tinystr!(16, "roc")),
361 expected_month: 7,
362 expected_day: 13,
363 },
364 ];
365
366 for case in cases {
367 check_test_case(case);
368 }
369 }
370
371 #[test]
372 fn test_roc_prior_era() {
373 let cases = [
378 TestCase {
379 fixed_date: RataDie::new(697977),
380 iso_year: 1911,
381 iso_month: 12,
382 iso_day: 31,
383 expected_year: 1,
384 expected_era: Era(tinystr!(16, "roc-inverse")),
385 expected_month: 12,
386 expected_day: 31,
387 },
388 TestCase {
389 fixed_date: RataDie::new(697613),
390 iso_year: 1911,
391 iso_month: 1,
392 iso_day: 1,
393 expected_year: 1,
394 expected_era: Era(tinystr!(16, "roc-inverse")),
395 expected_month: 1,
396 expected_day: 1,
397 },
398 TestCase {
399 fixed_date: RataDie::new(697612),
400 iso_year: 1910,
401 iso_month: 12,
402 iso_day: 31,
403 expected_year: 2,
404 expected_era: Era(tinystr!(16, "roc-inverse")),
405 expected_month: 12,
406 expected_day: 31,
407 },
408 TestCase {
409 fixed_date: RataDie::new(696576),
410 iso_year: 1908,
411 iso_month: 2,
412 iso_day: 29,
413 expected_year: 4,
414 expected_era: Era(tinystr!(16, "roc-inverse")),
415 expected_month: 2,
416 expected_day: 29,
417 },
418 TestCase {
419 fixed_date: RataDie::new(1),
420 iso_year: 1,
421 iso_month: 1,
422 iso_day: 1,
423 expected_year: 1911,
424 expected_era: Era(tinystr!(16, "roc-inverse")),
425 expected_month: 1,
426 expected_day: 1,
427 },
428 TestCase {
429 fixed_date: RataDie::new(0),
430 iso_year: 0,
431 iso_month: 12,
432 iso_day: 31,
433 expected_year: 1912,
434 expected_era: Era(tinystr!(16, "roc-inverse")),
435 expected_month: 12,
436 expected_day: 31,
437 },
438 ];
439
440 for case in cases {
441 check_test_case(case);
442 }
443 }
444
445 #[test]
446 fn test_roc_directionality_near_epoch() {
447 let rd_epoch_start = 697978;
451 for i in (rd_epoch_start - 100)..=(rd_epoch_start + 100) {
452 for j in (rd_epoch_start - 100)..=(rd_epoch_start + 100) {
453 let iso_i = Iso::iso_from_fixed(RataDie::new(i));
454 let iso_j = Iso::iso_from_fixed(RataDie::new(j));
455
456 let roc_i = iso_i.to_calendar(Roc);
457 let roc_j = iso_j.to_calendar(Roc);
458
459 assert_eq!(
460 i.cmp(&j),
461 iso_i.cmp(&iso_j),
462 "ISO directionality inconsistent with directionality for i: {i}, j: {j}"
463 );
464 assert_eq!(
465 i.cmp(&j),
466 roc_i.cmp(&roc_j),
467 "ROC directionality inconsistent with directionality for i: {i}, j: {j}"
468 );
469 }
470 }
471 }
472
473 #[test]
474 fn test_roc_directionality_near_rd_zero() {
475 for i in -100..=100 {
477 for j in -100..100 {
478 let iso_i = Iso::iso_from_fixed(RataDie::new(i));
479 let iso_j = Iso::iso_from_fixed(RataDie::new(j));
480
481 let roc_i = iso_i.to_calendar(Roc);
482 let roc_j = iso_j.to_calendar(Roc);
483
484 assert_eq!(
485 i.cmp(&j),
486 iso_i.cmp(&iso_j),
487 "ISO directionality inconsistent with directionality for i: {i}, j: {j}"
488 );
489 assert_eq!(
490 i.cmp(&j),
491 roc_i.cmp(&roc_j),
492 "ROC directionality inconsistent with directionality for i: {i}, j: {j}"
493 );
494 }
495 }
496 }
497}