use std::fs::File;
use std::io::Read;
use std::str::FromStr;

use chrono::{Datelike, FixedOffset as ChronoFixedOffset, NaiveDate, Timelike, Utc as ChronoUtc};
use strum::EnumMessage;

use speedate::{
    float_parse_bytes, float_parse_str, int_parse_bytes, int_parse_str, Date, DateConfig, DateConfigBuilder, DateTime,
    DateTimeConfig, DateTimeConfigBuilder, Duration, IntFloat, MicrosecondsPrecisionOverflowBehavior, ParseError, Time,
    TimeConfig, TimeConfigBuilder, TimestampUnit,
};

/// macro for expected values
macro_rules! expect_ok_or_error {
    ($type:ty, $name:ident, ok, $input:literal, $expected:expr) => {
        paste::item! {
            #[test]
            fn [< expect_ $name _ok >]() {
                let v = <$type>::parse_str($input).unwrap();
                assert_eq!(v.to_string(), $expected);
            }
        }
    };
    ($type:ty, $name:ident, err, $input:literal, $error:expr) => {
        paste::item! {
            #[test]
            fn [< expect_ $name _ $error:snake _error >]() {
                match <$type>::parse_str($input) {
                    Ok(t) => panic!("unexpectedly valid: {:?} -> {:?}", $input, t),
                    Err(e) => assert_eq!(e, ParseError::$error),
                }
            }
        }
    };
}

/// macro for comparing a type's `from_str` and `parse_str` methods
macro_rules! expect_fromstr_matches_parse_str {
    ($type:ty, $name:ident, ok, $_expected:expr, $example:expr) => {
        paste::item! {
            #[test]
            fn [< expect_ $name _fromstr_matches_parse_str_ok >]() {
                expect_fromstr_matches_parse_str!(@body $type, $example);
            }
        }
    };
    ($type:ty, $name:ident, err, $error:expr, $example:expr) => {
        paste::item! {
            #[test]
            fn [< expect_ $name _fromstr_matches_parse_str_ $error:snake _error >]() {
                expect_fromstr_matches_parse_str!(@body $type, $example);
            }
        }
    };
    (@body $type:ty, $example:expr) => {{
        let fromstr = <$type>::from_str($example);
        let parse_str = <$type>::parse_str($example);
        assert_eq!(fromstr, parse_str, "fromstr: {fromstr:?}, parse_str: {parse_str:?}");
    }};
}

/// macro to define many tests for expected values
macro_rules! param_tests {
    ($type:ty, $($name:ident: $ok_or_err:ident => $input:literal, $expected:expr;)*) => {
        $(
            expect_ok_or_error!($type, $name, $ok_or_err, $input, $expected);
            expect_fromstr_matches_parse_str!($type, $name, $ok_or_err, $expected, $input);
        )*
    }
}

#[test]
fn date() {
    let d = Date::parse_str("2020-01-01").unwrap();
    assert_eq!(
        d,
        Date {
            year: 2020,
            month: 1,
            day: 1
        }
    );
    assert_eq!(d.to_string(), "2020-01-01");
    assert_eq!(format!("{d:?}"), "Date { year: 2020, month: 1, day: 1 }");
}

#[test]
fn date_bytes_err() {
    // https://github.com/python/cpython/blob/5849af7a80166e9e82040e082f22772bd7cf3061/Lib/test/datetimetester.py#L3237
    // bytes of '\ud800'
    let bytes: Vec<u8> = vec![92, 117, 100, 56, 48, 48];
    match Date::parse_bytes_rfc3339(&bytes) {
        Ok(_) => panic!("unexpectedly valid"),
        Err(e) => assert_eq!(e, ParseError::TooShort),
    }
    let bytes: Vec<u8> = vec![b'2', b'0', b'0', b'0', 92, 117, 100, 56, 48, 48];
    match Date::parse_bytes_rfc3339(&bytes) {
        Ok(_) => panic!("unexpectedly valid"),
        Err(e) => assert_eq!(e, ParseError::InvalidCharDateSep),
    }
}

#[test]
fn error_str() {
    let error = match Date::parse_str_rfc3339("123") {
        Ok(_) => panic!("unexpectedly valid"),
        Err(e) => e,
    };
    assert_eq!(error, ParseError::TooShort);
    assert_eq!(error.to_string(), "too_short");
    assert_eq!(error.get_documentation(), Some("input is too short"));
}

param_tests! {
    Date,
    // date_short_3: err => "123", TooShort;
    date_short_9: err => "2000:12:1", TooShort;
    date: err => "xxxx:12:31", InvalidCharYear;
    date_year_sep: err => "2020x12:13", InvalidCharDateSep;
    date_mo_sep: err => "2020-12x13", InvalidCharDateSep;
    date: err => "2020-13-01", OutOfRangeMonth;
    date: err => "2020-04-31", OutOfRangeDay;
    date_extra_space: err => "2020-04-01 ", ExtraCharacters;
    date_extra_xxx: err => "2020-04-01xxx", ExtraCharacters;
    // leap year dates
    date_simple: ok => "2020-04-01", "2020-04-01";
    date_normal_not_leap: ok => "2003-02-28", "2003-02-28";
    date_normal_not_leap: err => "2003-02-29", OutOfRangeDay;
    date_normal_leap_year: ok => "2004-02-29", "2004-02-29";
    date_special_100_not_leap: err => "1900-02-29", OutOfRangeDay;
    date_special_400_leap: ok => "2000-02-29", "2000-02-29";
    date_special_1600ad_leap: ok => "1600-02-29", "1600-02-29";
    date_special_1200ad_leap: ok => "1200-02-29", "1200-02-29";
    date_special_1201ad_not_leap: err => "1201-02-29", OutOfRangeDay;
    date_special_1202ad_not_leap: err => "1202-02-29", OutOfRangeDay;
    date_special_1203ad_not_leap: err => "1203-02-29", OutOfRangeDay;
    date_special_1204ad_leap: ok => "1204-02-29", "1204-02-29";
    date_special_1300ad_not_leap: err => "1300-02-29", OutOfRangeDay;
    date_special_1400ad_not_leap: err => "1400-02-29", OutOfRangeDay;
    date_special_1500ad_not_leap: err => "1500-02-29", OutOfRangeDay;
    date_special_1bc_leap: ok => "0000-02-29", "0000-02-29";
    date_special_1ad_not_leap: err => "0001-02-29", OutOfRangeDay;
    date_special_4ad_leap: ok => "0004-02-29", "0004-02-29";
    date_special_100ad_not_leap: err => "0100-02-29", OutOfRangeDay;
    date_special_200ad_not_leap: err => "0200-02-29", OutOfRangeDay;
    date_special_300ad_not_leap: err => "0300-02-29", OutOfRangeDay;
    date_special_400ad_leap: ok => "0400-02-29", "0400-02-29";
    date_special_404ad_leap: ok => "0404-02-29", "0404-02-29";
    date_unix_before_watershed: ok => "19999872000", "2603-10-10";
    date_unix_after_watershed: ok => "20044800000", "1970-08-21";
    date_unix_too_low: err => "-62167219200001", DateTooSmall;
}

#[test]
fn date_from_timestamp_extremes() {
    match Date::from_timestamp(i64::MIN, false, &DateConfigBuilder::new().build()) {
        Ok(dt) => panic!("unexpectedly valid, {dt}"),
        Err(e) => assert_eq!(e, ParseError::DateTooSmall),
    }
    match Date::from_timestamp(i64::MAX, false, &DateConfigBuilder::new().build()) {
        Ok(dt) => panic!("unexpectedly valid, {dt}"),
        Err(e) => assert_eq!(e, ParseError::DateTooLarge),
    }
    let d = Date::from_timestamp(-62_167_219_200_000, false, &DateConfigBuilder::new().build()).unwrap();
    assert_eq!(d.to_string(), "0000-01-01");
    match Date::from_timestamp(-62_167_219_200_001, false, &DateConfigBuilder::new().build()) {
        Ok(dt) => panic!("unexpectedly valid, {dt}"),
        Err(e) => assert_eq!(e, ParseError::DateTooSmall),
    }
    let d = Date::from_timestamp(253_402_300_799_000, false, &DateConfigBuilder::new().build()).unwrap();
    assert_eq!(d.to_string(), "9999-12-31");
    match Date::from_timestamp(253_402_300_800_000, false, &DateConfigBuilder::new().build()) {
        Ok(dt) => panic!("unexpectedly valid, {dt}"),
        Err(e) => assert_eq!(e, ParseError::DateTooLarge),
    }
}

