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 > 0 {
131 types::EraYear {
132 era: tinystr!(16, "roc"),
133 era_index: Some(1),
134 year: extended_year,
135 ambiguity: types::YearAmbiguity::CenturyRequired,
136 }
137 } else {
138 types::EraYear {
139 era: tinystr!(16, "broc"),
140 era_index: Some(0),
141 year: 1 - 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) - ROC_ERA_OFFSET
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.extended_year(),
238 if case.expected_era == "roc" {
239 case.expected_year
240 } else {
241 1 - case.expected_year
242 },
243 "Failed year check from RD: {case:?}\nISO: {iso_from_rd:?}\nROC: {roc_from_rd:?}"
244 );
245 assert_eq!(
246 roc_from_rd.month().ordinal,
247 case.expected_month,
248 "Failed month check from RD: {case:?}\nISO: {iso_from_rd:?}\nROC: {roc_from_rd:?}"
249 );
250 assert_eq!(roc_from_rd.day_of_month().0, case.expected_day,
251 "Failed day_of_month check from RD: {case:?}\nISO: {iso_from_rd:?}\nROC: {roc_from_rd:?}");
252
253 let iso_from_case = Date::try_new_iso(case.iso_year, case.iso_month, case.iso_day)
254 .expect("Failed to initialize ISO date for {case:?}");
255 let roc_from_case = Date::new_from_iso(iso_from_case, Roc);
256 assert_eq!(iso_from_rd, iso_from_case,
257 "ISO from RD not equal to ISO generated from manually-input ymd\nCase: {case:?}\nRD: {iso_from_rd:?}\nManual: {iso_from_case:?}");
258 assert_eq!(roc_from_rd, roc_from_case,
259 "ROC date from RD not equal to ROC generated from manually-input ymd\nCase: {case:?}\nRD: {roc_from_rd:?}\nManual: {roc_from_case:?}");
260 }
261
262 #[test]
263 fn test_roc_current_era() {
264 let cases = [
270 TestCase {
271 rd: RataDie::new(697978),
272 iso_year: 1912,
273 iso_month: 1,
274 iso_day: 1,
275 expected_year: 1,
276 expected_era: "roc",
277 expected_month: 1,
278 expected_day: 1,
279 },
280 TestCase {
281 rd: RataDie::new(698037),
282 iso_year: 1912,
283 iso_month: 2,
284 iso_day: 29,
285 expected_year: 1,
286 expected_era: "roc",
287 expected_month: 2,
288 expected_day: 29,
289 },
290 TestCase {
291 rd: RataDie::new(698524),
292 iso_year: 1913,
293 iso_month: 6,
294 iso_day: 30,
295 expected_year: 2,
296 expected_era: "roc",
297 expected_month: 6,
298 expected_day: 30,
299 },
300 TestCase {
301 rd: RataDie::new(738714),
302 iso_year: 2023,
303 iso_month: 7,
304 iso_day: 13,
305 expected_year: 112,
306 expected_era: "roc",
307 expected_month: 7,
308 expected_day: 13,
309 },
310 ];
311
312 for case in cases {
313 check_test_case(case);
314 }
315 }
316
317 #[test]
318 fn test_roc_prior_era() {
319 let cases = [
324 TestCase {
325 rd: RataDie::new(697977),
326 iso_year: 1911,
327 iso_month: 12,
328 iso_day: 31,
329 expected_year: 1,
330 expected_era: "broc",
331 expected_month: 12,
332 expected_day: 31,
333 },
334 TestCase {
335 rd: RataDie::new(697613),
336 iso_year: 1911,
337 iso_month: 1,
338 iso_day: 1,
339 expected_year: 1,
340 expected_era: "broc",
341 expected_month: 1,
342 expected_day: 1,
343 },
344 TestCase {
345 rd: RataDie::new(697612),
346 iso_year: 1910,
347 iso_month: 12,
348 iso_day: 31,
349 expected_year: 2,
350 expected_era: "broc",
351 expected_month: 12,
352 expected_day: 31,
353 },
354 TestCase {
355 rd: RataDie::new(696576),
356 iso_year: 1908,
357 iso_month: 2,
358 iso_day: 29,
359 expected_year: 4,
360 expected_era: "broc",
361 expected_month: 2,
362 expected_day: 29,
363 },
364 TestCase {
365 rd: RataDie::new(1),
366 iso_year: 1,
367 iso_month: 1,
368 iso_day: 1,
369 expected_year: 1911,
370 expected_era: "broc",
371 expected_month: 1,
372 expected_day: 1,
373 },
374 TestCase {
375 rd: RataDie::new(0),
376 iso_year: 0,
377 iso_month: 12,
378 iso_day: 31,
379 expected_year: 1912,
380 expected_era: "broc",
381 expected_month: 12,
382 expected_day: 31,
383 },
384 ];
385
386 for case in cases {
387 check_test_case(case);
388 }
389 }
390
391 #[test]
392 fn test_roc_directionality_near_epoch() {
393 let rd_epoch_start = 697978;
397 for i in (rd_epoch_start - 100)..=(rd_epoch_start + 100) {
398 for j in (rd_epoch_start - 100)..=(rd_epoch_start + 100) {
399 let iso_i = Date::from_rata_die(RataDie::new(i), Iso);
400 let iso_j = Date::from_rata_die(RataDie::new(j), Iso);
401
402 let roc_i = Date::from_rata_die(RataDie::new(i), Roc);
403 let roc_j = Date::from_rata_die(RataDie::new(j), Roc);
404
405 assert_eq!(
406 i.cmp(&j),
407 iso_i.cmp(&iso_j),
408 "ISO directionality inconsistent with directionality for i: {i}, j: {j}"
409 );
410 assert_eq!(
411 i.cmp(&j),
412 roc_i.cmp(&roc_j),
413 "ROC directionality inconsistent with directionality for i: {i}, j: {j}"
414 );
415 }
416 }
417 }
418
419 #[test]
420 fn test_roc_directionality_near_rd_zero() {
421 for i in -100..=100 {
423 for j in -100..100 {
424 let iso_i = Date::from_rata_die(RataDie::new(i), Iso);
425 let iso_j = Date::from_rata_die(RataDie::new(j), Iso);
426
427 let roc_i = Date::from_rata_die(RataDie::new(i), Roc);
428 let roc_j = Date::from_rata_die(RataDie::new(j), Roc);
429
430 assert_eq!(
431 i.cmp(&j),
432 iso_i.cmp(&iso_j),
433 "ISO directionality inconsistent with directionality for i: {i}, j: {j}"
434 );
435 assert_eq!(
436 i.cmp(&j),
437 roc_i.cmp(&roc_j),
438 "ROC directionality inconsistent with directionality for i: {i}, j: {j}"
439 );
440 }
441 }
442 }
443}