1use crate::any_calendar::AnyCalendarKind;
35use crate::calendar_arithmetic::{ArithmeticDate, CalendarArithmetic};
36use crate::gregorian::year_as_gregorian;
37use crate::iso::Iso;
38use crate::{types, Calendar, CalendarError, Date, DateDuration, DateDurationUnit, DateTime, Time};
39use calendrical_calculations::helpers::I32CastError;
40use calendrical_calculations::rata_die::RataDie;
41use tinystr::tinystr;
42
43#[derive(Copy, Clone, Debug, Hash, Default, Eq, PartialEq, PartialOrd, Ord)]
59#[allow(clippy::exhaustive_structs)] pub struct Julian;
61
62#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
64pub struct JulianDateInner(pub(crate) ArithmeticDate<Julian>);
66
67impl CalendarArithmetic for Julian {
68 type YearInfo = ();
69
70 fn month_days(year: i32, month: u8, _data: ()) -> u8 {
71 match month {
72 4 | 6 | 9 | 11 => 30,
73 2 if Self::is_leap_year(year, ()) => 29,
74 2 => 28,
75 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
76 _ => 0,
77 }
78 }
79
80 fn months_for_every_year(_: i32, _data: ()) -> u8 {
81 12
82 }
83
84 fn is_leap_year(year: i32, _data: ()) -> bool {
85 calendrical_calculations::julian::is_leap_year(year)
86 }
87
88 fn last_month_day_in_year(_year: i32, _data: ()) -> (u8, u8) {
89 (12, 31)
90 }
91
92 fn days_in_provided_year(year: i32, _data: ()) -> u16 {
93 if Self::is_leap_year(year, ()) {
94 366
95 } else {
96 365
97 }
98 }
99}
100
101impl Calendar for Julian {
102 type DateInner = JulianDateInner;
103 fn date_from_codes(
104 &self,
105 era: types::Era,
106 year: i32,
107 month_code: types::MonthCode,
108 day: u8,
109 ) -> Result<Self::DateInner, CalendarError> {
110 let year = if era.0 == tinystr!(16, "ce") {
111 if year <= 0 {
112 return Err(CalendarError::OutOfRange);
113 }
114 year
115 } else if era.0 == tinystr!(16, "bce") {
116 if year <= 0 {
117 return Err(CalendarError::OutOfRange);
118 }
119 1 - year
120 } else {
121 return Err(CalendarError::UnknownEra(era.0, self.debug_name()));
122 };
123
124 ArithmeticDate::new_from_codes(self, year, month_code, day).map(JulianDateInner)
125 }
126 fn date_from_iso(&self, iso: Date<Iso>) -> JulianDateInner {
127 let fixed_iso = Iso::fixed_from_iso(*iso.inner());
128 Self::julian_from_fixed(fixed_iso)
129 }
130
131 fn date_to_iso(&self, date: &Self::DateInner) -> Date<Iso> {
132 let fixed_julian = Julian::fixed_from_julian(date.0);
133 Iso::iso_from_fixed(fixed_julian)
134 }
135
136 fn months_in_year(&self, date: &Self::DateInner) -> u8 {
137 date.0.months_in_year()
138 }
139
140 fn days_in_year(&self, date: &Self::DateInner) -> u16 {
141 date.0.days_in_year()
142 }
143
144 fn days_in_month(&self, date: &Self::DateInner) -> u8 {
145 date.0.days_in_month()
146 }
147
148 fn day_of_week(&self, date: &Self::DateInner) -> types::IsoWeekday {
149 Iso.day_of_week(Julian.date_to_iso(date).inner())
150 }
151
152 fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
153 date.0.offset_date(offset, &());
154 }
155
156 #[allow(clippy::field_reassign_with_default)]
157 fn until(
158 &self,
159 date1: &Self::DateInner,
160 date2: &Self::DateInner,
161 _calendar2: &Self,
162 _largest_unit: DateDurationUnit,
163 _smallest_unit: DateDurationUnit,
164 ) -> DateDuration<Self> {
165 date1.0.until(date2.0, _largest_unit, _smallest_unit)
166 }
167
168 fn year(&self, date: &Self::DateInner) -> types::FormattableYear {
171 year_as_gregorian(date.0.year)
172 }
173
174 fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
175 Self::is_leap_year(date.0.year, ())
176 }
177
178 fn month(&self, date: &Self::DateInner) -> types::FormattableMonth {
180 date.0.month()
181 }
182
183 fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
185 date.0.day_of_month()
186 }
187
188 fn day_of_year_info(&self, date: &Self::DateInner) -> types::DayOfYearInfo {
189 let prev_year = date.0.year - 1;
190 let next_year = date.0.year + 1;
191 types::DayOfYearInfo {
192 day_of_year: date.0.day_of_year(),
193 days_in_year: date.0.days_in_year(),
194 prev_year: crate::gregorian::year_as_gregorian(prev_year),
195 days_in_prev_year: Julian::days_in_year_direct(prev_year),
196 next_year: crate::gregorian::year_as_gregorian(next_year),
197 }
198 }
199
200 fn debug_name(&self) -> &'static str {
201 "Julian"
202 }
203
204 fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
205 None
206 }
207}
208
209impl Julian {
210 pub fn new() -> Self {
212 Self
213 }
214
215 pub(crate) const fn fixed_from_julian(date: ArithmeticDate<Julian>) -> RataDie {
217 calendrical_calculations::julian::fixed_from_julian(date.year, date.month, date.day)
218 }
219
220 fn days_in_year_direct(year: i32) -> u16 {
223 if Julian::is_leap_year(year, ()) {
224 366
225 } else {
226 365
227 }
228 }
229
230 fn julian_from_fixed(date: RataDie) -> JulianDateInner {
231 let (year, month, day) = match calendrical_calculations::julian::julian_from_fixed(date) {
232 Err(I32CastError::BelowMin) => return JulianDateInner(ArithmeticDate::min_date()),
233 Err(I32CastError::AboveMax) => return JulianDateInner(ArithmeticDate::max_date()),
234 Ok(ymd) => ymd,
235 };
236 JulianDateInner(ArithmeticDate::new_unchecked(year, month, day))
237 }
238}
239
240impl Date<Julian> {
241 pub fn try_new_julian_date(
256 year: i32,
257 month: u8,
258 day: u8,
259 ) -> Result<Date<Julian>, CalendarError> {
260 ArithmeticDate::new_from_ordinals(year, month, day)
261 .map(JulianDateInner)
262 .map(|inner| Date::from_raw(inner, Julian))
263 }
264}
265
266impl DateTime<Julian> {
267 pub fn try_new_julian_datetime(
286 year: i32,
287 month: u8,
288 day: u8,
289 hour: u8,
290 minute: u8,
291 second: u8,
292 ) -> Result<DateTime<Julian>, CalendarError> {
293 Ok(DateTime {
294 date: Date::try_new_julian_date(year, month, day)?,
295 time: Time::try_new(hour, minute, second, 0)?,
296 })
297 }
298}
299
300#[cfg(test)]
301mod test {
302 use super::*;
303 use types::Era;
304
305 #[test]
306 fn test_day_iso_to_julian() {
307 let iso_date = Date::try_new_iso_date(200, 3, 1).unwrap();
309 let julian_date = Julian.date_from_iso(iso_date);
310 assert_eq!(julian_date.0.year, 200);
311 assert_eq!(julian_date.0.month, 3);
312 assert_eq!(julian_date.0.day, 1);
313
314 let iso_date = Date::try_new_iso_date(200, 2, 28).unwrap();
316 let julian_date = Julian.date_from_iso(iso_date);
317 assert_eq!(julian_date.0.year, 200);
318 assert_eq!(julian_date.0.month, 2);
319 assert_eq!(julian_date.0.day, 29);
320
321 let iso_date = Date::try_new_iso_date(400, 3, 1).unwrap();
323 let julian_date = Julian.date_from_iso(iso_date);
324 assert_eq!(julian_date.0.year, 400);
325 assert_eq!(julian_date.0.month, 2);
326 assert_eq!(julian_date.0.day, 29);
327
328 let iso_date = Date::try_new_iso_date(2022, 1, 1).unwrap();
330 let julian_date = Julian.date_from_iso(iso_date);
331 assert_eq!(julian_date.0.year, 2021);
332 assert_eq!(julian_date.0.month, 12);
333 assert_eq!(julian_date.0.day, 19);
334 }
335
336 #[test]
337 fn test_day_julian_to_iso() {
338 let julian_date = Date::try_new_julian_date(200, 3, 1).unwrap();
340 let iso_date = Julian.date_to_iso(julian_date.inner());
341 let iso_expected_date = Date::try_new_iso_date(200, 3, 1).unwrap();
342 assert_eq!(iso_date, iso_expected_date);
343
344 let julian_date = Date::try_new_julian_date(200, 2, 29).unwrap();
346 let iso_date = Julian.date_to_iso(julian_date.inner());
347 let iso_expected_date = Date::try_new_iso_date(200, 2, 28).unwrap();
348 assert_eq!(iso_date, iso_expected_date);
349
350 let julian_date = Date::try_new_julian_date(400, 2, 29).unwrap();
352 let iso_date = Julian.date_to_iso(julian_date.inner());
353 let iso_expected_date = Date::try_new_iso_date(400, 3, 1).unwrap();
354 assert_eq!(iso_date, iso_expected_date);
355
356 let julian_date = Date::try_new_julian_date(2021, 12, 19).unwrap();
358 let iso_date = Julian.date_to_iso(julian_date.inner());
359 let iso_expected_date = Date::try_new_iso_date(2022, 1, 1).unwrap();
360 assert_eq!(iso_date, iso_expected_date);
361
362 let julian_date = Date::try_new_julian_date(2022, 2, 16).unwrap();
364 let iso_date = Julian.date_to_iso(julian_date.inner());
365 let iso_expected_date = Date::try_new_iso_date(2022, 3, 1).unwrap();
366 assert_eq!(iso_date, iso_expected_date);
367 }
368
369 #[test]
370 fn test_roundtrip_negative() {
371 let iso_date = Date::try_new_iso_date(-1000, 3, 3).unwrap();
373 let julian = iso_date.to_calendar(Julian::new());
374 let recovered_iso = julian.to_iso();
375 assert_eq!(iso_date, recovered_iso);
376 }
377
378 #[test]
379 fn test_julian_near_era_change() {
380 #[derive(Debug)]
384 struct TestCase {
385 fixed_date: i64,
386 iso_year: i32,
387 iso_month: u8,
388 iso_day: u8,
389 expected_year: i32,
390 expected_era: Era,
391 expected_month: u32,
392 expected_day: u32,
393 }
394
395 let cases = [
396 TestCase {
397 fixed_date: 1,
398 iso_year: 1,
399 iso_month: 1,
400 iso_day: 1,
401 expected_year: 1,
402 expected_era: Era(tinystr!(16, "ce")),
403 expected_month: 1,
404 expected_day: 3,
405 },
406 TestCase {
407 fixed_date: 0,
408 iso_year: 0,
409 iso_month: 12,
410 iso_day: 31,
411 expected_year: 1,
412 expected_era: Era(tinystr!(16, "ce")),
413 expected_month: 1,
414 expected_day: 2,
415 },
416 TestCase {
417 fixed_date: -1,
418 iso_year: 0,
419 iso_month: 12,
420 iso_day: 30,
421 expected_year: 1,
422 expected_era: Era(tinystr!(16, "ce")),
423 expected_month: 1,
424 expected_day: 1,
425 },
426 TestCase {
427 fixed_date: -2,
428 iso_year: 0,
429 iso_month: 12,
430 iso_day: 29,
431 expected_year: 1,
432 expected_era: Era(tinystr!(16, "bce")),
433 expected_month: 12,
434 expected_day: 31,
435 },
436 TestCase {
437 fixed_date: -3,
438 iso_year: 0,
439 iso_month: 12,
440 iso_day: 28,
441 expected_year: 1,
442 expected_era: Era(tinystr!(16, "bce")),
443 expected_month: 12,
444 expected_day: 30,
445 },
446 TestCase {
447 fixed_date: -367,
448 iso_year: -1,
449 iso_month: 12,
450 iso_day: 30,
451 expected_year: 1,
452 expected_era: Era(tinystr!(16, "bce")),
453 expected_month: 1,
454 expected_day: 1,
455 },
456 TestCase {
457 fixed_date: -368,
458 iso_year: -1,
459 iso_month: 12,
460 iso_day: 29,
461 expected_year: 2,
462 expected_era: Era(tinystr!(16, "bce")),
463 expected_month: 12,
464 expected_day: 31,
465 },
466 TestCase {
467 fixed_date: -1462,
468 iso_year: -4,
469 iso_month: 12,
470 iso_day: 30,
471 expected_year: 4,
472 expected_era: Era(tinystr!(16, "bce")),
473 expected_month: 1,
474 expected_day: 1,
475 },
476 TestCase {
477 fixed_date: -1463,
478 iso_year: -4,
479 iso_month: 12,
480 iso_day: 29,
481 expected_year: 5,
482 expected_era: Era(tinystr!(16, "bce")),
483 expected_month: 12,
484 expected_day: 31,
485 },
486 ];
487
488 for case in cases {
489 let iso_from_fixed: Date<Iso> = Iso::iso_from_fixed(RataDie::new(case.fixed_date));
490 let julian_from_fixed: Date<Julian> = Date::new_from_iso(iso_from_fixed, Julian);
491 assert_eq!(julian_from_fixed.year().number, case.expected_year,
492 "Failed year check from fixed: {case:?}\nISO: {iso_from_fixed:?}\nJulian: {julian_from_fixed:?}");
493 assert_eq!(julian_from_fixed.year().era, case.expected_era,
494 "Failed era check from fixed: {case:?}\nISO: {iso_from_fixed:?}\nJulian: {julian_from_fixed:?}");
495 assert_eq!(julian_from_fixed.month().ordinal, case.expected_month,
496 "Failed month check from fixed: {case:?}\nISO: {iso_from_fixed:?}\nJulian: {julian_from_fixed:?}");
497 assert_eq!(julian_from_fixed.day_of_month().0, case.expected_day,
498 "Failed day check from fixed: {case:?}\nISO: {iso_from_fixed:?}\nJulian: {julian_from_fixed:?}");
499
500 let iso_date_man: Date<Iso> =
501 Date::try_new_iso_date(case.iso_year, case.iso_month, case.iso_day)
502 .expect("Failed to initialize ISO date for {case:?}");
503 let julian_date_man: Date<Julian> = Date::new_from_iso(iso_date_man, Julian);
504 assert_eq!(iso_from_fixed, iso_date_man,
505 "ISO from fixed not equal to ISO generated from manually-input ymd\nCase: {case:?}\nFixed: {iso_from_fixed:?}\nMan: {iso_date_man:?}");
506 assert_eq!(julian_from_fixed, julian_date_man,
507 "Julian from fixed not equal to Julian generated from manually-input ymd\nCase: {case:?}\nFixed: {julian_from_fixed:?}\nMan: {julian_date_man:?}");
508 }
509 }
510
511 #[test]
512 fn test_julian_fixed_date_conversion() {
513 for i in -10000..=10000 {
516 let fixed = RataDie::new(i);
517 let julian = Julian::julian_from_fixed(fixed);
518 let new_fixed = Julian::fixed_from_julian(julian.0);
519 assert_eq!(fixed, new_fixed);
520 }
521 }
522
523 #[test]
524 fn test_julian_directionality() {
525 for i in -100..=100 {
529 for j in -100..=100 {
530 let julian_i = Julian::julian_from_fixed(RataDie::new(i)).0;
531 let julian_j = Julian::julian_from_fixed(RataDie::new(j)).0;
532
533 assert_eq!(
534 i.cmp(&j),
535 julian_i.cmp(&julian_j),
536 "Julian directionality inconsistent with directionality for i: {i}, j: {j}"
537 );
538 }
539 }
540 }
541
542 #[test]
543 fn test_hebrew_epoch() {
544 assert_eq!(
545 calendrical_calculations::julian::fixed_from_julian_book_version(-3761, 10, 7),
546 RataDie::new(-1373427)
547 );
548 }
549
550 #[test]
551 fn test_julian_leap_years() {
552 assert!(Julian::is_leap_year(4, ()));
553 assert!(Julian::is_leap_year(0, ()));
554 assert!(Julian::is_leap_year(-4, ()));
555
556 Date::try_new_julian_date(2020, 2, 29).unwrap();
557 }
558}