#[test]
fn date_from_timestamp_special_dates() {
    let d = Date::from_timestamp(-11_676_096_000 + 1000, false, &DateConfigBuilder::new().build()).unwrap();
    assert_eq!(d.to_string(), "1600-01-01");
    // check if there is any error regarding offset at the second level
    // and if rounding down works
    let d = Date::from_timestamp(-11_676_096_000 + 86399, false, &DateConfigBuilder::new().build()).unwrap();
    assert_eq!(d.to_string(), "1600-01-01");
    let d = Date::from_timestamp(-11_673_417_600, false, &DateConfigBuilder::new().build()).unwrap();
    assert_eq!(d.to_string(), "1600-02-01");
}

#[test]
fn date_watershed() {
    let dt = Date::from_timestamp(20_000_000_000, false, &DateConfigBuilder::new().build()).unwrap();
    assert_eq!(dt.to_string(), "2603-10-11");
    let dt = Date::from_timestamp(20_000_000_001, false, &DateConfigBuilder::new().build()).unwrap();
    assert_eq!(dt.to_string(), "1970-08-20");
    let dt = Date::from_timestamp(-20_000_000_000, false, &DateConfigBuilder::new().build()).unwrap();
    assert_eq!(dt.to_string(), "1336-03-23");
    let dt = Date::from_timestamp(-20_000_000_001, false, &DateConfigBuilder::new().build()).unwrap();
    assert_eq!(dt.to_string(), "1969-05-14");
}

#[test]
fn date_from_timestamp_milliseconds() {
    let d1 = Date::from_timestamp(1_654_472_524, false, &DateConfigBuilder::new().build()).unwrap();
    assert_eq!(
        d1,
        Date {
            year: 2022,
            month: 6,
            day: 5
        }
    );
    let d2 = Date::from_timestamp(1_654_472_524_000, false, &DateConfigBuilder::new().build()).unwrap();
    assert_eq!(d2, d1);
}

#[test]
fn date_timestamp_ms_method() {
    let d = Date::parse_str("2022-06-07").unwrap();
    assert_eq!(d.timestamp_ms(), 1_654_560_000_000);
}

fn try_date_timestamp(ts: i64, check_timestamp: bool) {
    let chrono_date = chrono::DateTime::from_timestamp(ts, 0).unwrap().date_naive();
    let d = Date::from_timestamp(ts, false, &DateConfigBuilder::new().build()).unwrap();
    // println!("{} => {:?}", ts, d);
    assert_eq!(
        d,
        Date {
            year: chrono_date.year() as u16,
            month: chrono_date.month() as u8,
            day: chrono_date.day() as u8,
        },
        "timestamp: {ts} => {chrono_date}"
    );
    if check_timestamp {
        assert_eq!(d.timestamp(), ts);
    }
}

#[test]
fn date_from_timestamp_range() {
    for ts in (0..4_000_000_000).step_by(86_400) {
        try_date_timestamp(ts, true);
        try_date_timestamp(ts + 40_000, false);
        try_date_timestamp(-ts, true);
        try_date_timestamp(-ts - 40_000, false);
    }
}

#[test]
fn date_comparison() {
    let d1 = Date::parse_str("2020-02-03").unwrap();
    let d2 = Date::parse_str("2021-01-02").unwrap();
    assert!(d1 < d2);
    assert!(d1 <= d2);
    assert!(d1 <= d1.clone());
    assert!(d2 > d1);
    assert!(d2 >= d1);
    assert!(d2 >= d2.clone());
}

#[test]
fn date_timestamp_exact() {
    let d = Date::from_timestamp(1_654_560_000, true, &DateConfigBuilder::new().build()).unwrap();
    assert_eq!(d.to_string(), "2022-06-07");
    assert_eq!(d.timestamp(), 1_654_560_000);

    match Date::from_timestamp(1_654_560_001, true, &DateConfigBuilder::new().build()) {
        Ok(d) => panic!("unexpectedly valid, {d}"),
        Err(e) => assert_eq!(e, ParseError::DateNotExact),
    }

    // milliseconds
    let d = Date::from_timestamp(1_654_560_000_000, true, &DateConfigBuilder::new().build()).unwrap();
    assert_eq!(d.to_string(), "2022-06-07");
    assert_eq!(d.timestamp(), 1_654_560_000);

    match Date::from_timestamp(1_654_560_000_001, true, &DateConfigBuilder::new().build()) {
        Ok(d) => panic!("unexpectedly valid, {d}"),
        Err(e) => assert_eq!(e, ParseError::DateNotExact),
    }
}

macro_rules! date_from_timestamp {
    ($($year:literal, $month:literal, $day:literal;)*) => {
        $(
        paste::item! {
            #[test]
            fn [< date_from_timestamp_ $year _ $month _ $day >]() {
                let chrono_date = NaiveDate::from_ymd_opt($year, $month, $day).unwrap();
                let ts = chrono_date.and_hms_opt(0, 0, 0).unwrap().and_utc().timestamp();
                let d = Date::from_timestamp(ts, false, &DateConfigBuilder::new().build()).unwrap();
                assert_eq!(
                    d,
                    Date {
                        year: $year,
                        month: $month,
                        day: $day,
                    },
                    "timestamp: {} => {}",
                    ts,
                    chrono_date
                );
            }
        }
        )*
    }
}

date_from_timestamp! {
    1970, 1, 1;
    1970, 1, 31;
    1970, 2, 1;
    1970, 2, 28;
    1970, 3, 1;
    1600, 1, 1;
    1601, 1, 1;
    1700, 1, 1;
    1842, 8, 20;
    1900, 1, 1;
    1900, 6, 1;
    1901, 1, 1;
    1904, 1, 1;
    1904, 2, 29;
    1904, 6, 1;
    1924, 6, 1;
    2200, 1, 1;
}

#[test]
fn date_today() {
    let today = Date::today(0).unwrap();
    let chrono_now = ChronoUtc::now();
    assert_eq!(
        today,
        Date {
            year: chrono_now.year() as u16,
            month: chrono_now.month() as u8,
            day: chrono_now.day() as u8,
        }
    );
}

#[test]
fn date_today_offset() {
    for offset in (-86399..86399).step_by(1000) {
        let today = Date::today(offset).unwrap();
        let chrono_now_utc = ChronoUtc::now();
        let chrono_tz = ChronoFixedOffset::east_opt(offset).unwrap();
        let chrono_now = chrono_now_utc.with_timezone(&chrono_tz);
        assert_eq!(
            today,
            Date {
                year: chrono_now.year() as u16,
                month: chrono_now.month() as u8,
                day: chrono_now.day() as u8,
            }
        );
    }
}

macro_rules! time_from_timestamp {
    ($($ts_secs:literal, $ts_micro:literal => $hour:literal, $minute:literal, $second:literal, $microsecond:literal;)*) => {
        $(
        paste::item! {
            #[test]
            fn [< time_from_timestamp_ $hour _ $minute _ $second _ $microsecond >]() {
                let d = Time::from_timestamp($ts_secs, $ts_micro).unwrap();
                assert_eq!(d, Time {
                    hour: $hour,
                    minute: $minute,
                    second: $second,
                    microsecond: $microsecond,
                    tz_offset: None,
                },
                "timestamp: {} => {}:{}:{}.{}",
                $ts_secs,
                $hour, $minute, $second, $microsecond);
            }
        }
        )*
    }
}

time_from_timestamp! {
    0, 0 => 0, 0, 0, 0;
    1, 0 => 0, 0, 1, 0;
    3600, 0 => 1, 0, 0, 0;
    3700, 0 => 1, 1, 40, 0;
    86399, 0 => 23, 59, 59, 0;
    0, 100 => 0, 0, 0, 100;
    0, 5_000_000 => 0, 0, 5, 0;
    36_005, 1_500_000 => 10, 0, 6, 500_000;
}

#[test]
fn time_from_timestamp_error() {
    match Time::from_timestamp(86400, 0) {
        Ok(_) => panic!("unexpectedly valid"),
        Err(e) => assert_eq!(e, ParseError::TimeTooLarge),
    }
    match Time::from_timestamp(86390, 10_000_000) {
        Ok(_) => panic!("unexpectedly valid"),
        Err(e) => assert_eq!(e, ParseError::TimeTooLarge),
    }
    match Time::from_timestamp(u32::MAX, u32::MAX) {
        Ok(_) => panic!("unexpectedly valid"),
        Err(e) => assert_eq!(e, ParseError::TimeTooLarge),
    }
}

