Documentation
#[test]
fn months_round_trip() {
    for month in 1..=12 {
        let lexical = format!("--{month:02}");
        let value = xsd::parse(&lexical, xsd::G_MONTH).unwrap();
        assert_eq!(value.to_string(), lexical);
        assert_eq!(value.r#type(), xsd::G_MONTH);
        assert_eq!(xsd::parse_g_month(&lexical).unwrap(), value);
    }
}

#[test]
fn months_report_lexical_range_and_timezone_failures() {
    use xsd::ParseCalendarError::{InvalidLexical, OutOfRange};
    for (input, cause) in [
        ("--00", OutOfRange),
        ("--13", OutOfRange),
        ("--99", OutOfRange),
        ("--1", InvalidLexical),
        ("--01--", InvalidLexical),
        (" --01", InvalidLexical),
        ("--01", InvalidLexical),
        ("--01+14:01", InvalidLexical),
        ("--01z", InvalidLexical),
        ("--01-14:01", InvalidLexical),
    ] {
        assert_eq!(xsd::parse_g_month(input).unwrap_err(), cause, "{input}");
        let error = xsd::parse(input, xsd::G_MONTH).unwrap_err();
        assert!(core::error::Error::source(&error).is_some());
        assert!(error.to_string().contains("xsd:gMonth"));
        assert!(
            matches!(error, xsd::ParseError::InvalidCalendar { datatype: xsd::PrimitiveType::GMonth, source } if source == cause)
        );
    }
}
#[test]
fn days_round_trip_and_reject_invalid_fields() {
    use xsd::ParseCalendarError::{InvalidLexical, OutOfRange};
    for day in 1..=31 {
        let lexical = format!("---{day:02}");
        let value = xsd::parse_g_day(&lexical).unwrap();
        assert_eq!(value.to_string(), lexical);
        assert_eq!(value.r#type(), xsd::G_DAY);
        assert_eq!(xsd::parse(&lexical, xsd::G_DAY).unwrap(), value);
    }
    for (input, cause) in [
        ("---00", OutOfRange),
        ("---32", OutOfRange),
        ("---99", OutOfRange),
        ("---1", InvalidLexical),
        ("--01", InvalidLexical),
        ("---01 ", InvalidLexical),
        ("---01", InvalidLexical),
        ("---01+00:60", InvalidLexical),
        ("---01z", InvalidLexical),
        ("---01+14:01", InvalidLexical),
    ] {
        assert_eq!(xsd::parse_g_day(input).unwrap_err(), cause, "{input}");
        assert!(
            matches!(xsd::parse(input, xsd::G_DAY), Err(xsd::ParseError::InvalidCalendar { datatype: xsd::PrimitiveType::GDay, source }) if source == cause)
        );
    }
}

#[test]
fn month_days_round_trip_and_validate_calendar_combinations() {
    use xsd::ParseCalendarError::{InvalidLexical, OutOfRange};
    for (month, last_day) in (1..=12).zip([31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]) {
        for day in 0..=32 {
            let lexical = format!("--{month:02}-{day:02}");
            if day == 0 || day > last_day {
                assert_eq!(xsd::parse_g_month_day(&lexical).unwrap_err(), OutOfRange);
            } else {
                let value = xsd::parse_g_month_day(&lexical).unwrap();
                assert_eq!(value.to_string(), lexical);
                assert_eq!(value.r#type(), xsd::G_MONTH_DAY);
                assert_eq!(xsd::parse(&lexical, xsd::G_MONTH_DAY).unwrap(), value);
            }
        }
    }
    for (input, cause) in [
        ("--00-01", OutOfRange),
        ("--13-01", OutOfRange),
        ("--1-01", InvalidLexical),
        ("--0101", InvalidLexical),
        ("--01-01 ", InvalidLexical),
        ("--01-1", InvalidLexical),
        ("--01-01+15:00", InvalidLexical),
        ("--02-29z", InvalidLexical),
        ("--01-01-14:01", InvalidLexical),
    ] {
        assert_eq!(xsd::parse_g_month_day(input).unwrap_err(), cause, "{input}");
        assert!(
            matches!(xsd::parse(input, xsd::G_MONTH_DAY), Err(xsd::ParseError::InvalidCalendar { datatype: xsd::PrimitiveType::GMonthDay, source }) if source == cause)
        );
    }
}
#[test]
fn years_validate_lexical_rules_bounds_and_timezone_suffixes() {
    use xsd::ParseCalendarError::{InvalidLexical, OutOfRange};
    for (input, cause) in [
        ("", InvalidLexical),
        ("123", InvalidLexical),
        ("+2026", InvalidLexical),
        ("02026", InvalidLexical),
        ("-02026", InvalidLexical),
        ("-00000", InvalidLexical),
        ("2026 ", InvalidLexical),
        ("2026", InvalidLexical),
        ("2026-01", InvalidLexical),
        ("2147483648", OutOfRange),
        ("-2147483649", OutOfRange),
        ("9999999999999999999999999999999", OutOfRange),
        ("2026z", InvalidLexical),
        ("-0001-14:01", InvalidLexical),
        ("0000+00:60", InvalidLexical),
        ("2026+14:01", InvalidLexical),
    ] {
        assert_eq!(xsd::parse_g_year(input).unwrap_err(), cause, "{input}");
        assert!(
            matches!(xsd::parse(input, xsd::G_YEAR), Err(xsd::ParseError::InvalidCalendar { datatype: xsd::PrimitiveType::GYear, source }) if source == cause)
        );
    }
}
#[test]
fn year_months_round_trip_at_year_boundaries() {
    for year in [i32::MIN, -10000, -1, 0, 1, 10000, i32::MAX] {
        for month in 1..=12 {
            let value = xsd::Value::from(xsd::PrimitiveValue::g_year_month(year, month).unwrap());
            let lexical = value.to_string();
            assert_eq!(xsd::parse_g_year_month(&lexical).unwrap(), value);
            assert_eq!(xsd::parse(&lexical, xsd::G_YEAR_MONTH).unwrap(), value);
        }
    }
}

#[test]
fn year_months_reject_invalid_fields_and_timezone_loss() {
    use xsd::ParseCalendarError::{InvalidLexical, OutOfRange};
    for (input, cause) in [
        ("2026-00", OutOfRange),
        ("2026-13", OutOfRange),
        ("2147483648-01", OutOfRange),
        ("-2147483649-12", OutOfRange),
        ("-00000-01", InvalidLexical),
        ("+2026-01", InvalidLexical),
        ("02026-01", InvalidLexical),
        ("2026-1", InvalidLexical),
        ("202601", InvalidLexical),
        ("2026-01-01", InvalidLexical),
        ("2026-01", InvalidLexical),
        ("2026-01 ", InvalidLexical),
        ("2026-01z", InvalidLexical),
        ("-0001-01-14:01", InvalidLexical),
        ("0000-01+00:60", InvalidLexical),
        ("2026-01+14:01", InvalidLexical),
    ] {
        assert_eq!(
            xsd::parse_g_year_month(input).unwrap_err(),
            cause,
            "{input}"
        );
        assert!(
            matches!(xsd::parse(input, xsd::G_YEAR_MONTH), Err(xsd::ParseError::InvalidCalendar { datatype: xsd::PrimitiveType::GYearMonth, source }) if source == cause)
        );
    }
}