extern crate chrono; extern crate chrono_tz; use chrono::{Datelike, NaiveDate}; #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum DayOfWeek { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, LeapDay, YearDay, } impl From for String { fn from(day: DayOfWeek) -> Self { match day { DayOfWeek::Sunday => "Sunday", DayOfWeek::Monday => "Monday", DayOfWeek::Tuesday => "Tuesday", DayOfWeek::Wednesday => "Wednesday", DayOfWeek::Thursday => "Thursday", DayOfWeek::Friday => "Friday", DayOfWeek::Saturday => "Saturday", DayOfWeek::LeapDay => "LeapDay", DayOfWeek::YearDay => "YearDay", } .to_owned() } } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum Month { January, February, March, April, May, June, Sol, July, August, September, October, November, December, } impl From for Month { fn from(val: u32) -> Month { match val { 1 => Month::January, 2 => Month::February, 3 => Month::March, 4 => Month::April, 5 => Month::May, 6 => Month::June, 7 => Month::Sol, 8 => Month::July, 9 => Month::August, 10 => Month::September, 11 => Month::October, 12 => Month::November, 13 => Month::December, _ => panic!("invalid month number"), } } } impl From for String { fn from(val: Month) -> String { match val { Month::January => "January", Month::February => "February", Month::March => "March", Month::April => "April", Month::May => "May", Month::June => "June", Month::Sol => "Sol", Month::July => "July", Month::August => "August", Month::September => "September", Month::October => "October", Month::November => "November", Month::December => "December", } .to_owned() } } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct IFC { year: u32, ordinal: u32, leap_year: bool, } fn is_leap_year(year: i32) -> bool { NaiveDate::from_ymd(year, 12, 31).ordinal() == 366 } impl IFC { pub fn ymd(year: u32, month: u8, day: u8) -> IFC { let leap_year = is_leap_year(year as i32 - 10000); let ordinal = if is_leap_year(year as i32 - 10000) { if month == 6 && day == 29 { 168 } else if month > 6 { (month as u32 - 1) * 28 + (day as u32) } else { (month as u32 - 1) * 28 + (day as u32) - 1 } } else { (month as u32 - 1) * 28 + (day as u32) - 1 }; IFC { year, ordinal, leap_year, } } pub fn weekday_ifc(&self) -> DayOfWeek { match self.day() % 7 { 0 => DayOfWeek::Saturday, 1 => DayOfWeek::Sunday, 2 => DayOfWeek::Monday, 3 => DayOfWeek::Tuesday, 4 => DayOfWeek::Wednesday, 5 => DayOfWeek::Thursday, 6 => DayOfWeek::Friday, _ => panic!("impossible condition"), } } pub fn month_ifc(&self) -> Month { Month::from(self.month()) } } impl From> for IFC { fn from(d: chrono::Date) -> IFC { IFC::from(d.naive_utc()) } } impl From> for IFC { fn from(d: chrono::Date) -> IFC { IFC::from(d.naive_utc()) } } impl From for IFC { fn from(d: NaiveDate) -> IFC { //println!("d: {} [{}]", d.format("%Y-%m-%d"), d.ordinal()); IFC { year: (d.year() + 10000) as u32, ordinal: d.ordinal0(), leap_year: is_leap_year(d.year()), } } } impl Datelike for IFC { fn year(&self) -> i32 { self.year as i32 } fn month(&self) -> u32 { self.month0() + 1 } fn month0(&self) -> u32 { if self.leap_year && self.ordinal == 365 { 12 } else if self.leap_year && self.ordinal == 168 { 5 } else { self.ordinal / 28 } } fn day(&self) -> u32 { self.day0() + 1 } fn day0(&self) -> u32 { if self.leap_year { if self.ordinal == 365 { 28 } else if self.ordinal == 168 { 28 } else if self.ordinal > 168 { (self.ordinal - 1).rem_euclid(28) as u32 } else { self.ordinal.rem_euclid(28) as u32 } } else { if self.ordinal == 364 { 28 } else { self.ordinal.rem_euclid(28) as u32 } } } fn ordinal(&self) -> u32 { self.ordinal + 1 } fn ordinal0(&self) -> u32 { self.ordinal } fn weekday(&self) -> chrono::Weekday { if self.day0() == 28 { chrono::Weekday::Sat } else { match self.day0().rem_euclid(7) { 0 => chrono::Weekday::Sun, 1 => chrono::Weekday::Mon, 2 => chrono::Weekday::Tue, 3 => chrono::Weekday::Wed, 4 => chrono::Weekday::Thu, 5 => chrono::Weekday::Fri, 6 => chrono::Weekday::Sat, _ => panic!("rem_euclid should not return anything outside the 0..6 range"), } } } fn iso_week(&self) -> chrono::IsoWeek { panic!("iso_week is not implemented because chrono does not expose any constructors for IsoWeek!"); } fn with_year(&self, year: i32) -> Option { Some(IFC { year: (year as u32) + 10000, ordinal: self.ordinal, leap_year: is_leap_year(year), }) } fn with_month(&self, month: u32) -> Option { Some(IFC::ymd(self.year, month as u8, self.day() as u8)) } fn with_month0(&self, month: u32) -> Option { Some(IFC::ymd(self.year, month as u8 + 1, self.day() as u8)) } fn with_day(&self, day: u32) -> Option { Some(IFC::ymd(self.year, self.month() as u8, day as u8)) } fn with_day0(&self, day: u32) -> Option { Some(IFC::ymd(self.year, self.month() as u8, day as u8 + 1)) } fn with_ordinal(&self, ordinal: u32) -> Option { Some(IFC { year: self.year, ordinal, leap_year: self.leap_year, }) } fn with_ordinal0(&self, ordinal: u32) -> Option { Some(IFC { year: self.year, ordinal: ordinal + 1, leap_year: self.leap_year, }) } } #[cfg(test)] mod tests { use super::*; use chrono::NaiveDate; #[test] fn check_start_of_month() { assert_eq!( IFC::ymd(12019, 1, 1), IFC { year: 12019, ordinal: 0, leap_year: false } ); assert_eq!( IFC::ymd(12019, 2, 1), IFC { year: 12019, ordinal: 28, leap_year: false } ); assert_eq!( IFC::ymd(12019, 3, 1), IFC { year: 12019, ordinal: 56, leap_year: false } ); assert_eq!( IFC::ymd(12019, 4, 1), IFC { year: 12019, ordinal: 84, leap_year: false } ); assert_eq!( IFC::ymd(12019, 5, 1), IFC { year: 12019, ordinal: 112, leap_year: false } ); assert_eq!( IFC::ymd(12019, 6, 1), IFC { year: 12019, ordinal: 140, leap_year: false } ); assert_eq!( IFC::ymd(12019, 7, 1), IFC { year: 12019, ordinal: 168, leap_year: false } ); assert_eq!( IFC::ymd(12019, 8, 1), IFC { year: 12019, ordinal: 196, leap_year: false } ); assert_eq!( IFC::ymd(12019, 9, 1), IFC { year: 12019, ordinal: 224, leap_year: false } ); assert_eq!( IFC::ymd(12019, 10, 1), IFC { year: 12019, ordinal: 252, leap_year: false } ); assert_eq!( IFC::ymd(12019, 11, 1), IFC { year: 12019, ordinal: 280, leap_year: false } ); assert_eq!( IFC::ymd(12019, 12, 1), IFC { year: 12019, ordinal: 308, leap_year: false } ); assert_eq!( IFC::ymd(12019, 13, 1), IFC { year: 12019, ordinal: 336, leap_year: false } ); } #[test] fn check_start_of_month_leap_year() { assert_eq!( IFC::ymd(12020, 1, 1), IFC { year: 12020, ordinal: 0, leap_year: true } ); assert_eq!( IFC::ymd(12020, 2, 1), IFC { year: 12020, ordinal: 28, leap_year: true } ); assert_eq!( IFC::ymd(12020, 3, 1), IFC { year: 12020, ordinal: 56, leap_year: true } ); assert_eq!( IFC::ymd(12020, 4, 1), IFC { year: 12020, ordinal: 84, leap_year: true } ); assert_eq!( IFC::ymd(12020, 5, 1), IFC { year: 12020, ordinal: 112, leap_year: true } ); assert_eq!( IFC::ymd(12020, 6, 1), IFC { year: 12020, ordinal: 140, leap_year: true } ); assert_eq!( IFC::ymd(12020, 6, 29), IFC { year: 12020, ordinal: 168, leap_year: true } ); assert_eq!( IFC::ymd(12020, 7, 1), IFC { year: 12020, ordinal: 169, leap_year: true } ); assert_eq!( IFC::ymd(12020, 8, 1), IFC { year: 12020, ordinal: 197, leap_year: true } ); assert_eq!( IFC::ymd(12020, 9, 1), IFC { year: 12020, ordinal: 225, leap_year: true } ); assert_eq!( IFC::ymd(12020, 10, 1), IFC { year: 12020, ordinal: 253, leap_year: true } ); assert_eq!( IFC::ymd(12020, 11, 1), IFC { year: 12020, ordinal: 281, leap_year: true } ); assert_eq!( IFC::ymd(12020, 12, 1), IFC { year: 12020, ordinal: 309, leap_year: true } ); assert_eq!( IFC::ymd(12020, 13, 1), IFC { year: 12020, ordinal: 337, leap_year: true } ); } #[test] fn it_matches_january_1() { assert_eq!( IFC::from(NaiveDate::from_ymd(2019, 1, 1)), IFC::ymd(12019, 1, 1) ); } #[test] fn it_matches_february_1() { assert_eq!( IFC::from(NaiveDate::from_ymd(2019, 1, 29)), IFC::ymd(12019, 2, 1) ); } #[test] fn it_matches_sol_1() { assert_eq!( IFC::from(NaiveDate::from_ymd(2019, 6, 18)), IFC::ymd(12019, 7, 1) ); } #[test] fn it_matches_year_day() { assert_eq!( IFC::from(NaiveDate::from_ymd(2019, 12, 31)), IFC::ymd(12019, 13, 29) ); } #[test] fn it_matches_leap_day() { assert_eq!( IFC::from(NaiveDate::from_ymd(2019, 6, 18)), IFC::ymd(12019, 7, 1), ); assert_eq!( IFC::from(NaiveDate::from_ymd(2020, 6, 17)), IFC::ymd(12020, 6, 29), ); assert_eq!( IFC::from(NaiveDate::from_ymd(2020, 6, 18)), IFC::ymd(12020, 7, 1), ); assert_ne!(IFC::ymd(12020, 6, 29), IFC::ymd(12020, 7, 1)); } #[test] fn it_handles_gregorian_leap_day() { assert_eq!( IFC::from(NaiveDate::from_ymd(2019, 3, 1)), IFC::ymd(12019, 3, 4) ); assert_eq!( IFC::from(NaiveDate::from_ymd(2020, 2, 29)), IFC::ymd(12020, 3, 4) ); } #[test] fn it_handles_days_between_leap_days() { assert_eq!( IFC::from(NaiveDate::from_ymd(2020, 4, 8)), IFC::ymd(12020, 4, 15), ); } #[test] fn it_handles_days_after_ifc_leap_day() { assert_eq!( IFC::from(NaiveDate::from_ymd(2020, 10, 8)), IFC::ymd(12020, 11, 1), ); } #[test] fn it_matches_year_day_in_leap_year() { assert_eq!(NaiveDate::from_ymd(2020, 12, 31).ordinal(), 366); assert_eq!( IFC::from(NaiveDate::from_ymd(2020, 12, 31)), IFC::ymd(12020, 13, 29) ); } #[test] fn it_reports_correct_month() { assert_eq!(IFC::ymd(12019, 1, 1).month(), 1); assert_eq!(IFC::ymd(12019, 1, 1).month0(), 0); assert_eq!(IFC::ymd(12020, 6, 28).month(), 6); assert_eq!(IFC::ymd(12020, 6, 28).month0(), 5); assert_eq!(IFC::ymd(12020, 7, 1).month(), 7); assert_eq!(IFC::ymd(12020, 7, 1).month0(), 6); assert_eq!(IFC::ymd(12019, 13, 1).month(), 13); assert_eq!(IFC::ymd(12019, 13, 1).month0(), 12); } #[test] fn it_reports_correct_day() { assert_eq!(IFC::ymd(12019, 1, 1).day(), 1); assert_eq!(IFC::ymd(12019, 1, 1).day0(), 0); assert_eq!(IFC::ymd(12020, 3, 1).day(), 1); assert_eq!(IFC::ymd(12020, 3, 1).day0(), 0); assert_eq!(IFC::ymd(12020, 6, 28).day(), 28); assert_eq!(IFC::ymd(12020, 6, 28).day0(), 27); assert_eq!(IFC::ymd(12020, 7, 1).day(), 1); assert_eq!(IFC::ymd(12020, 7, 1).day0(), 0); assert_eq!(IFC::ymd(12019, 13, 1).day(), 1); assert_eq!(IFC::ymd(12019, 13, 1).day0(), 0); assert_eq!(IFC::ymd(12019, 13, 29).day(), 29); assert_eq!(IFC::ymd(12019, 13, 29).day0(), 28); } #[test] fn it_reports_correct_month_in_leap_year() { assert_eq!(IFC::ymd(12020, 1, 1).month(), 1); assert_eq!(IFC::ymd(12020, 1, 1).month0(), 0); assert_eq!(IFC::ymd(12020, 3, 1).month(), 3); assert_eq!(IFC::ymd(12020, 3, 1).month0(), 2); assert_eq!(IFC::ymd(12020, 6, 29).month(), 6); assert_eq!(IFC::ymd(12020, 6, 29).month0(), 5); assert_eq!(IFC::ymd(12020, 7, 1).month(), 7); assert_eq!(IFC::ymd(12020, 7, 1).month0(), 6); } #[test] fn it_reports_correct_day_in_leap_year() { assert_eq!(IFC::ymd(12019, 1, 1).day(), 1); assert_eq!(IFC::ymd(12019, 1, 1).day0(), 0); assert_eq!(IFC::ymd(12020, 6, 28).day(), 28); assert_eq!(IFC::ymd(12020, 6, 28).day0(), 27); assert_eq!(IFC::ymd(12020, 6, 29).day(), 29); assert_eq!(IFC::ymd(12020, 6, 29).day0(), 28); assert_eq!(IFC::ymd(12020, 7, 1).day(), 1); assert_eq!(IFC::ymd(12020, 7, 1).day0(), 0); assert_eq!(IFC::ymd(12019, 13, 1).day(), 1); assert_eq!(IFC::ymd(12019, 13, 1).day0(), 0); assert_eq!(IFC::ymd(12019, 13, 29).day(), 29); assert_eq!(IFC::ymd(12019, 13, 29).day0(), 28); } #[test] fn it_reports_correct_day_of_week() { assert_eq!(IFC::ymd(12019, 1, 1).weekday(), chrono::Weekday::Sun); assert_eq!(IFC::ymd(12019, 6, 1).weekday(), chrono::Weekday::Sun); assert_eq!(IFC::ymd(12019, 6, 28).weekday(), chrono::Weekday::Sat); assert_eq!(IFC::ymd(12019, 7, 1).weekday(), chrono::Weekday::Sun); assert_eq!(IFC::ymd(12019, 13, 28).weekday(), chrono::Weekday::Sat); assert_eq!(IFC::ymd(12019, 13, 29).weekday(), chrono::Weekday::Sat); assert_eq!(IFC::ymd(12020, 6, 28).weekday(), chrono::Weekday::Sat); assert_eq!(IFC::ymd(12020, 6, 29).weekday(), chrono::Weekday::Sat); assert_eq!(IFC::ymd(12020, 7, 1).weekday(), chrono::Weekday::Sun); assert_eq!(IFC::ymd(12020, 13, 28).weekday(), chrono::Weekday::Sat); assert_eq!(IFC::ymd(12020, 13, 29).weekday(), chrono::Weekday::Sat); } }