fn try_datetime_timestamp(chrono_dt: chrono::DateTime<ChronoUtc>) {
    let ts = chrono_dt.timestamp();
    let dt = DateTime::from_timestamp(ts, chrono_dt.nanosecond() / 1_000).unwrap();
    // println!("{} ({}) => {}", ts, chrono_dt, dt);
    assert_eq!(
        dt,
        DateTime {
            date: Date {
                year: chrono_dt.year() as u16,
                month: chrono_dt.month() as u8,
                day: chrono_dt.day() as u8,
            },
            time: Time {
                hour: chrono_dt.hour() as u8,
                minute: chrono_dt.minute() as u8,
                second: chrono_dt.second() as u8,
                microsecond: chrono_dt.nanosecond() / 1_000,
                tz_offset: None,
            },
        },
        "timestamp: {ts} => {chrono_dt}"
    );
    assert_eq!(dt.timestamp(), ts);
}

macro_rules! datetime_from_timestamp {
    ($($year:literal, $month:literal, $day:literal, $hour:literal, $minute:literal, $second:literal, $microsecond:literal;)*) => {
        $(
        paste::item! {
            #[test]
            fn [< datetime_from_timestamp_ $year _ $month _ $day _t_ $hour _ $minute _ $second _ $microsecond >]() {
                let chrono_dt = NaiveDate::from_ymd_opt($year, $month, $day).unwrap().and_hms_nano_opt($hour, $minute, $second, $microsecond * 1_000).unwrap().and_utc();
                try_datetime_timestamp(chrono_dt);
            }
        }
        )*
    }
}

datetime_from_timestamp! {
    1970, 1, 1, 0, 0, 0, 0;
    1970, 1, 1, 0, 0, 1, 0;
    1970, 1, 1, 0, 1, 0, 0;
    1970, 1, 2, 0, 0, 0, 0;
    1970, 1, 2, 0, 0, 0, 500000;
    1969, 12, 30, 15, 51, 29, 10630;
}

#[test]
fn datetime_from_timestamp_range() {
    for ts in (0..157_766_400).step_by(757) {
        try_datetime_timestamp(chrono::DateTime::from_timestamp(ts, 0).unwrap());
        try_datetime_timestamp(chrono::DateTime::from_timestamp(-ts, 0).unwrap());
    }
}

#[test]
fn datetime_from_timestamp_specific() {
    let dt = DateTime::from_timestamp(-11676095999, 4291493).unwrap();
    assert_eq!(dt.to_string(), "1600-01-01T00:00:05.291493");
    let dt = DateTime::from_timestamp(-1, 1667444).unwrap();
    assert_eq!(dt.to_string(), "1970-01-01T00:00:00.667444");
    let dt = DateTime::from_timestamp(32_503_680_000_000, 0).unwrap();
    assert_eq!(dt.to_string(), "3000-01-01T00:00:00");
    let dt = DateTime::from_timestamp(-11_676_096_000, 0).unwrap();
    assert_eq!(dt.to_string(), "1600-01-01T00:00:00");
    let dt = DateTime::from_timestamp(1_095_216_660_480, 3221223).unwrap();
    assert_eq!(dt.to_string(), "2004-09-15T02:51:03.701223");

    let d = DateTime::from_timestamp(253_402_300_799_000, 999999).unwrap();
    assert_eq!(d.to_string(), "9999-12-31T23:59:59.999999");
    match Date::from_timestamp(253_402_300_800_000, false, &DateConfigBuilder::new().build()) {
        Ok(dt) => panic!("unexpectedly valid, {dt}"),
        Err(e) => assert_eq!(e, ParseError::DateTooLarge),
    }
}

#[test]
fn datetime_watershed() {
    let dt = DateTime::from_timestamp(20_000_000_000, 0).unwrap();
    assert_eq!(dt.to_string(), "2603-10-11T11:33:20");
    let dt = DateTime::from_timestamp(20_000_000_001, 0).unwrap();
    assert_eq!(dt.to_string(), "1970-08-20T11:33:20.001000");
    let dt = DateTime::from_timestamp(-20_000_000_001, 0).unwrap();
    assert_eq!(dt.to_string(), "1969-05-14T12:26:39.999000");
    let dt = DateTime::from_timestamp(-20_000_000_000, 0).unwrap();
    assert_eq!(dt.to_string(), "1336-03-23T12:26:40");
}

#[test]
fn datetime_now() {
    let speedate_now = DateTime::now(0).unwrap();
    let chrono_now = ChronoUtc::now();
    let diff = speedate_now.timestamp() as f64 - chrono_now.timestamp() as f64;
    assert!(diff.abs() < 0.1);
}

#[test]
fn datetime_now_offset() {
    let speedate_now = DateTime::now(3600).unwrap();
    let chrono_now = ChronoUtc::now();
    let diff = speedate_now.timestamp() as f64 - chrono_now.timestamp() as f64 - 3600.0;
    assert!(diff.abs() < 0.1);
    let diff = speedate_now.timestamp_tz() as f64 - chrono_now.timestamp() as f64;
    assert!(diff.abs() < 0.1);
}

#[test]
fn datetime_with_tz_offset() {
    let dt_z = DateTime::parse_str("2022-01-01T12:13:14.567+00:00").unwrap();

    let dt_m8 = dt_z.with_timezone_offset(Some(-8 * 3600)).unwrap();
    assert_eq!(dt_m8.to_string(), "2022-01-01T12:13:14.567000-08:00");

    let dt_naive = dt_z.with_timezone_offset(None).unwrap();
    assert_eq!(dt_naive.to_string(), "2022-01-01T12:13:14.567000");

    let dt_naive = DateTime::parse_str("2000-01-01T00:00:00").unwrap();

    let dt_p16 = dt_naive.with_timezone_offset(Some(16 * 3600)).unwrap();
    assert_eq!(dt_p16.to_string(), "2000-01-01T00:00:00+16:00");

    let error = match dt_naive.with_timezone_offset(Some(86_400)) {
        Ok(_) => panic!("unexpectedly valid"),
        Err(e) => e,
    };
    assert_eq!(error, ParseError::OutOfRangeTz);
}

#[test]
fn datetime_in_timezone() {
    let dt_z = DateTime::parse_str("2000-01-01T15:00:00.567000Z").unwrap();

    let dt_p1 = dt_z.in_timezone(3_600).unwrap();
    assert_eq!(dt_p1.to_string(), "2000-01-01T16:00:00.567000+01:00");

    let dt_m2 = dt_z.in_timezone(-7_200).unwrap();
    assert_eq!(dt_m2.to_string(), "2000-01-01T13:00:00.567000-02:00");

    let dt_naive = DateTime::parse_str("2000-01-01T00:00:00").unwrap();
    let error = match dt_naive.in_timezone(3_600) {
        Ok(_) => panic!("unexpectedly valid"),
        Err(e) => e,
    };
    assert_eq!(error, ParseError::TzRequired);

    let error = match dt_z.in_timezone(86_400) {
        Ok(_) => panic!("unexpectedly valid"),
        Err(e) => e,
    };
    assert_eq!(error, ParseError::OutOfRangeTz);
}

#[test]
fn time() {
    let t = Time::parse_str("12:13:14.123456").unwrap();
    assert_eq!(
        t,
        Time {
            hour: 12,
            minute: 13,
            second: 14,
            microsecond: 123456,
            tz_offset: None,
        }
    );
    assert_eq!(t.to_string(), "12:13:14.123456");
    assert_eq!(
        format!("{t:?}"),
        "Time { hour: 12, minute: 13, second: 14, microsecond: 123456, tz_offset: None }"
    );
}

#[test]
fn time_comparison() {
    let t1 = Time::parse_str("12:13:14").unwrap();
    let t2 = Time::parse_str("12:10:20").unwrap();

    assert!(t1 > t2);
    assert!(t1 >= t2);
    assert!(t1 >= t1.clone());
    assert!(t2 < t1);
    assert!(t2 <= t1);
    assert!(t2 <= t2.clone());
    assert!(t1.eq(&t1.clone()));
    assert!(!t1.eq(&t2));

    let t3 = Time::parse_str("12:13:14.123").unwrap();
    let t4 = Time::parse_str("12:13:13.999").unwrap();
    assert!(t3 > t4);
}

#[test]
fn time_comparison_timezone() {
    let t1 = Time::parse_str("12:10:00+00:00").unwrap();
    let t2 = Time::parse_str("12:10:00+01:00").unwrap();

    assert!(t1 > t2);
    assert!(t1 >= t2);
    assert!(t1 >= t1.clone());
    assert!(t2 < t1);
    assert!(t2 <= t1);
    assert!(t2 <= t2.clone());
    assert!(t1.eq(&t1.clone()));
    assert!(!t1.eq(&t2));

    let t3 = Time::parse_str("12:13:13.999+00:00").unwrap();
    let t4 = Time::parse_str("12:13:13.123+00:00").unwrap();
    assert!(t3 > t4);

    let t5 = Time::parse_str("12:13:13-20:00").unwrap();
    let t6 = Time::parse_str("12:13:13+00:00").unwrap();
    assert!(t5 > t6);
}

