/*
Copyright 2020-2023, Savanni D'Gerinel <savanni@luminescent-dreams.com>

This file is part of the Luminescent Dreams Tools.

Luminescent Dreams Tools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

Luminescent Dreams Tools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with Lumeto. If not, see <https://www.gnu.org/licenses/>.
*/

use chrono::{Datelike, NaiveDate};
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use thiserror::Error;

#[derive(Clone, Debug, Error, PartialEq)]
pub enum Error {
    #[error("invalid date")]
    InvalidDate,
}

#[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<u8> for Month {
    fn from(val: u8) -> Month {
        Month::from(val as u32)
    }
}

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()
    }
}

fn is_leap_year(year: i32) -> bool {
    NaiveDate::from_ymd_opt(year, 12, 31).unwrap().ordinal() == 366
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum IFC {
    LeapDay(i32),
    YearDay(i32),
    Day(Day),
}

impl IFC {
    pub fn leap_day(year: i32) -> Result<Self, Error> {
        if is_leap_year(year) {
            Ok(Self::LeapDay(year))
        } else {
            Err(Error::InvalidDate)
        }
    }

    pub fn year_day(year: i32) -> Self {
        Self::YearDay(year)
    }

    pub fn ymd(year: i32, month: u8, day: u8) -> Result<Self, Error> {
        if month < 1 || month > 13 {
            Err(Error::InvalidDate)
        } else if day < 1 || day > 28 {
            Err(Error::InvalidDate)
        } else {
            Ok(Self::Day(Day { year, month, day }))
        }
    }

    pub fn year(&self) -> i32 {
        match *self {
            IFC::LeapDay(year) => year,
            IFC::YearDay(year) => year,
            IFC::Day(Day { year, .. }) => year,
        }
    }

    pub fn month(&self) -> Month {
        match *self {
            IFC::Day(Day { month, .. }) => Month::from(month),
            IFC::LeapDay(_) => Month::June,
            IFC::YearDay(_) => Month::December,
        }
    }

    pub fn day(&self) -> u8 {
        match *self {
            IFC::Day(Day { day, .. }) => day,
            IFC::LeapDay(_) => 29,
            IFC::YearDay(_) => 29,
        }
    }

    pub fn weekday(&self) -> DayOfWeek {
        match *self {
            IFC::LeapDay(_) => DayOfWeek::LeapDay,
            IFC::YearDay(_) => DayOfWeek::YearDay,
            IFC::Day(Day { day, .. }) => match (day - 1) % 7 {
                0 => DayOfWeek::Sunday,
                1 => DayOfWeek::Monday,
                2 => DayOfWeek::Tuesday,
                3 => DayOfWeek::Wednesday,
                4 => DayOfWeek::Thursday,
                5 => DayOfWeek::Friday,
                6 => DayOfWeek::Saturday,
                _ => panic!("impossible calculation"),
            },
        }
    }

    pub fn day_ordinal(&self) -> u32 {
        self.day_ordinal0() + 1
    }

    pub fn day_ordinal0(&self) -> u32 {
        match *self {
            IFC::LeapDay(_) => 168,
            IFC::YearDay(year) => {
                if is_leap_year(year) {
                    365
                } else {
                    364
                }
            }
            IFC::Day(Day { year, month, day }) => {
                u32::from(month - 1) * 28
                    + u32::from(day - 1)
                    + if is_leap_year(year) && month > 6 {
                        1
                    } else {
                        0
                    }
            }
        }
    }

    pub fn week_ordinal(&self) -> u32 {
        self.week_ordinal0() + 1
    }

    pub fn week_ordinal0(&self) -> u32 {
        match *self {
            IFC::LeapDay(_) => 0,
            IFC::YearDay(_) => 0,
            IFC::Day(Day { month, day, .. }) => u32::from(month - 1) * 4 + (u32::from(day - 1) / 7),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Day {
    pub year: i32,
    pub month: u8,
    pub day: u8,
}

impl From<chrono::NaiveDate> for IFC {
    fn from(date: chrono::NaiveDate) -> Self {
        if date.month() == 12 && date.day() == 31 {
            Self::YearDay(date.year())
        } else if is_leap_year(date.year()) && date.month() == 6 && date.day() == 17 {
            Self::LeapDay(date.year())
        } else {
            let mut days = date.ordinal();
            if is_leap_year(date.year())
                && date > NaiveDate::from_ymd_opt(date.year(), 6, 17).unwrap()
            {
                days = days - 1;
            }
            let mut month: u8 = (days / 28).try_into().unwrap();
            let mut day: u8 = (days % 28).try_into().unwrap();
            if day == 0 {
                month = month - 1;
                day = 28;
            }
            Self::Day(Day {
                year: date.year(),
                month: month + 1,
                day,
            })
        }
    }
}

impl From<IFC> for chrono::NaiveDate {
    fn from(ifc: IFC) -> Self {
        Self::from_ymd_opt(ifc.year(), 1, 1).unwrap()
            + chrono::naive::Days::new(ifc.day_ordinal0().into())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    use chrono::NaiveDate;

    #[test]
    fn creates_a_day() {
        assert_eq!(IFC::leap_day(12020), Ok(IFC::LeapDay(12020)));
        assert_eq!(IFC::year_day(12020), IFC::YearDay(12020));
        assert_eq!(
            IFC::ymd(12022, 13, 28),
            Ok(IFC::Day(Day {
                year: 12022,
                month: 13,
                day: 28
            }))
        );
    }

    #[test]
    fn rejects_invalid_dates() {
        assert_eq!(IFC::leap_day(12022), Err(Error::InvalidDate));
        assert_eq!(IFC::ymd(12022, 13, 0), Err(Error::InvalidDate));
        assert_eq!(IFC::ymd(12022, 14, 1), Err(Error::InvalidDate));
    }

    #[test]
    fn it_expresses_day_of_week() {
        assert_eq!(IFC::leap_day(12020).unwrap().weekday(), DayOfWeek::LeapDay);
        assert_eq!(IFC::year_day(12022).weekday(), DayOfWeek::YearDay);
        assert_eq!(IFC::ymd(12022, 1, 1).unwrap().weekday(), DayOfWeek::Sunday);
        assert_eq!(IFC::ymd(12022, 1, 2).unwrap().weekday(), DayOfWeek::Monday);
        assert_eq!(IFC::ymd(12022, 1, 3).unwrap().weekday(), DayOfWeek::Tuesday);
        assert_eq!(
            IFC::ymd(12022, 1, 4).unwrap().weekday(),
            DayOfWeek::Wednesday
        );
        assert_eq!(
            IFC::ymd(12022, 1, 5).unwrap().weekday(),
            DayOfWeek::Thursday
        );
        assert_eq!(IFC::ymd(12022, 1, 6).unwrap().weekday(), DayOfWeek::Friday);
        assert_eq!(
            IFC::ymd(12022, 1, 7).unwrap().weekday(),
            DayOfWeek::Saturday
        );
        assert_eq!(IFC::ymd(12022, 1, 8).unwrap().weekday(), DayOfWeek::Sunday);
    }

    #[test]
    fn it_expresses_month() {
        assert_eq!(IFC::ymd(12022, 1, 4).unwrap().month(), Month::January);
        assert_eq!(IFC::ymd(12022, 12, 1).unwrap().month(), Month::November);
        assert_eq!(IFC::leap_day(12020).unwrap().month(), Month::June);
        assert_eq!(IFC::year_day(12020).month(), Month::December);
    }

    #[test]
    fn it_reports_ordinal_days() {
        assert_eq!(IFC::ymd(12022, 1, 1).unwrap().day_ordinal(), 1);
        assert_eq!(IFC::ymd(12022, 1, 1).unwrap().day_ordinal0(), 0);

        assert_eq!(IFC::ymd(12022, 1, 28).unwrap().day_ordinal(), 28);
        assert_eq!(IFC::ymd(12022, 1, 28).unwrap().day_ordinal0(), 27);

        assert_eq!(IFC::ymd(12022, 2, 1).unwrap().day_ordinal(), 29);
        assert_eq!(IFC::ymd(12022, 2, 1).unwrap().day_ordinal0(), 28);

        assert_eq!(IFC::ymd(12022, 3, 1).unwrap().day_ordinal(), 57);
        assert_eq!(IFC::ymd(12022, 3, 1).unwrap().day_ordinal0(), 56);

        assert_eq!(IFC::ymd(12022, 4, 1).unwrap().day_ordinal(), 85);
        assert_eq!(IFC::ymd(12022, 4, 1).unwrap().day_ordinal0(), 84);

        assert_eq!(IFC::ymd(12022, 5, 1).unwrap().day_ordinal(), 113);
        assert_eq!(IFC::ymd(12022, 5, 1).unwrap().day_ordinal0(), 112);

        assert_eq!(IFC::ymd(12022, 6, 1).unwrap().day_ordinal(), 141);
        assert_eq!(IFC::ymd(12022, 6, 1).unwrap().day_ordinal0(), 140);

        assert_eq!(IFC::ymd(12022, 7, 1).unwrap().day_ordinal(), 169);
        assert_eq!(IFC::ymd(12022, 7, 1).unwrap().day_ordinal0(), 168);

        assert_eq!(IFC::ymd(12022, 8, 1).unwrap().day_ordinal(), 197);
        assert_eq!(IFC::ymd(12022, 8, 1).unwrap().day_ordinal0(), 196);

        assert_eq!(IFC::ymd(12022, 9, 1).unwrap().day_ordinal(), 225);
        assert_eq!(IFC::ymd(12022, 9, 1).unwrap().day_ordinal0(), 224);

        assert_eq!(IFC::ymd(12022, 10, 1).unwrap().day_ordinal(), 253);
        assert_eq!(IFC::ymd(12022, 10, 1).unwrap().day_ordinal0(), 252);

        assert_eq!(IFC::ymd(12022, 11, 1).unwrap().day_ordinal(), 281);
        assert_eq!(IFC::ymd(12022, 11, 1).unwrap().day_ordinal0(), 280);

        assert_eq!(IFC::ymd(12022, 12, 1).unwrap().day_ordinal(), 309);
        assert_eq!(IFC::ymd(12022, 12, 1).unwrap().day_ordinal0(), 308);

        assert_eq!(IFC::ymd(12022, 13, 1).unwrap().day_ordinal(), 337);
        assert_eq!(IFC::ymd(12022, 13, 1).unwrap().day_ordinal0(), 336);

        assert_eq!(IFC::ymd(12022, 13, 28).unwrap().day_ordinal(), 364);
        assert_eq!(IFC::ymd(12022, 13, 28).unwrap().day_ordinal0(), 363);

        assert_eq!(IFC::year_day(12022).day_ordinal(), 365);
    }

    #[test]
    fn it_reports_ordinal_days_on_leap_year() {
        assert_eq!(IFC::ymd(12020, 1, 1).unwrap().day_ordinal(), 1);
        assert_eq!(IFC::ymd(12020, 1, 1).unwrap().day_ordinal0(), 0);

        assert_eq!(IFC::ymd(12020, 1, 28).unwrap().day_ordinal(), 28);
        assert_eq!(IFC::ymd(12020, 1, 28).unwrap().day_ordinal0(), 27);

        assert_eq!(IFC::ymd(12020, 2, 1).unwrap().day_ordinal(), 29);
        assert_eq!(IFC::ymd(12020, 2, 1).unwrap().day_ordinal0(), 28);

        assert_eq!(IFC::ymd(12020, 3, 1).unwrap().day_ordinal(), 57);
        assert_eq!(IFC::ymd(12020, 3, 1).unwrap().day_ordinal0(), 56);

        assert_eq!(IFC::ymd(12020, 4, 1).unwrap().day_ordinal(), 85);
        assert_eq!(IFC::ymd(12020, 4, 1).unwrap().day_ordinal0(), 84);

        assert_eq!(IFC::ymd(12020, 5, 1).unwrap().day_ordinal(), 113);
        assert_eq!(IFC::ymd(12020, 5, 1).unwrap().day_ordinal0(), 112);

        assert_eq!(IFC::ymd(12020, 6, 1).unwrap().day_ordinal(), 141);
        assert_eq!(IFC::ymd(12020, 6, 1).unwrap().day_ordinal0(), 140);

        assert_eq!(IFC::leap_day(12020).unwrap().day_ordinal(), 169);
        assert_eq!(IFC::leap_day(12020).unwrap().day_ordinal0(), 168);

        assert_eq!(IFC::ymd(12020, 7, 1).unwrap().day_ordinal(), 170);
        assert_eq!(IFC::ymd(12020, 7, 1).unwrap().day_ordinal0(), 169);

        assert_eq!(IFC::ymd(12020, 8, 1).unwrap().day_ordinal(), 198);
        assert_eq!(IFC::ymd(12020, 8, 1).unwrap().day_ordinal0(), 197);

        assert_eq!(IFC::ymd(12020, 9, 1).unwrap().day_ordinal(), 226);
        assert_eq!(IFC::ymd(12020, 9, 1).unwrap().day_ordinal0(), 225);

        assert_eq!(IFC::ymd(12020, 10, 1).unwrap().day_ordinal(), 254);
        assert_eq!(IFC::ymd(12020, 10, 1).unwrap().day_ordinal0(), 253);

        assert_eq!(IFC::ymd(12020, 11, 1).unwrap().day_ordinal(), 282);
        assert_eq!(IFC::ymd(12020, 11, 1).unwrap().day_ordinal0(), 281);

        assert_eq!(IFC::ymd(12020, 12, 1).unwrap().day_ordinal(), 310);
        assert_eq!(IFC::ymd(12020, 12, 1).unwrap().day_ordinal0(), 309);

        assert_eq!(IFC::ymd(12020, 13, 1).unwrap().day_ordinal(), 338);
        assert_eq!(IFC::ymd(12020, 13, 1).unwrap().day_ordinal0(), 337);

        assert_eq!(IFC::ymd(12020, 13, 28).unwrap().day_ordinal(), 365);
        assert_eq!(IFC::ymd(12020, 13, 28).unwrap().day_ordinal0(), 364);

        assert_eq!(IFC::year_day(12020).day_ordinal(), 366);
    }

    #[test]
    fn it_reports_ordinal_weeks() {
        assert_eq!(IFC::ymd(12022, 1, 1).unwrap().week_ordinal(), 1);
        assert_eq!(IFC::ymd(12022, 1, 1).unwrap().week_ordinal0(), 0);
        assert_eq!(IFC::ymd(12022, 1, 4).unwrap().week_ordinal(), 1);
        assert_eq!(IFC::ymd(12022, 1, 4).unwrap().week_ordinal0(), 0);
        assert_eq!(IFC::ymd(12022, 1, 7).unwrap().week_ordinal(), 1);
        assert_eq!(IFC::ymd(12022, 1, 7).unwrap().week_ordinal0(), 0);
        assert_eq!(IFC::ymd(12022, 1, 8).unwrap().week_ordinal(), 2);
        assert_eq!(IFC::ymd(12022, 1, 8).unwrap().week_ordinal0(), 1);
        assert_eq!(IFC::ymd(12022, 2, 1).unwrap().week_ordinal(), 5);
        assert_eq!(IFC::ymd(12022, 2, 1).unwrap().week_ordinal0(), 4);
        assert_eq!(IFC::ymd(12022, 13, 28).unwrap().week_ordinal(), 52);
        assert_eq!(IFC::ymd(12022, 13, 28).unwrap().week_ordinal0(), 51);
    }

    #[test]
    fn it_converts_from_gregorian() {
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12022, 1, 1).unwrap()),
            IFC::ymd(12022, 1, 1).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12022, 1, 2).unwrap()),
            IFC::ymd(12022, 1, 2).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12022, 1, 3).unwrap()),
            IFC::ymd(12022, 1, 3).unwrap()
        );
        assert_eq!(
            IFC::from(chrono::NaiveDate::from_ymd_opt(2023, 01, 26).unwrap()),
            IFC::ymd(2023, 1, 26).unwrap()
        );
        assert_eq!(
            IFC::from(chrono::NaiveDate::from_ymd_opt(2023, 01, 27).unwrap()),
            IFC::ymd(2023, 1, 27).unwrap()
        );
        assert_eq!(
            IFC::from(chrono::NaiveDate::from_ymd_opt(2023, 01, 28).unwrap()),
            IFC::ymd(2023, 1, 28).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12022, 1, 29).unwrap()),
            IFC::ymd(12022, 2, 1).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12022, 1, 30).unwrap()),
            IFC::ymd(12022, 2, 2).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12022, 2, 26).unwrap()),
            IFC::ymd(12022, 3, 1).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12022, 3, 26).unwrap()),
            IFC::ymd(12022, 4, 1).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12022, 4, 23).unwrap()),
            IFC::ymd(12022, 5, 1).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12022, 5, 21).unwrap()),
            IFC::ymd(12022, 6, 1).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12022, 6, 18).unwrap()),
            IFC::ymd(12022, 7, 1).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12022, 7, 16).unwrap()),
            IFC::ymd(12022, 8, 1).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12022, 8, 13).unwrap()),
            IFC::ymd(12022, 9, 1).unwrap()
        );
        assert_eq!(
            IFC::from(chrono::NaiveDate::from_ymd_opt(2023, 08, 12).unwrap()),
            IFC::ymd(2023, 8, 28).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12022, 9, 10).unwrap()),
            IFC::ymd(12022, 10, 1).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12022, 10, 8).unwrap()),
            IFC::ymd(12022, 11, 1).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12022, 11, 5).unwrap()),
            IFC::ymd(12022, 12, 1).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12022, 12, 3).unwrap()),
            IFC::ymd(12022, 13, 1).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12022, 12, 31).unwrap()),
            IFC::YearDay(12022)
        );
    }

    #[test]
    fn it_converts_from_gregorian_on_leap_year() {
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12020, 1, 1).unwrap()),
            IFC::ymd(12020, 1, 1).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12020, 1, 29).unwrap()),
            IFC::ymd(12020, 2, 1).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12020, 2, 26).unwrap()),
            IFC::ymd(12020, 3, 1).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12020, 2, 28).unwrap()),
            IFC::ymd(12020, 3, 3).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12020, 2, 29).unwrap()),
            IFC::ymd(12020, 3, 4).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12020, 3, 1).unwrap()),
            IFC::ymd(12020, 3, 5).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12020, 3, 25).unwrap()),
            IFC::ymd(12020, 4, 1).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12020, 4, 22).unwrap()),
            IFC::ymd(12020, 5, 1).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12020, 5, 20).unwrap()),
            IFC::ymd(12020, 6, 1).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12020, 6, 17).unwrap()),
            IFC::LeapDay(12020)
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12020, 6, 18).unwrap()),
            IFC::ymd(12020, 7, 1).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12020, 7, 16).unwrap()),
            IFC::ymd(12020, 8, 1).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12020, 8, 13).unwrap()),
            IFC::ymd(12020, 9, 1).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12020, 9, 10).unwrap()),
            IFC::ymd(12020, 10, 1).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12020, 10, 8).unwrap()),
            IFC::ymd(12020, 11, 1).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12020, 11, 5).unwrap()),
            IFC::ymd(12020, 12, 1).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12020, 12, 3).unwrap()),
            IFC::ymd(12020, 13, 1).unwrap()
        );
        assert_eq!(
            IFC::from(NaiveDate::from_ymd_opt(12020, 12, 31).unwrap()),
            IFC::YearDay(12020)
        );
    }

    #[test]
    fn it_converts_to_gregorian() {
        assert_eq!(
            NaiveDate::from(IFC::ymd(12020, 1, 1).unwrap()),
            NaiveDate::from_ymd_opt(12020, 1, 1).unwrap()
        );

        assert_eq!(
            NaiveDate::from(IFC::leap_day(12020).unwrap()),
            NaiveDate::from_ymd_opt(12020, 6, 17).unwrap()
        );

        assert_eq!(
            NaiveDate::from(IFC::ymd(12020, 3, 4).unwrap()),
            NaiveDate::from_ymd_opt(12020, 2, 29).unwrap()
        );

        assert_eq!(
            NaiveDate::from(IFC::year_day(12020)),
            NaiveDate::from_ymd_opt(12020, 12, 31).unwrap()
        );
    }

    /*
    #[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 report_leap_day() {}

    #[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(), DayOfWeek::Sunday);
        assert_eq!(IFC::ymd(12019, 6, 1).weekday(), DayOfWeek::Sunday);
        assert_eq!(IFC::ymd(12019, 6, 28).weekday(), DayOfWeek::Saturday);
        assert_eq!(IFC::ymd(12019, 7, 1).weekday(), DayOfWeek::Sunday);
        assert_eq!(IFC::ymd(12019, 13, 28).weekday(), DayOfWeek::Saturday);
        assert_eq!(IFC::ymd(12019, 13, 29).weekday(), DayOfWeek::Saturday);

        assert_eq!(IFC::ymd(12020, 6, 28).weekday(), DayOfWeek::Saturday);
        assert_eq!(IFC::ymd(12020, 6, 29).weekday(), DayOfWeek::Saturday);
        assert_eq!(IFC::ymd(12020, 7, 1).weekday(), DayOfWeek::Sunday);
        assert_eq!(IFC::ymd(12020, 13, 28).weekday(), DayOfWeek::Saturday);
        assert_eq!(IFC::ymd(12020, 13, 29).weekday(), DayOfWeek::Saturday);

        assert_eq!(IFC::ymd(12022, 13, 31)
    }
    */
}