// 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),
// which have been released as Lisp code at
// 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
// package root or at http://www.apache.org/licenses/LICENSE-2.0.
use crate::helpers::{i64_to_i32, k_day_after, I32CastError};
use crate::rata_die::RataDie;
// The Gregorian epoch is equivalent to first day in fixed day measurement
const EPOCH: RataDie = RataDie::new(1);
const DAYS_IN_YEAR: i64 = 365;
// One leap year every 4 years
const DAYS_IN_4_YEAR_CYCLE: i64 = DAYS_IN_YEAR * 4 + 1;
// No leap year every 100 years
const DAYS_IN_100_YEAR_CYCLE: i64 = 25 * DAYS_IN_4_YEAR_CYCLE - 1;
// One extra leap year every 400 years
/// The number of days in the 400 year cycle.
pub const DAYS_IN_400_YEAR_CYCLE: i64 = 4 * DAYS_IN_100_YEAR_CYCLE + 1;
/// Whether or not `year` is a leap year
///
/// Inspired by Neri-Schneider
pub const fn is_leap_year(year: i32) -> bool {
// This is branch-free, as it compiles to a conditional move
if year % 25 != 0 {
year % 4 == 0
} else {
year % 16 == 0
}
}
// Fixed is day count representation of calendars starting from Jan 1st of year 1.
// The fixed calculations algorithms are from the Calendrical Calculations book.
//
/// Lisp code reference:
pub const fn fixed_from_gregorian(year: i32, month: u8, day: u8) -> RataDie {
day_before_year(year)
.add(days_before_month(year, month) as i64)
.add(day as i64)
}
/// The number of days in this year before this month starts
///
/// Inspired by Neri-Schneider
pub const fn days_before_month(year: i32, month: u8) -> u16 {
if month < 3 {
// This compiles to a conditional move, so there's only one branch in this function
if month == 1 {
0
} else {
31
}
} else {
31 + 28 + is_leap_year(year) as u16 + ((979 * (month as u32) - 2919) >> 5) as u16
}
}
/// Lisp code reference:
pub const fn year_from_fixed(date: RataDie) -> Result {
// Shouldn't overflow because it's not possbile to construct extreme values of RataDie
let date = date.since(EPOCH);
let (n_400, date) = (
date.div_euclid(DAYS_IN_400_YEAR_CYCLE),
date.rem_euclid(DAYS_IN_400_YEAR_CYCLE),
);
let (n_100, date) = (date / DAYS_IN_100_YEAR_CYCLE, date % DAYS_IN_100_YEAR_CYCLE);
let (n_4, date) = (date / DAYS_IN_4_YEAR_CYCLE, date % DAYS_IN_4_YEAR_CYCLE);
let n_1 = date / DAYS_IN_YEAR;
let year = 400 * n_400 + 100 * n_100 + 4 * n_4 + n_1 + (n_100 != 4 && n_1 != 4) as i64;
i64_to_i32(year)
}
/// Calculates the day before Jan 1 of `year`.
pub const fn day_before_year(year: i32) -> RataDie {
let prev_year = (year as i64) - 1;
// Calculate days per year
let mut fixed: i64 = DAYS_IN_YEAR * prev_year;
// Adjust for leap year logic. We can avoid the branch of div_euclid by making prev_year positive:
// YEAR_SHIFT is larger (in magnitude) than any prev_year, and, being divisible by 400,
// distributes correctly over the calculation on the next line.
const YEAR_SHIFT: i64 = (-(i32::MIN as i64 - 1) / 400 + 1) * 400;
fixed += (prev_year + YEAR_SHIFT) / 4 - (prev_year + YEAR_SHIFT) / 100
+ (prev_year + YEAR_SHIFT) / 400
- const { YEAR_SHIFT / 4 - YEAR_SHIFT / 100 + YEAR_SHIFT / 400 };
RataDie::new(fixed)
}
/// Calculates the month/day from the 1-based day of the year
pub fn year_day(year: i32, day_of_year: u16) -> (u8, u8) {
// Calculates the prior days of the year, then applies a correction based on leap year conditions for the correct ISO date conversion.
let correction = if day_of_year < 31 + 28 + is_leap_year(year) as u16 {
-1
} else {
(!is_leap_year(year)) as i32
};
let month = ((12 * (day_of_year as i32 + correction) + 373) / 367) as u8; // in 1..12 < u8::MAX
let day = (day_of_year - days_before_month(year, month)) as u8; // <= days_in_month < u8::MAX
(month, day)
}
/// Lisp code reference:
pub fn gregorian_from_fixed(date: RataDie) -> Result<(i32, u8, u8), I32CastError> {
let year = year_from_fixed(date)?;
let day_of_year = date - day_before_year(year);
let (month, day) = year_day(year, day_of_year as u16);
Ok((year, month, day))
}
/// Calculates the date of Easter in the given year
pub fn easter(year: i32) -> RataDie {
let century = (year / 100) + 1;
let shifted_epact =
(14 + 11 * year.rem_euclid(19) - century * 3 / 4 + (5 + 8 * century) / 25).rem_euclid(30);
let adjusted_epact = shifted_epact
+ (shifted_epact == 0 || (shifted_epact == 1 && 10 < year.rem_euclid(19))) as i32;
let paschal_moon = fixed_from_gregorian(year, 4, 19) - adjusted_epact as i64;
k_day_after(0, paschal_moon)
}
#[test]
fn test_easter() {
// https://en.wikipedia.org/wiki/List_of_dates_for_Easter
for (y, m, d) in [
(2021, 4, 4),
(2022, 4, 17),
(2023, 4, 9),
(2024, 3, 31),
(2025, 4, 20),
(2026, 4, 5),
(2027, 3, 28),
(2028, 4, 16),
(2029, 4, 1),
] {
assert_eq!(easter(y), fixed_from_gregorian(y, m, d));
}
}