#[test]
fn time_total_seconds() {
    let t = Time::parse_str("01:02:03.04").unwrap();
    assert_eq!(t.total_seconds(), 3600 + 2 * 60 + 3);

    let t = Time::parse_str("12:13:14.999999").unwrap();
    assert_eq!(t.total_seconds(), 12 * 3600 + 13 * 60 + 14);
}

#[test]
fn time_total_ms() {
    let t = Time::parse_str("01:02:03.04").unwrap();
    assert_eq!(t.total_ms(), (3600 + 2 * 60 + 3) * 1000 + 40);

    let t = Time::parse_str("12:13:14.999999").unwrap();
    assert_eq!(t.total_ms(), (12 * 3600 + 13 * 60 + 14) * 1000 + 999);
}

#[test]
fn time_with_tz_offset() {
    let t_z = Time::parse_str("12:13:14.567+00:00").unwrap();

    let t_m8 = t_z.with_timezone_offset(Some(-8 * 3600)).unwrap();
    assert_eq!(t_m8.to_string(), "12:13:14.567000-08:00");

    let t_naive = t_z.with_timezone_offset(None).unwrap();
    assert_eq!(t_naive.to_string(), "12:13:14.567000");

    let t_naive = Time::parse_str("00:00:00").unwrap();

    let t_p16 = t_naive.with_timezone_offset(Some(16 * 3600)).unwrap();
    assert_eq!(t_p16.to_string(), "00:00:00+16:00");

    let error = match t_naive.with_timezone_offset(Some(86_400)) {
        Ok(_) => panic!("unexpectedly valid"),
        Err(e) => e,
    };
    assert_eq!(error, ParseError::OutOfRangeTz);
}

#[test]
fn time_in_timezone() {
    let t_z = Time::parse_str("15:00:00.567Z").unwrap();

    let t_p1 = t_z.in_timezone(3_600).unwrap();
    assert_eq!(t_p1.to_string(), "16:00:00.567000+01:00");

    let t_m2 = t_z.in_timezone(-7_200).unwrap();
    assert_eq!(t_m2.to_string(), "13:00:00.567000-02:00");

    let t_naive = Time::parse_str("10:00:00").unwrap();
    let error = match t_naive.in_timezone(3_600) {
        Ok(_) => panic!("unexpectedly valid"),
        Err(e) => e,
    };
    assert_eq!(error, ParseError::TzRequired);

    let error = match t_z.in_timezone(86_400) {
        Ok(_) => panic!("unexpectedly valid"),
        Err(e) => e,
    };
    assert_eq!(error, ParseError::OutOfRangeTz);
}

param_tests! {
    Time,
    time_min: ok => "00:00:00.000000", "00:00:00";
    time_max: ok => "23:59:59.999999", "23:59:59.999999";
    time_no_fraction: ok => "12:13:14", "12:13:14";
    time_fraction_small: ok => "12:13:14.123", "12:13:14.123000";
    time_no_sec: ok => "12:13", "12:13:00";
    time_tz: ok => "12:13:14z", "12:13:14Z";
    time: err => "xxx", TooShort;
    time: err => "xx:12", InvalidCharHour;
    time_sep_hour: err => "12x12", InvalidCharTimeSep;
    time: err => "12:x0", InvalidCharMinute;
    time_sep_min: err => "12:13x", InvalidCharTzSign;
    time: err => "12:13:x", InvalidCharSecond;
    time: err => "12:13:12.", SecondFractionMissing;
    time: err => "12:13:12.1234567", SecondFractionTooLong;
    time: err => "24:00:00", OutOfRangeHour;
    time: err => "23:60:00", OutOfRangeMinute;
    time: err => "23:59:60", OutOfRangeSecond;
    time_extra_x: err => "23:59:59xxx", InvalidCharTzSign;
    time_extra_space: err => "23:59:59 ", InvalidCharTzSign;
}

#[test]
fn datetime_naive() {
    let dt = DateTime::parse_str("2020-01-01T12:13:14.123456").unwrap();
    assert_eq!(
        dt,
        DateTime {
            date: Date {
                year: 2020,
                month: 1,
                day: 1,
            },
            time: Time {
                hour: 12,
                minute: 13,
                second: 14,
                microsecond: 123456,
                tz_offset: None,
            },
        }
    );
    assert_eq!(dt.to_string(), "2020-01-01T12:13:14.123456");
    assert_eq!(
        format!("{dt:?}"),
        "DateTime { date: Date { year: 2020, month: 1, day: 1 }, time: Time { hour: 12, minute: 13, second: 14, microsecond: 123456, tz_offset: None } }"
    );
}

#[test]
fn datetime_tz_z() {
    let dt = DateTime::parse_str("2020-01-01 12:13:14z").unwrap();
    assert_eq!(
        dt,
        DateTime {
            date: Date {
                year: 2020,
                month: 1,
                day: 1,
            },
            time: Time {
                hour: 12,
                minute: 13,
                second: 14,
                microsecond: 0,
                tz_offset: Some(0),
            },
        }
    );
    assert_eq!(dt.to_string(), "2020-01-01T12:13:14Z");
}

#[test]
fn datetime_bytes() {
    let dt = DateTime::parse_bytes(b"2020-01-01 12:13:14z").unwrap();
    assert_eq!(dt.to_string(), "2020-01-01T12:13:14Z");
}

#[test]
fn datetime_tz_2hours() {
    let dt = DateTime::parse_str("2020-01-01T12:13:14+02:00").unwrap();
    assert_eq!(
        dt,
        DateTime {
            date: Date {
                year: 2020,
                month: 1,
                day: 1,
            },
            time: Time {
                hour: 12,
                minute: 13,
                second: 14,
                microsecond: 0,
                tz_offset: Some(7_200),
            },
        }
    );
    assert_eq!(dt.to_string(), "2020-01-01T12:13:14+02:00");
}

#[test]
fn datetime_tz_negative_2212() {
    // using U+2212 for negative timezones
    let dt = DateTime::parse_str("2020-01-01T12:13:14−02:15").unwrap();
    assert_eq!(dt.time.tz_offset, Some(-8100));
    assert_eq!(dt.to_string(), "2020-01-01T12:13:14-02:15");

    let dt = DateTime::parse_str("2020-01-01T12:13:14−00:01").unwrap();
    assert_eq!(dt.time.tz_offset, Some(-60));
    assert_eq!(dt.to_string(), "2020-01-01T12:13:14-00:01");

    let dt = DateTime::parse_str("2020-01-01T12:13:14−01:00").unwrap();
    assert_eq!(dt.time.tz_offset, Some(-3600));
    assert_eq!(dt.to_string(), "2020-01-01T12:13:14-01:00");
}

#[test]
fn datetime_timestamp() {
    let dt = DateTime::from_timestamp(1_000_000_000, 999_999).unwrap();
    assert_eq!(dt.to_string(), "2001-09-09T01:46:40.999999");
    assert_eq!(dt.timestamp(), 1_000_000_000);

    // using ms unix timestamp
    let dt = DateTime::from_timestamp(1_000_000_000_000, 999_999).unwrap();
    assert_eq!(dt.to_string(), "2001-09-09T01:46:40.999999");
    assert_eq!(dt.timestamp(), 1_000_000_000);
    // using ms unix timestamp

    let d_naive = DateTime::parse_str("1970-01-02T00:00").unwrap();
    assert_eq!(d_naive.timestamp(), 86400);
}

#[test]
fn datetime_timestamp_tz() {
    let t_naive = DateTime::parse_str("1970-01-02T00:00").unwrap();
    assert_eq!(t_naive.timestamp(), 24 * 3600);
    assert_eq!(t_naive.timestamp_tz(), 24 * 3600);

    let dt_zulu = DateTime::parse_str("1970-01-02T00:00Z").unwrap();
    assert_eq!(dt_zulu.timestamp(), 24 * 3600);
    assert_eq!(dt_zulu.timestamp_tz(), 24 * 3600);

    let dt_plus_1 = DateTime::parse_str("1970-01-02T00:00+01:00").unwrap();
    assert_eq!(dt_plus_1.timestamp(), 24 * 3600);
    assert_eq!(dt_plus_1.timestamp_tz(), 23 * 3600);
}

#[test]
fn datetime_timestamp_ms_methods() {
    let dt = DateTime::parse_str("1970-01-02T00:00:00.123Z").unwrap();
    assert_eq!(dt.timestamp_ms(), 86_400_123);
    assert_eq!(dt.timestamp_tz_ms(), 86_400_123);

    let dt = DateTime::parse_str("1970-01-02T00:00:00.123+01:00").unwrap();
    assert_eq!(dt.timestamp_ms(), 86_400_123);
    assert_eq!(dt.timestamp_tz_ms(), 82_800_123);
}

