monorepo/ifc/src/lib.rs

671 lines
17 KiB
Rust
Raw Normal View History

2021-12-15 04:25:48 +00:00
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<DayOfWeek> 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<u32> 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<Month> 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<chrono::Date<chrono::Utc>> for IFC {
fn from(d: chrono::Date<chrono::Utc>) -> IFC {
IFC::from(d.naive_utc())
}
}
2022-01-19 04:26:22 +00:00
impl From<chrono::Date<chrono_tz::Tz>> for IFC {
fn from(d: chrono::Date<chrono_tz::Tz>) -> IFC {
IFC::from(d.naive_utc())
}
}
2021-12-15 04:25:48 +00:00
impl From<chrono::NaiveDate> 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<IFC> {
Some(IFC {
year: (year as u32) + 10000,
ordinal: self.ordinal,
leap_year: is_leap_year(year),
})
}
fn with_month(&self, month: u32) -> Option<IFC> {
Some(IFC::ymd(self.year, month as u8, self.day() as u8))
}
fn with_month0(&self, month: u32) -> Option<IFC> {
Some(IFC::ymd(self.year, month as u8 + 1, self.day() as u8))
}
fn with_day(&self, day: u32) -> Option<IFC> {
Some(IFC::ymd(self.year, self.month() as u8, day as u8))
}
fn with_day0(&self, day: u32) -> Option<IFC> {
Some(IFC::ymd(self.year, self.month() as u8, day as u8 + 1))
}
fn with_ordinal(&self, ordinal: u32) -> Option<IFC> {
Some(IFC {
year: self.year,
ordinal,
leap_year: self.leap_year,
})
}
fn with_ordinal0(&self, ordinal: u32) -> Option<IFC> {
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);
}
}