Source code
Revision control
Copy as Markdown
Other Tools
// This file is part of ICU4X.
//
// The contents of this file implement algorithms from Calendrical Calculations
// by Reingold & Dershowitz, Cambridge University Press, 4th edition (2018),
// under the Apache-2.0 license. Accordingly, this file is released under
// the Apache License, Version 2.0 which can be found at the calendrical_calculations
use crate::helpers::{i64_to_i32, I32CastError};
use crate::rata_die::RataDie;
// Julian epoch is equivalent to fixed_from_iso of December 30th of 0 year
// 1st Jan of 1st year Julian is equivalent to December 30th of 0th year of ISO year
const JULIAN_EPOCH: RataDie = RataDie::new(-1);
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/1ee51ecfaae6f856b0d7de3e36e9042100b4f424/calendar.l#L1684-L1687>
#[inline(always)]
pub const fn is_leap_year(year: i32) -> bool {
year % 4 == 0
}
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/1ee51ecfaae6f856b0d7de3e36e9042100b4f424/calendar.l#L1689-L1709>
pub const fn fixed_from_julian(year: i32, month: u8, day: u8) -> RataDie {
let mut fixed =
JULIAN_EPOCH.to_i64_date() - 1 + 365 * (year as i64 - 1) + (year as i64 - 1).div_euclid(4);
debug_assert!(month > 0 && month < 13, "Month should be in range 1..=12.");
fixed += match month {
1 => 0,
2 => 31,
3 => 59,
4 => 90,
5 => 120,
6 => 151,
7 => 181,
8 => 212,
9 => 243,
10 => 273,
11 => 304,
12 => 334,
_ => -1,
};
// Only add one if the month is after February (month > 2), since leap days are added to the end of February
if month > 2 && is_leap_year(year) {
fixed += 1;
}
RataDie::new(fixed + (day as i64))
}
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/1ee51ecfaae6f856b0d7de3e36e9042100b4f424/calendar.l#L1711-L1738>
pub fn julian_from_fixed(date: RataDie) -> Result<(i32, u8, u8), I32CastError> {
let approx = (4 * date.to_i64_date() + 1464).div_euclid(1461);
let year = i64_to_i32(approx)?;
let prior_days = date
- fixed_from_julian(year, 1, 1)
- if is_leap_year(year) && date > fixed_from_julian(year, 2, 28) {
1
} else {
0
};
let adjusted_year = if prior_days >= 365 {
year.saturating_add(1)
} else {
year
};
let adjusted_prior_days = prior_days.rem_euclid(365);
debug_assert!((0..365).contains(&adjusted_prior_days));
let month = if adjusted_prior_days < 31 {
1
} else if adjusted_prior_days < 59 {
2
} else if adjusted_prior_days < 90 {
3
} else if adjusted_prior_days < 120 {
4
} else if adjusted_prior_days < 151 {
5
} else if adjusted_prior_days < 181 {
6
} else if adjusted_prior_days < 212 {
7
} else if adjusted_prior_days < 243 {
8
} else if adjusted_prior_days < 273 {
9
} else if adjusted_prior_days < 304 {
10
} else if adjusted_prior_days < 334 {
11
} else {
12
};
let day = (date - fixed_from_julian(adjusted_year, month, 1) + 1) as u8; // as days_in_month is < u8::MAX
debug_assert!(day <= 31, "Day assertion failed; date: {date:?}, adjusted_year: {adjusted_year}, prior_days: {prior_days}, month: {month}, day: {day}");
Ok((adjusted_year, month, day))
}
/// Get a fixed date from the ymd of a Julian date; years are counted as in _Calendrical Calculations_ by Reingold & Dershowitz,
/// meaning there is no year 0. For instance, near the epoch date, years are counted: -3, -2, -1, 1, 2, 3 instead of -2, -1, 0, 1, 2, 3.
///
/// Primarily useful for use with code constructing epochs specified in the bookg
pub const fn fixed_from_julian_book_version(book_year: i32, month: u8, day: u8) -> RataDie {
debug_assert!(book_year != 0);
// TODO: Should we check the bounds here?
fixed_from_julian(
if book_year < 0 {
book_year + 1
} else {
book_year
},
month,
day,
)
}