#[test]
fn datetime_comparison_naive() {
    let dt1 = DateTime::parse_str("2020-02-03T04:05:06.07").unwrap();
    let dt2 = DateTime::parse_str("2021-01-02T03:04:05.06").unwrap();

    assert!(dt2 > dt1);
    assert!(dt2 >= dt1);
    assert!(dt2 >= dt2.clone());
    assert!(dt1 < dt2);
    assert!(dt1 <= dt2);
    assert!(dt1 <= dt1.clone());

    let dt3 = DateTime::parse_str("2020-02-03T04:05:06.123").unwrap();
    let dt4 = DateTime::parse_str("2020-02-03T04:05:06.124").unwrap();
    assert!(dt4 > dt3);
    assert!(dt3 < dt4);
}

#[test]
fn datetime_comparison_timezone() {
    let dt1 = DateTime::parse_str("2000-01-01T00:00:00+01:00").unwrap();
    let dt2 = DateTime::parse_str("2000-01-01T00:00:00+02:00").unwrap();

    assert!(dt1 > dt2);
    assert!(dt1 >= dt2);
    assert!(dt2 < dt1);
    assert!(dt2 <= dt1);

    let dt3 = DateTime::parse_str("2000-01-01T00:00:00").unwrap();

    assert!(dt1 >= dt3);
    assert!(dt3 <= dt1);

    let dt4 = DateTime::parse_str("1970-01-01T04:00:00.222+02:00").unwrap();
    assert_eq!(dt4.timestamp_tz(), 2 * 3600);
    let dt5 = DateTime::parse_str("1970-01-01T03:00:00Z").unwrap();
    assert_eq!(dt5.timestamp_tz(), 3 * 3600);
    assert!(dt5 > dt4);
    assert!(dt4 <= dt4.clone());

    // assert that microseconds are used for comparison here
    let dt6 = DateTime::parse_str("1970-01-01T04:00:00.333+02:00").unwrap();
    assert_eq!(dt6.timestamp_tz(), 2 * 3600);
    assert!(dt6 > dt4);
    assert_ne!(dt6, dt4);

    // even on different dates, tz has an effect
    let dt7 = DateTime::parse_str("2022-01-01T23:00:00Z").unwrap();
    let dt8 = DateTime::parse_str("2022-01-02T01:00:00+03:00").unwrap();
    assert!(dt7 > dt8);
}

param_tests! {
    DateTime,
    dt_longest: ok => "2020-01-01T12:13:14.123456−02:15", "2020-01-01T12:13:14.123456-02:15";
    dt_tz_negative: ok => "2020-01-01T12:13:14-02:15", "2020-01-01T12:13:14-02:15";
    dt_tz_negative_10: ok => "2020-01-01T12:13:14-11:30", "2020-01-01T12:13:14-11:30";
    dt_tz_no_colon: ok => "2020-01-01T12:13:14+1234", "2020-01-01T12:13:14+12:34";
    dt_seconds_fraction_break: ok => "2020-01-01 12:13:14.123z", "2020-01-01T12:13:14.123000Z";
    dt_seconds_fraction_comma: ok => "2020-01-01 12:13:14,123z", "2020-01-01T12:13:14.123000Z";
    dt_underscore: ok => "2020-01-01_12:13:14,123z", "2020-01-01T12:13:14.123000Z";
    dt_unix1: ok => "1654646400", "2022-06-08T00:00:00";
    dt_unix2: ok => "1654646404", "2022-06-08T00:00:04";
    dt_unix_1_neg: ok => "-1654646400", "1917-07-27T00:00:00";
    dt_unix_2_neg: ok => "-1654646404", "1917-07-26T23:59:56";
    dt_unix_float: ok => "1654646404.5", "2022-06-08T00:00:04.500000";
    dt_unix_float_limit: ok => "1654646404.123456", "2022-06-08T00:00:04.123456";
    dt_unix_float_precision: ok => "1735624581.348192", "2024-12-31T05:56:21.348192";
    dt_unix_float_ms: ok => "1654646404000.5", "2022-06-08T00:00:04.000500";
    dt_unix_float_ms_limit: ok => "1654646404123.456", "2022-06-08T00:00:04.123456";
    dt_unix_float_ms_neg: ok => "-1654646404.123456", "1917-07-26T23:59:55.876544";
    dt_unix_float_ms_neg_limit: ok => "-1654646404000.123", "1917-07-26T23:59:55.999877";
    dt_unix_float_empty: ok => "1654646404.", "2022-06-08T00:00:04";
    dt_unix_float_ms_empty: ok => "1654646404000.", "2022-06-08T00:00:04";
    dt_unix_float_too_long: err => "1654646404.1234567", SecondFractionTooLong;
    dt_unix_float_ms_too_long: err => "1654646404123.4567", MillisecondFractionTooLong;
    dt_short_date: err => "xxx", TooShort;
    dt_short_time: err => "2020-01-01T12:0", TooShort;
    dt: err => "202x-01-01", InvalidCharYear;
    dt: err => "2020-01-01x", InvalidCharDateTimeSep;
    dt: err => "2020-01-01Txx:00", InvalidCharHour;
    dt_1: err => "2020-01-01T12:00:00x", InvalidCharTzSign;
    // same first byte as U+2212, different second b'\xe2\x89\x92'.decode()
    dt_2: err => "2020-01-01T12:00:00≒", InvalidCharTzSign;
    // same first and second bytes as U+2212, different third b'\xe2\x88\x93'.decode()
    dt_3: err => "2020-01-01T12:00:00∓", InvalidCharTzSign;
    dt: err => "2020-01-01T12:00:00+x", InvalidCharTzHour;
    dt: err => "2020-01-01T12:00:00+00x", InvalidCharTzMinute;
    dt_extra_space_z: err => "2020-01-01T12:00:00Z ", ExtraCharacters;
    dt_extra_space_tz1: err => "2020-01-01T12:00:00+00:00 ", ExtraCharacters;
    dt_extra_space_tz2: err => "2020-01-01T12:00:00+0000 ", ExtraCharacters;
    dt_extra_xxx: err => "2020-01-01T12:00:00Zxxx", ExtraCharacters;
    tz_pos_2359: ok => "2020-01-01T12:00:00+23:59", "2020-01-01T12:00:00+23:59";
    tz_neg_2359: ok => "2020-01-01T12:00:00-23:59", "2020-01-01T12:00:00-23:59";
    tz_60mins: err => "2020-01-01T12:00:00+00:60", OutOfRangeTzMinute;
    tz_pos_gt2359: err => "2020-01-01T12:00:00+24:00", OutOfRangeTz;
    tz_neg_gt2359: err => "2020-01-01T12:00:00-24:00", OutOfRangeTz;
    tz_pos_99hr: err => "2020-01-01T12:00:00+99:59", OutOfRangeTz;
    tz_neg_99hr: err => "2020-01-01T12:00:00-99:59", OutOfRangeTz;
}

fn extract_values(line: &str, prefix: &str) -> (String, String) {
    let parts: Vec<&str> = line.trim_start_matches(prefix).split("->").collect();
    assert_eq!(parts.len(), 2);
    (parts[0].trim().to_string(), parts[1].trim().to_string())
}

#[test]
fn test_ok_values_txt() {
    let mut f = File::open("./tests/values_ok.txt").unwrap();
    let mut contents = String::new();
    f.read_to_string(&mut contents).unwrap();
    let mut success = 0;
    for (i, line) in contents.split('\n').enumerate() {
        let line_no = i + 1;
        if line.starts_with('#') || line.is_empty() || line == "\r" {
            continue;
        } else if line.starts_with("date:") {
            let (input, expected_str) = extract_values(line, "date:");
            let d = Date::parse_str(&input)
                .map_err(|e| panic!("error on line {line_no} {line:?}: {e:?}"))
                .unwrap();
            assert_eq!(d.to_string(), expected_str, "error on line {line_no}");
        } else if line.starts_with("time:") {
            let (input, expected_str) = extract_values(line, "time:");
            let t = Time::parse_str(&input)
                .map_err(|e| panic!("error on line {line_no} {line:?}: {e:?}"))
                .unwrap();
            assert_eq!(t.to_string(), expected_str, "error on line {line_no}");
        } else if line.starts_with("dt:") {
            let (input, expected_str) = extract_values(line, "dt:");
            let dt = DateTime::parse_str(&input)
                .map_err(|e| panic!("error on line {line_no} {line:?}: {e:?}"))
                .unwrap();
            assert_eq!(dt.to_string(), expected_str, "error on line {line_no}");
        } else {
            panic!("unexpected line: {line:?}");
        }
        success += 1;
    }
    println!("{success} formats successfully parsed");
}

