use crate::error::Error;
use crate::epoch::Epoch;
use crate::instant::Instant;
#[cfg(feature = "serde")]
use serde::{Serialize, Deserialize};
pub trait Calendar {
fn is_gregorian() -> bool;
#[must_use]
fn name() -> &'static str {
if <Self as Calendar>::is_gregorian() {
"Gregorian"
} else {
"Julian"
}
}
#[must_use]
fn epoch() -> Instant {
if <Self as Calendar>::is_gregorian() {
Epoch::GregorianCalendar.as_instant()
} else {
Epoch::JulianCalendar.as_instant()
}
}
#[must_use]
fn is_year_leap(year: i32) -> bool {
if <Self as Calendar>::is_gregorian() {
(year%4==0) && ((year%100!=0) || (year%400==0))
} else {
year%4==0
}
}
#[allow(clippy::manual_range_contains)]
fn day_number(year: i32, month: u8, day: i64) -> Result<i64, Error> {
if month<1 || month>12 { return Err(Error::RangeError); }
let mut m0 = i64::from(month).checked_sub(1).ok_or(Error::RangeError)?;
let d0 = day.checked_sub(1).ok_or(Error::RangeError)?;
m0 = (m0 + 10) % 12;
let y: i64 = i64::from(year) - m0/10;
let mut day = {
365*y
+ y/4
+ (y>>63)
+ (m0*306 + 5)/10
+ d0
};
if <Self as Calendar>::is_gregorian() {
day = day
- y/100
+ y/400;
}
Ok(day - 306)
}
#[allow(clippy::cast_sign_loss)]
#[allow(clippy::cast_possible_truncation)]
fn from_day_number(mut day_number: i64) -> Result<(i32, u8, u8), Error> {
let (min,max) = if <Self as Calendar>::is_gregorian() {
(-784_352_296_671, 784_352_295_938)
} else {
(-784_368_402_798,784_368_402_065)
};
if day_number < min || day_number > max {
return Err(Error::RangeError);
}
day_number += 306;
let days_in_year_times_10000 = if <Self as Calendar>::is_gregorian() {
365_2425
} else {
365_2500
};
let mut offset_year: i64 = (10_000 * day_number + 14780) / days_in_year_times_10000;
let calc_remaining_days = |day_number: i64, offset_year: i64| -> i64 {
let zeroeth_year = offset_year>>63;
let mut remaining_days = day_number - 365*offset_year - offset_year/4 - zeroeth_year;
if <Self as Calendar>::is_gregorian() {
remaining_days = remaining_days + offset_year/100 - offset_year/400;
}
remaining_days
};
let mut remaining_days = calc_remaining_days(day_number, offset_year);
if remaining_days < 0 {
offset_year -= 1;
remaining_days = calc_remaining_days(day_number, offset_year);
}
let offset_month = (100*remaining_days + 52)/3060;
let year = offset_year + (offset_month + 2)/12;
let month = (offset_month + 2)%12;
assert!(month >= 0);
assert!(month < 12);
let day = remaining_days - (offset_month*306 + 5)/10;
assert!(day < 31);
assert!(day >= 0);
Ok((year as i32, (month+1) as u8, (day+1) as u8))
}
#[must_use]
fn month_days(month: u8, year: i32) -> u8 {
assert!(month>=1);
assert!(month<=12);
match month {
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
2 => if <Self as Calendar>::is_year_leap(year + i32::from((month-1)/12)) { 29 } else { 28 },
4 | 6 | 9 | 11 => 30,
_ => unreachable!()
}
}
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature ="serde", derive(Serialize, Deserialize))]
pub struct Julian;
impl Calendar for Julian {
fn is_gregorian() -> bool {
false
}
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature ="serde", derive(Serialize, Deserialize))]
pub struct Gregorian;
impl Calendar for Gregorian {
fn is_gregorian() -> bool {
true
}
}
#[cfg(test)]
mod test {
use super::{Calendar, Gregorian, Julian};
#[test]
fn test_gregorian_julian_date_matches() {
crate::setup_logging();
let dnj = Julian::day_number(1582,10,5).unwrap();
let dng = Gregorian::day_number(1582,10,15).unwrap();
assert_eq!(dnj - 2, dng);
let dnj = Julian::day_number(-4713,1,1).unwrap();
let dng = Gregorian::day_number(-4714,11,24).unwrap();
assert_eq!(dnj - 2, dng);
}
#[test]
fn test_calendar_gregorian_day_numbers() {
crate::setup_logging();
let dn = Gregorian::day_number(1,1,1).unwrap();
assert_eq!(dn, 0);
let (y,m,d) = Gregorian::from_day_number(0).unwrap();
assert_eq!( (y,m,d), (1,1,1) );
let dn = Gregorian::day_number(0,12,31).unwrap();
assert_eq!(dn, -1);
let (y,m,d) = Gregorian::from_day_number(-1).unwrap();
assert_eq!( (y,m,d), (0,12,31) );
let mar1 = Gregorian::day_number(0,3,1).unwrap();
assert_eq!(mar1, -306);
let (y,m,d) = Gregorian::from_day_number(mar1).unwrap();
assert_eq!( (y,m,d), (0,3,1) );
let feb29 = Gregorian::day_number(0,2,29).unwrap();
assert_eq!(feb29, -307);
let (y,m,d) = Gregorian::from_day_number(feb29).unwrap();
assert_eq!( (y,m,d), (0,2,29) );
let feb28 = Gregorian::day_number(0,2,28).unwrap();
assert_eq!(feb28, -308);
let (y,m,d) = Gregorian::from_day_number(feb28).unwrap();
assert_eq!( (y,m,d), (0,2,28) );
let dn = Gregorian::day_number(4,1,1).unwrap();
assert_eq!(dn, 365*3);
let (y,m,d) = Gregorian::from_day_number(dn).unwrap();
assert_eq!( (y,m,d), (4,1,1) );
let dn = Gregorian::day_number(1582,1,1).unwrap();
assert_eq!(dn, 365*(1582-1) + (1582-1)/4 - (1582-1)/100 + (1582-1)/400);
let (y,m,d) = Gregorian::from_day_number(dn).unwrap();
assert_eq!( (y,m,d), (1582,1,1) );
let dn = Gregorian::day_number(1582,3,1).unwrap();
assert_eq!(dn, 365*(1582-1) + (1582-1)/4 - (1582-1)/100 + (1582-1)/400 + 31 + 28);
let (y,m,d) = Gregorian::from_day_number(dn).unwrap();
assert_eq!( (y,m,d), (1582,3,1) );
let dn = Gregorian::day_number(1582,10,15).unwrap();
assert_eq!(dn, 365*(1582-1) + (1582-1)/4 - (1582-1)/100 + (1582-1)/400
+ 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 14);
let (y,m,d) = Gregorian::from_day_number(dn).unwrap();
assert_eq!( (y,m,d), (1582,10,15) );
let dn = Gregorian::day_number(2000,1,1).unwrap();
assert_eq!(dn, 730119);
let (y,m,d) = Gregorian::from_day_number(730119).unwrap();
assert_eq!( (y,m,d) , (2000,1,1) );
let dn = Gregorian::day_number(-2147483648,1,1).unwrap();
assert_eq!(dn, -784_352_296_671);
let (y,m,d) = Gregorian::from_day_number(-784_352_296_671).unwrap();
assert_eq!(y,-2147483648);
assert_eq!(m,1);
assert_eq!(d,1);
let dn = Gregorian::day_number(2147483647,12,31).unwrap();
assert_eq!(dn, 784_352_295_938);
let (y,m,d) = Gregorian::from_day_number(784_352_295_938).unwrap();
assert_eq!(y,2147483647);
assert_eq!(m,12);
assert_eq!(d,31);
}
#[test]
fn test_calendar_julian_day_numbers() {
crate::setup_logging();
let dn = Julian::day_number(1,1,1).unwrap();
assert_eq!(dn, 0);
let (y,m,d) = Julian::from_day_number(0).unwrap();
assert_eq!(y,1);
assert_eq!(m,1);
assert_eq!(d,1);
let dn = Julian::day_number(2000,1,1).unwrap();
assert_eq!(dn, 730134);
let (y,m,d) = Julian::from_day_number(dn).unwrap();
assert_eq!(y,2000);
assert_eq!(m,1);
assert_eq!(d,1);
let dn = Julian::day_number(-2147483648,1,1).unwrap();
assert_eq!(dn, -784_368_402_798);
let (y,m,d) = Julian::from_day_number(dn).unwrap();
assert_eq!(y,-2147483648);
assert_eq!(m,1);
assert_eq!(d,1);
let dn = Julian::day_number(2147483647,12,31).unwrap();
assert_eq!(dn, 784_368_402_065);
let (y,m,d) = Julian::from_day_number(dn).unwrap();
assert_eq!(y,2147483647);
assert_eq!(m,12);
assert_eq!(d,31);
}
}