#[test]
fn test_err_values_txt() {
    let mut f = File::open("./tests/values_err.txt").unwrap();
    let mut contents = String::new();
    f.read_to_string(&mut contents).unwrap();
    let mut success = 0;
    for (i, line) in contents.split('\n').enumerate() {
        let line_no = i + 1;
        if line.starts_with('#') || line.is_empty() {
            continue;
        }
        if DateTime::parse_str_rfc3339(line.trim()).is_ok() {
            panic!("unexpected valid line {line_no}: {line:?}")
        }
        success += 1;
    }
    println!("{success} correctly invalid");
}

#[test]
fn duration_simple() {
    let d = Duration::parse_str("P1Y").unwrap();
    assert_eq!(
        d,
        Duration {
            positive: true,
            day: 365,
            second: 0,
            microsecond: 0
        }
    );
    assert_eq!(d.to_string(), "P1Y");
}

#[test]
fn duration_zero() {
    let d = Duration::parse_str("PT0S").unwrap();
    assert_eq!(
        d,
        Duration {
            positive: true,
            day: 0,
            second: 0,
            microsecond: 0
        }
    );
    assert_eq!(d.to_string(), "PT0S");
}

#[test]
fn duration_total_seconds() {
    let d = Duration::parse_str("P1MT1.5S").unwrap();
    assert_eq!(
        d,
        Duration {
            positive: true,
            day: 30,
            second: 1,
            microsecond: 500_000
        }
    );
    assert_eq!(d.to_string(), "P30DT1.5S");
    assert_eq!(d.signed_total_seconds(), 30 * 86_400 + 1);
    assert_eq!(d.signed_microseconds(), 500_000);
}

#[test]
fn duration_total_ms() {
    let d = Duration::parse_str("P1DT0.5S").unwrap();
    assert_eq!(d.signed_total_ms(), 86_400_500);

    let d = Duration::parse_str("-PT1.001S").unwrap();
    assert_eq!(d.signed_total_ms(), -1_001);
}

#[test]
fn duration_total_seconds_neg() {
    let d = Duration::parse_str("-P1DT42.123456S").unwrap();
    assert_eq!(
        d,
        Duration {
            positive: false,
            day: 1,
            second: 42,
            microsecond: 123_456
        }
    );
    assert_eq!(d.to_string(), "-P1DT42.123456S");
    assert_eq!(d.signed_total_seconds(), -86_442);
    assert_eq!(d.signed_microseconds(), -123_456);
}

#[test]
fn duration_fractions() {
    let d = Duration::parse_str("P1.123W").unwrap();
    assert_eq!(
        d,
        Duration {
            positive: true,
            day: 7,
            second: 74390,
            microsecond: 400_000
        }
    );
}

#[test]
fn duration_new_normalise() {
    let d = Duration::new(false, 1, 86500, 1_000_123).unwrap();
    assert_eq!(
        d,
        Duration {
            positive: false,
            day: 2,
            second: 101,
            microsecond: 123,
        }
    );
}

#[test]
fn duration_new_normalise2() {
    let d = Duration::new(true, 0, 0, 1_000_000).unwrap();
    assert_eq!(
        d,
        Duration {
            positive: true,
            day: 0,
            second: 1,
            microsecond: 0,
        }
    );
}

#[test]
fn duration_comparison() {
    let d1 = Duration::new(true, 0, 0, 1_000_000).unwrap();
    let d2 = Duration::new(true, 0, 0, 1_000_001).unwrap();
    assert!(d1 < d2);
    assert!(d1 <= d2);
    assert!(d1 <= d1.clone());
    assert!(d2 > d1);
    assert!(d2 >= d1);
    assert!(d2 >= d2.clone());

    let d3 = Duration::new(true, 3, 0, 0).unwrap();
    let d4 = Duration::new(false, 4, 0, 0).unwrap();
    assert!(d3 > d4);
    assert!(d3 >= d4);
    assert!(d4 < d3);
    assert!(d4 <= d3);
    // from docs: `positive` is included in in comparisons, thus `+P1D` is greater than `-P2D`
    let d5 = Duration::parse_str("+P1D").unwrap();
    let d6 = Duration::parse_str("-P2D").unwrap();
    assert!(d5 > d6);

    let d7 = Duration::new(false, 3, 0, 0).unwrap();
    let d8 = Duration::new(false, 4, 0, 0).unwrap();
    assert!(d7 > d8);
    assert!(d8 < d7);
}

#[test]
fn duration_new_err() {
    let d = Duration::new(true, u32::MAX, 4294967295, 905969663);
    match d {
        Ok(t) => panic!("unexpectedly valid: {t:?}"),
        Err(e) => assert_eq!(e, ParseError::DurationValueTooLarge),
    }
    let d = Duration::new(true, u32::MAX, 0, 0);
    match d {
        Ok(t) => panic!("unexpectedly valid: {t:?}"),
        Err(e) => assert_eq!(e, ParseError::DurationDaysTooLarge),
    }
}

#[test]
fn datetime_timestamp_ms_fractional() {
    let dt = DateTime::parse_str("2022-06-07T00:00:01.111Z").unwrap();
    assert_eq!(dt.timestamp_ms(), 1_654_560_001_111);
    assert_eq!(dt.timestamp_tz_ms(), 1_654_560_001_111);

    let dt = DateTime::parse_str("2022-06-07T00:00:01.111+02:00").unwrap();
    assert_eq!(dt.timestamp_ms(), 1_654_560_001_111);
    assert_eq!(dt.timestamp_tz_ms(), 1_654_552_801_111);
}

#[test]
fn duration_hours() {
    let d = Duration::parse_str("PT5H45M").unwrap();
    assert_eq!(
        d,
        Duration {
            positive: true,
            day: 0,
            second: 20700,
            microsecond: 0
        }
    );
    assert_eq!(d.to_string(), "PT5H45M");
    assert_eq!(d.signed_total_seconds(), (5 * 60 * 60) + (45 * 60));
    assert_eq!(d.signed_microseconds(), 0);
}

#[test]
fn duration_minutes() {
    let d = Duration::parse_str("PT30M").unwrap();
    assert_eq!(
        d,
        Duration {
            positive: true,
            day: 0,
            second: 1800,
            microsecond: 0
        }
    );
    assert_eq!(d.to_string(), "PT30M");
    assert_eq!(d.signed_total_seconds(), 30 * 60);
    assert_eq!(d.signed_microseconds(), 0);
}

param_tests! {
    Duration,
    duration_too_short1: err => "", TooShort;
    duration_too_short2: err => "+", TooShort;
    duration_too_short3: err => "P", TooShort;
    duration_too_short4: err => "+PT", TooShort;
    duration_1y: ok => "P1Y", "P1Y";
    duration_123y: ok => "P123Y", "P123Y";
    duration_123_8y: ok => "P123.8Y", "P123Y292D";
    duration_1m: ok => "P1M", "P30D";
    duration_1_5m: ok => "P1.5M", "P45D";
    duration_1w: ok => "P1W", "P7D";
    duration_1_1w: ok => "P1.1W", "P7DT16H48M";
    duration_1_123w: ok => "P1.123W", "P7DT20H39M50.4S";
    duration_simple_negative: ok => "-P1Y", "-P1Y";
    duration_simple_positive: ok => "+P1Y", "P1Y";
    duration_fraction1: ok => "PT0.555555S", "PT0.555555S";
    duration_fraction2: ok => "P1Y1DT2H0.5S", "P1Y1DT2H0.5S";
    duration_1: ok => "P1DT1S", "P1DT1S";
    duration_all: ok => "P1Y2M3DT4H5M6S", "P1Y63DT4H5M6S";
    // FIXME: this is current behaviour, but we should probably error on
    // out-of order durations (not RFC3339 compliant)
    duration_all_wrong_order: ok => "P3D2M1YT6S5M4H", "P1Y63DT4H5M6S";
    // FIXME: this is current behaviour, but we should probably error on
    // repeated units (not RFC3339 compliant)
    duration_unit_repeated: ok => "P1Y2Y", "P3Y";
    duration: err => "PD", DurationInvalidNumber;
    duration: err => "P1DT1MT1S", DurationTRepeated;
    duration: err => "P1DT1.1M1S", DurationInvalidFraction;
    duration: err => "P1DT1X", DurationInvalidTimeUnit;
    duration_invalid_day_unit1: err => "P1X", DurationInvalidDateUnit;
    duration_invalid_day_unit2: err => "P1", DurationInvalidDateUnit;
    duration_time_42s: ok => "00:00:42", "PT42S";
    duration_time_42s_no_leading_0: ok => "0:00:42", "PT42S";
    duration_time_1m: ok => "00:01", "PT1M";
    duration_time_1m_no_leading_0: ok => "0:01:00", "PT1M";
    duration_time_1h_2m_3s: ok => "01:02:03", "PT1H2M3S";
    duration_time_1h_2m_3s_no_leading_0: ok => "1:02:03", "PT1H2M3S";
    duration_time_fraction: ok => "00:01:03.123", "PT1M3.123S";
    duration_time_extra: err => "00:01:03.123x", ExtraCharacters;
    duration_time_timezone: err => "00:01:03x", ExtraCharacters;
    duration_time_more_than_24_hour: ok => "24:01:03", "P1DT1M3S";
    duration_time_way_more_than_24_hour: ok => "2400000000:01:03", "P273972Y220DT1M3S";
    duration_time_way_more_than_24_hour_long_fraction: ok => "2400000000:01:03.654321", "P273972Y220DT1M3.654321S";
    duration_time_invalid_over_limit_hour: err => "100000000000:01:03", DurationHourValueTooLarge;
    duration_time_overflow_hour: err => "100000000000000000000000:01:03", DurationHourValueTooLarge;
    duration_time_invalid_format_hour: err => "1000xxx000:01:03", InvalidCharHour;
    duration_time_invalid_format_hour2: err => "1 10:10", InvalidCharHour;
    duration_time_invalid_minute: err => "00:60:03", OutOfRangeMinute;
    duration_time_invalid_second: err => "00:00:60", OutOfRangeSecond;
    duration_time_fraction_too_long: err => "00:00:00.1234567", SecondFractionTooLong;
    duration_time_fraction_missing: err => "00:00:00.", SecondFractionMissing;
    duration_days_1day1: ok => "1 day", "P1D";
    duration_days_1day2: ok => "1day", "P1D";
    duration_days_1day3: ok => "1 day,", "P1D";
    duration_days_1day4: ok => "1 day, ", "P1D";
    duration_days_1day5: ok => "1days", "P1D";
    duration_days_1day6: ok => "1DAYS", "P1D";
    duration_days_1day7: ok => "1d", "P1D";
    duration_days_1day8: ok => "1d ", "P1D";
    duration_days_too_short: err => "x", DurationInvalidNumber;
    duration_days_invalid1: err => "1x", DurationInvalidDays;
    duration_days_invalid2: err => "1dx", TooShort;
    duration_days_invalid3: err => "1da", DurationInvalidDays;
    duration_days_invalid4: err => "1", DurationInvalidDays;
    duration_days_invalid5: err => "1 ", DurationInvalidDays;
    duration_days_invalid6: err => "1 x", DurationInvalidDays;
    duration_days_neg: ok => "-1 day", "-P1D";
    duration_days_pos: ok => "+1 day", "P1D";
    duration_days_123days: ok => "123days", "P123D";
    duration_days_time: ok => "1 day 00:00:42", "P1DT42S";
    duration_days_time_no_leading_0: ok => "1 day 1:00:42", "P1DT1H42S";
    duration_days_time_comma_no_leading_0: ok => "1 day, 1:00:42", "P1DT1H42S";
    duration_days_time_neg: ok => "-1 day 00:00:42", "-P1DT42S";
    duration_exceeds_day: ok => "PT86500S", "P1DT1M40S";
    duration_days_time_too_short: err => "1 day 00:", TooShort;
    duration_days_time_wrong: err => "1 day 00:xx", InvalidCharMinute;
    duration_days_time_extra: err => "1 day 00:00:00.123 ", ExtraCharacters;
    duration_days_time_more_than_24_hour: err => "1d 24:01:03", DurationHourValueTooLarge;
    duration_overflow: err => "18446744073709551616 day 12:00", DurationValueTooLarge;
    duration_fuzz1: err => "P18446744073709551611DT8031M1M1M1M", DurationValueTooLarge;
    duration_fuzz2: err => "P18446744073709550PT9970442H6R15D1D", DurationValueTooLarge;
}

#[test]
fn duration_large() {
    let d = Duration::parse_str("999999999 day 00:00").unwrap();
    assert_eq!(d.to_string(), "P2739726Y9D");

    let input = format!("{}1 day 00:00", u64::MAX);
    match Duration::parse_str(&input) {
        Ok(t) => panic!("unexpectedly valid: {input:?} -> {t:?}"),
        Err(e) => assert_eq!(e, ParseError::DurationValueTooLarge),
    }
}

#[test]
fn duration_limit() {
    let d = Duration::new(true, 999_999_999, 86399, 999_999).unwrap();
    assert_eq!(d.to_string(), "P2739726Y9DT23H59M59.999999S");

    match Duration::new(true, 999_999_999, 86399, 999_999 + 1) {
        Ok(t) => panic!("unexpectedly valid -> {t:?}"),
        Err(e) => assert_eq!(e, ParseError::DurationDaysTooLarge),
    }
    let d = Duration::new(false, 999_999_999, 86399, 999_999).unwrap();
    assert_eq!(d.to_string(), "-P2739726Y9DT23H59M59.999999S");

    match Duration::new(false, 999_999_999, 86399, 999_999 + 1) {
        Ok(t) => panic!("unexpectedly valid -> {t:?}"),
        Err(e) => assert_eq!(e, ParseError::DurationDaysTooLarge),
    }
}

macro_rules! int_ok_tests {
    ($($name:ident: $input:literal, $expected:expr;)*) => {
        $(
            paste::item! {
                #[test]
                fn [< parse_int_ok_ $name >]() {
                    let v = int_parse_str($input).unwrap();
                    assert_eq!(v, $expected);
                }
            }
        )*
    }
}

int_ok_tests! {
    zero: "0", 0;
    one: "1", 1;
    small: "123", 123;
    small_neg: "-123", -123;
    small_plus: "+123", 123;
    large: "1585201087123789", 1585201087123789;
    // big_neg: "-09223372036854775808", -09223372036854775808;
}

macro_rules! int_err_tests {
    ($($name:ident: $input:literal;)*) => {
        $(
            paste::item! {
                #[test]
                fn [< parse_int_err_ $name >]() {
                    assert!(int_parse_str($input).is_none())
                }
            }
        )*
    }
}

int_err_tests! {
    text: "xxx";
    double_neg: "--1";
    empty: "";
    too_big: "092233720368547758089";
    too_big_neg: "-092233720368547758089";
}

macro_rules! float_ok_tests {
    ($($name:ident: $input:literal, $expected:expr;)*) => {
        $(
            paste::item! {
                #[test]
                fn [< parse_float_ok_ $name >]() {
                    let v = format!("{:?}", float_parse_str($input));
                    assert_eq!(v, $expected);
                }
            }
        )*
    }
}

float_ok_tests! {
    zero: "0", "Int(0)";
    one: "1", "Int(1)";
    neg_one: "-1", "Int(-1)";
    one_point: "1.", "Float(1.0)";
    decimal: "1.5", "Float(1.5)";
    neg_decimal: "-1.5", "Float(-1.5)";
    decimal_zero: "1.0", "Float(1.0)";
    big_int: "1585201087123789", "Int(1585201087123789)";
    big_int_ones: "1111111111111111", "Int(1111111111111111)";
    big_float: "111111111.11111", "Float(111111111.11111)";
}

macro_rules! float_err_tests {
    ($($name:ident: $input:literal;)*) => {
        $(
            paste::item! {
                #[test]
                fn [< parse_float_err_ $name >]() {
                    assert!(float_parse_str($input).is_err())
                }
            }
        )*
    }
}

float_err_tests! {
    text: "xxx";
    double_neg: "--1";
    text_in_fraction: "123.XX";
    empty: "";
    i64_minus_1: "9223372036854775809";
    too_big: "092233720368547758089";
    too_big_neg: "-092233720368547758089";
    too_big_neg2: "9223372036854775808";
    error_in_decimal: "123.XX";
    error_in_decimal_spaces: "123.12 ";
}

#[test]
fn test_time_parse_truncate_seconds() {
    let time = Time::parse_bytes_with_config(
        "12:13:12.123456789".as_bytes(),
        &(TimeConfigBuilder::new()
            .microseconds_precision_overflow_behavior(MicrosecondsPrecisionOverflowBehavior::Truncate)
            .build()),
    )
    .unwrap();
    assert_eq!(time.to_string(), "12:13:12.123456");
}

#[test]
fn test_datetime_parse_truncate_seconds() {
    let time = DateTime::parse_bytes_with_config(
        "2020-01-01T12:13:12.123456789".as_bytes(),
        &DateTimeConfigBuilder::new()
            .time_config(
                TimeConfigBuilder::new()
                    .microseconds_precision_overflow_behavior(MicrosecondsPrecisionOverflowBehavior::Truncate)
                    .build(),
            )
            .build(),
    )
    .unwrap();
    assert_eq!(time.to_string(), "2020-01-01T12:13:12.123456");
}

#[test]
fn test_duration_parse_truncate_seconds() {
    let time = Duration::parse_bytes_with_config(
        "00:00:00.1234567".as_bytes(),
        &(TimeConfigBuilder::new()
            .microseconds_precision_overflow_behavior(MicrosecondsPrecisionOverflowBehavior::Truncate)
            .build()),
    )
    .unwrap();
    assert_eq!(time.to_string(), "PT0.123456S");
}

#[test]
fn test_time_parse_bytes_does_not_add_offset_for_rfc3339() {
    let time = Time::parse_bytes_with_config(
        "12:13:12".as_bytes(),
        &(TimeConfigBuilder::new().unix_timestamp_offset(Some(0)).build()),
    )
    .unwrap();
    assert_eq!(time.to_string(), "12:13:12");
}

#[test]
fn test_datetime_parse_bytes_does_not_add_offset_for_rfc3339() {
    let time = DateTime::parse_bytes_with_config(
        "2020-01-01T12:13:12".as_bytes(),
        &DateTimeConfigBuilder::new()
            .time_config(TimeConfigBuilder::new().unix_timestamp_offset(Some(0)).build())
            .build(),
    )
    .unwrap();
    assert_eq!(time.to_string(), "2020-01-01T12:13:12");
}

#[test]
fn test_datetime_parse_unix_timestamp_from_bytes_with_utc_offset() {
    let time = DateTime::parse_bytes_with_config(
        "1689102037.5586429".as_bytes(),
        &DateTimeConfigBuilder::new()
            .time_config(
                TimeConfigBuilder::new()
                    .unix_timestamp_offset(Some(0))
                    .microseconds_precision_overflow_behavior(MicrosecondsPrecisionOverflowBehavior::Truncate)
                    .build(),
            )
            .build(),
    )
    .unwrap();
    assert_eq!(time.to_string(), "2023-07-11T19:00:37.558643Z");
}

#[test]
fn test_datetime_parse_unix_timestamp_from_bytes_as_naive() {
    let time = DateTime::parse_bytes_with_config(
        "1689102037.5586429".as_bytes(),
        &DateTimeConfigBuilder::new()
            .time_config(
                TimeConfigBuilder::new()
                    .unix_timestamp_offset(None)
                    .microseconds_precision_overflow_behavior(MicrosecondsPrecisionOverflowBehavior::Truncate)
                    .build(),
            )
            .build(),
    )
    .unwrap();
    assert_eq!(time.to_string(), "2023-07-11T19:00:37.558643");
}

#[test]
fn test_time_parse_unix_timestamp_from_bytes_with_utc_offset() {
    let time =
        Time::from_timestamp_with_config(1, 2, &(TimeConfigBuilder::new().unix_timestamp_offset(Some(0)).build()))
            .unwrap();
    assert_eq!(time.to_string(), "00:00:01.000002Z");
}

#[test]
fn test_time_parse_unix_timestamp_from_bytes_as_naive() {
    let time = Time::from_timestamp_with_config(1, 2, &(TimeConfigBuilder::new().unix_timestamp_offset(None).build()))
        .unwrap();
    assert_eq!(time.to_string(), "00:00:01.000002");
}

#[test]
fn test_time_config_builder() {
    assert_eq!(
        TimeConfigBuilder::new().build(),
        TimeConfig {
            microseconds_precision_overflow_behavior: MicrosecondsPrecisionOverflowBehavior::Error,
            unix_timestamp_offset: None,
        }
    );
    assert_eq!(TimeConfigBuilder::new().build(), TimeConfig::builder().build());
}

#[test]
fn test_date_config_builder() {
    assert_eq!(
        DateConfigBuilder::new().build(),
        DateConfig {
            timestamp_unit: TimestampUnit::Infer
        }
    );
    assert_eq!(DateConfigBuilder::new().build(), DateConfig::builder().build());
}

#[test]
fn test_datetimetime_config_builder() {
    assert_eq!(
        DateTimeConfigBuilder::new().build(),
        DateTimeConfig {
            timestamp_unit: TimestampUnit::Infer,
            time_config: TimeConfig {
                microseconds_precision_overflow_behavior: MicrosecondsPrecisionOverflowBehavior::Error,
                unix_timestamp_offset: None,
            }
        }
    );
    assert_eq!(DateTimeConfigBuilder::new().build(), DateTimeConfig::builder().build());
}

#[test]
fn date_dash_err() {
    let error = Date::parse_str("-").unwrap_err();
    assert_eq!(error, ParseError::TooShort);
    assert_eq!(error.to_string(), "too_short");
    assert_eq!(error.get_documentation(), Some("input is too short"));
}

#[test]
fn number_dash_err() {
    assert!(int_parse_str("-").is_none());
    assert!(int_parse_str("+").is_none());
    assert!(int_parse_bytes(b"-").is_none());
    assert!(int_parse_bytes(b"+").is_none());

    assert!(matches!(float_parse_str("-"), IntFloat::Err));
    assert!(matches!(float_parse_str("+"), IntFloat::Err));
    assert!(matches!(float_parse_bytes(b"-"), IntFloat::Err));
    assert!(matches!(float_parse_bytes(b"+"), IntFloat::Err));
}
#[test]
fn test_timestamp_unit_from_str() {
    use speedate::TimestampUnit;
    assert_eq!(TimestampUnit::from_str("s").unwrap(), TimestampUnit::Second);
    assert_eq!(TimestampUnit::from_str("ms").unwrap(), TimestampUnit::Millisecond);
    assert_eq!(TimestampUnit::from_str("infer").unwrap(), TimestampUnit::Infer);
    assert!(TimestampUnit::from_str("invalid").is_err());
}

#[test]
fn test_microseconds_precision_overflow_behavior_from_str() {
    use speedate::MicrosecondsPrecisionOverflowBehavior;
    assert_eq!(
        MicrosecondsPrecisionOverflowBehavior::from_str("truncate").unwrap(),
        MicrosecondsPrecisionOverflowBehavior::Truncate
    );
    assert_eq!(
        MicrosecondsPrecisionOverflowBehavior::from_str("error").unwrap(),
        MicrosecondsPrecisionOverflowBehavior::Error
    );
    assert!(MicrosecondsPrecisionOverflowBehavior::from_str("invalid").is_err());
}

#[test]
fn test_date_parse_timestamp_unit_second() {
    use speedate::{Date, TimestampUnit};
    let d = Date::parse_str_with_config(
        "1640995200",
        &DateConfigBuilder::new().timestamp_unit(TimestampUnit::Second).build(),
    )
    .unwrap();
    assert_eq!(d.to_string(), "2022-01-01");
}

#[test]
fn test_date_parse_timestamp_unit_millisecond() {
    use speedate::{Date, TimestampUnit};
    let d = Date::parse_str_with_config(
        "1640995200000",
        &DateConfigBuilder::new()
            .timestamp_unit(TimestampUnit::Millisecond)
            .build(),
    )
    .unwrap();
    assert_eq!(d.to_string(), "2022-01-01");
}

#[test]
fn test_datetime_parse_timestamp_units() {
    use speedate::{DateTime, TimeConfigBuilder, TimestampUnit};
    let dt_sec = DateTime::parse_str_with_config(
        "1641039194",
        &DateTimeConfigBuilder::new()
            .timestamp_unit(TimestampUnit::Second)
            .build(),
    )
    .unwrap();
    assert_eq!(dt_sec.to_string(), "2022-01-01T12:13:14");

    let dt_ms = DateTime::parse_str_with_config(
        "1641039194000",
        &DateTimeConfigBuilder::new()
            .timestamp_unit(TimestampUnit::Millisecond)
            .time_config(TimeConfigBuilder::new().build())
            .build(),
    )
    .unwrap();
    assert_eq!(dt_ms.to_string(), "2022-01-01T12:13:14");
}

#[test]
fn test_date_from_timestamp_negative_millisecond() {
    use speedate::{Date, TimestampUnit};

    let d = Date::from_timestamp(
        -1,
        false,
        &DateConfigBuilder::new()
            .timestamp_unit(TimestampUnit::Millisecond)
            .build(),
    )
    .unwrap();
    assert_eq!(d.to_string(), "1969-12-31");
}

#[test]
fn test_datetime_from_timestamp_negative_millisecond() {
    use speedate::{DateTime, TimestampUnit};

    let dt = DateTime::from_timestamp_with_config(
        -1,
        0,
        &DateTimeConfigBuilder::new()
            .timestamp_unit(TimestampUnit::Millisecond)
            .build(),
    )
    .unwrap();
    assert_eq!(dt.to_string(), "1969-12-31T23:59:59.999000");
}
