//! Temporal value types for openCypher.
//!
//! Implements Date, Time, DateTime, LocalTime, LocalDateTime, and Duration
//! according to the openCypher specification.

use chrono::{
    DateTime as ChronoDateTime, Datelike, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime,
    Timelike,
};
use serde::{Deserialize, Serialize};

/// Date value (no time, no timezone)
/// Represents a calendar date: year, month, day
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct DateValue {
    #[serde(with = "date_format")]
    pub date: NaiveDate,
}

impl DateValue {
    pub fn new(date: NaiveDate) -> Self {
        Self { date }
    }

    pub fn year(&self) -> i32 {
        self.date.year()
    }

    pub fn month(&self) -> u32 {
        self.date.month()
    }

    pub fn day(&self) -> u32 {
        self.date.day()
    }
}

impl std::fmt::Display for DateValue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.date.format("%Y-%m-%d"))
    }
}

/// Extended Date value for astronomical dates outside chrono's range
/// Chrono NaiveDate supports years -262143 to +262142
/// Neo4j supports years -999999999 to +999999999
/// This type stores year as i64 to support the full Neo4j range
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct ExtendedDateValue {
    pub year: i64,
    pub month: u8,
    pub day: u8,
}

impl ExtendedDateValue {
    pub fn new(year: i64, month: u8, day: u8) -> Self {
        Self { year, month, day }
    }

    /// Check if this date can be represented by chrono's NaiveDate
    pub fn is_in_chrono_range(&self) -> bool {
        self.year >= -262143 && self.year <= 262142
    }

    /// Try to convert to chrono NaiveDate (returns None if out of range)
    pub fn to_naive_date(&self) -> Option<NaiveDate> {
        if !self.is_in_chrono_range() {
            return None;
        }
        NaiveDate::from_ymd_opt(self.year as i32, self.month as u32, self.day as u32)
    }

    /// Create from chrono NaiveDate
    pub fn from_naive_date(date: &NaiveDate) -> Self {
        Self {
            year: date.year() as i64,
            month: date.month() as u8,
            day: date.day() as u8,
        }
    }

    /// Convert to epoch days (days since 1970-01-01)
    /// Uses proleptic Gregorian calendar
    pub fn to_epoch_days(&self) -> i64 {
        // Algorithm: count days from year 0 to target, then subtract epoch offset
        let y = self.year;
        let m = self.month as i64;
        let d = self.day as i64;

        // Adjust for months January and February (treat as months 13 and 14 of previous year)
        let (y, m) = if m <= 2 {
            (y - 1, m + 12)
        } else {
            (y, m)
        };

        // Days from year 0 using proleptic Gregorian calendar
        // Formula: 365*y + y/4 - y/100 + y/400 + (153*(m-3)+2)/5 + d - 719528
        // Where 719528 is the number of days from year 0 to 1970-01-01
        let leap_days = if y >= 0 {
            y / 4 - y / 100 + y / 400
        } else {
            // For negative years, we need floor division
            (y - 3) / 4 - (y - 99) / 100 + (y - 399) / 400
        };

        365 * y + leap_days + (153 * (m - 3) + 2) / 5 + d - 719528
    }

    /// Create from epoch days
    pub fn from_epoch_days(days: i64) -> Self {
        // Reverse of to_epoch_days algorithm
        // Add epoch offset
        let z = days + 719468; // Days since March 1, year 0

        // Compute era (400-year period)
        let era = if z >= 0 { z / 146097 } else { (z - 146096) / 146097 };
        let doe = z - era * 146097; // Day of era [0, 146096]
        let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; // Year of era [0, 399]
        let y = yoe + era * 400;
        let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); // Day of year [0, 365]
        let mp = (5 * doy + 2) / 153; // Month position [0, 11]
        let d = doy - (153 * mp + 2) / 5 + 1; // Day [1, 31]
        let m = if mp < 10 { mp + 3 } else { mp - 9 }; // Month [1, 12]
        let y = if m <= 2 { y + 1 } else { y };

        Self {
            year: y,
            month: m as u8,
            day: d as u8,
        }
    }
}

impl std::fmt::Display for ExtendedDateValue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if self.year >= 0 {
            write!(f, "{:04}-{:02}-{:02}", self.year, self.month, self.day)
        } else {
            // For negative years, use sign prefix
            write!(f, "-{:04}-{:02}-{:02}", -self.year, self.month, self.day)
        }
    }
}

/// Time with timezone offset
/// Represents a time of day with timezone: hour, minute, second, nanosecond, offset
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TimeValue {
    #[serde(with = "time_format")]
    pub time: NaiveTime,
    #[serde(with = "offset_format")]
    pub offset: FixedOffset,
}

impl TimeValue {
    pub fn new(time: NaiveTime, offset: FixedOffset) -> Self {
        Self { time, offset }
    }

    pub fn hour(&self) -> u32 {
        self.time.hour()
    }

    pub fn minute(&self) -> u32 {
        self.time.minute()
    }

    pub fn second(&self) -> u32 {
        self.time.second()
    }

    pub fn nanosecond(&self) -> u32 {
        self.time.nanosecond()
    }

    pub fn timezone(&self) -> String {
        self.offset.to_string()
    }
}

impl std::fmt::Display for TimeValue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let nanos = self.time.nanosecond();
        let time_part = self.time.format("%H:%M:%S");
        let offset_str = format_timezone_offset(&self.offset);

        if nanos == 0 {
            write!(f, "{}{}", time_part, offset_str)
        } else {
            write!(f, "{}.{}{}", time_part, format_nanoseconds(nanos), offset_str)
        }
    }
}

/// Format timezone offset: use Z for UTC, otherwise +HH:MM or -HH:MM
fn format_timezone_offset(offset: &chrono::FixedOffset) -> String {
    let secs = offset.local_minus_utc();
    if secs == 0 {
        "Z".to_string()
    } else {
        offset.to_string()
    }
}

/// Format nanoseconds with trailing zeros trimmed
fn format_nanoseconds(nanos: u32) -> String {
    let s = format!("{:09}", nanos);
    s.trim_end_matches('0').to_string()
}

impl Ord for TimeValue {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        // Convert both to UTC equivalent for comparison
        // Time in seconds since midnight, adjusted for timezone offset
        let self_seconds = self.time.num_seconds_from_midnight() as i64
            - self.offset.local_minus_utc() as i64;
        let other_seconds = other.time.num_seconds_from_midnight() as i64
            - other.offset.local_minus_utc() as i64;

        let self_nanos = self.time.nanosecond();
        let other_nanos = other.time.nanosecond();

        self_seconds.cmp(&other_seconds).then(self_nanos.cmp(&other_nanos))
    }
}

impl PartialOrd for TimeValue {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

/// Time without timezone
/// Represents a local time: hour, minute, second, nanosecond
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct LocalTimeValue {
    #[serde(with = "time_format")]
    pub time: NaiveTime,
}

impl LocalTimeValue {
    pub fn new(time: NaiveTime) -> Self {
        Self { time }
    }

    pub fn hour(&self) -> u32 {
        self.time.hour()
    }

    pub fn minute(&self) -> u32 {
        self.time.minute()
    }

    pub fn second(&self) -> u32 {
        self.time.second()
    }

    pub fn nanosecond(&self) -> u32 {
        self.time.nanosecond()
    }
}

impl std::fmt::Display for LocalTimeValue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let nanos = self.time.nanosecond();
        let time_part = self.time.format("%H:%M:%S");

        if nanos == 0 {
            write!(f, "{}", time_part)
        } else {
            write!(f, "{}.{}", time_part, format_nanoseconds(nanos))
        }
    }
}

/// DateTime with timezone
/// Represents a specific point in time with timezone
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DateTimeValue {
    #[serde(with = "datetime_format")]
    pub datetime: ChronoDateTime<FixedOffset>,
    /// Optional named timezone (e.g., "Europe/Stockholm") for display purposes
    #[serde(skip_serializing_if = "Option::is_none")]
    pub timezone_name: Option<String>,
}

impl DateTimeValue {
    pub fn new(datetime: ChronoDateTime<FixedOffset>) -> Self {
        Self { datetime, timezone_name: None }
    }

    pub fn with_timezone_name(datetime: ChronoDateTime<FixedOffset>, timezone_name: String) -> Self {
        Self { datetime, timezone_name: Some(timezone_name) }
    }

    pub fn year(&self) -> i32 {
        self.datetime.year()
    }

    pub fn month(&self) -> u32 {
        self.datetime.month()
    }

    pub fn day(&self) -> u32 {
        self.datetime.day()
    }

    pub fn hour(&self) -> u32 {
        self.datetime.hour()
    }

    pub fn minute(&self) -> u32 {
        self.datetime.minute()
    }

    pub fn second(&self) -> u32 {
        self.datetime.second()
    }

    pub fn nanosecond(&self) -> u32 {
        self.datetime.nanosecond()
    }

    pub fn timezone(&self) -> String {
        // Return named timezone if available, otherwise the offset string
        if let Some(ref tz_name) = self.timezone_name {
            tz_name.clone()
        } else {
            self.datetime.offset().to_string()
        }
    }
}

impl std::fmt::Display for DateTimeValue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let nanos = self.datetime.nanosecond();
        let seconds = self.datetime.second();
        let offset = self.datetime.offset();
        let offset_str = format_timezone_offset(offset);

        // Build the base datetime string
        // Neo4j format omits :SS when both seconds and nanoseconds are zero
        let base = if nanos == 0 && seconds == 0 {
            // Omit seconds: YYYY-MM-DDTHH:MM
            let date_time = self.datetime.format("%Y-%m-%dT%H:%M");
            format!("{}{}", date_time, offset_str)
        } else if nanos == 0 {
            // Include seconds but no fractional: YYYY-MM-DDTHH:MM:SS
            let date_time = self.datetime.format("%Y-%m-%dT%H:%M:%S");
            format!("{}{}", date_time, offset_str)
        } else {
            // Include seconds and fractional: YYYY-MM-DDTHH:MM:SS.nnnnnnnnn
            let date_time = self.datetime.format("%Y-%m-%dT%H:%M:%S");
            format!("{}.{}{}", date_time, format_nanoseconds(nanos), offset_str)
        };

        // Append timezone name if present (e.g., "[Europe/Stockholm]")
        if let Some(ref tz_name) = self.timezone_name {
            write!(f, "{}[{}]", base, tz_name)
        } else {
            write!(f, "{}", base)
        }
    }
}

impl Ord for DateTimeValue {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        // ChronoDateTime has Ord built-in
        self.datetime.cmp(&other.datetime)
    }
}

impl PartialOrd for DateTimeValue {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

/// DateTime without timezone
/// Represents a local date and time
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct LocalDateTimeValue {
    #[serde(with = "local_datetime_format")]
    pub datetime: NaiveDateTime,
}

impl LocalDateTimeValue {
    pub fn new(datetime: NaiveDateTime) -> Self {
        Self { datetime }
    }

    pub fn year(&self) -> i32 {
        self.datetime.year()
    }

    pub fn month(&self) -> u32 {
        self.datetime.month()
    }

    pub fn day(&self) -> u32 {
        self.datetime.day()
    }

    pub fn hour(&self) -> u32 {
        self.datetime.hour()
    }

    pub fn minute(&self) -> u32 {
        self.datetime.minute()
    }

    pub fn second(&self) -> u32 {
        self.datetime.second()
    }

    pub fn nanosecond(&self) -> u32 {
        self.datetime.nanosecond()
    }
}

impl std::fmt::Display for LocalDateTimeValue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let nanos = self.datetime.nanosecond();

        // Format date and time parts
        let date_time = self.datetime.format("%Y-%m-%dT%H:%M:%S");

        if nanos == 0 {
            // No fractional seconds
            write!(f, "{}", date_time)
        } else {
            // Include nanoseconds with trailing zeros trimmed
            write!(f, "{}.{}", date_time, format_nanoseconds(nanos))
        }
    }
}

/// Extended LocalDateTime value for astronomical dates outside chrono's range
/// Used for dates like -999999999-01-01T00:00:00 which chrono cannot represent
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct ExtendedLocalDateTimeValue {
    pub date: ExtendedDateValue,
    pub hour: u8,
    pub minute: u8,
    pub second: u8,
    pub nanosecond: u32,
}

impl ExtendedLocalDateTimeValue {
    pub fn new(date: ExtendedDateValue, hour: u8, minute: u8, second: u8, nanosecond: u32) -> Self {
        Self { date, hour, minute, second, nanosecond }
    }

    /// Check if this datetime can be represented by chrono's NaiveDateTime
    pub fn is_in_chrono_range(&self) -> bool {
        self.date.is_in_chrono_range()
    }

    /// Try to convert to chrono NaiveDateTime (returns None if out of range)
    pub fn to_naive_datetime(&self) -> Option<NaiveDateTime> {
        let date = self.date.to_naive_date()?;
        NaiveTime::from_hms_nano_opt(self.hour as u32, self.minute as u32, self.second as u32, self.nanosecond)
            .map(|time| NaiveDateTime::new(date, time))
    }

    /// Create from chrono NaiveDateTime
    pub fn from_naive_datetime(dt: &NaiveDateTime) -> Self {
        Self {
            date: ExtendedDateValue::from_naive_date(&dt.date()),
            hour: dt.hour() as u8,
            minute: dt.minute() as u8,
            second: dt.second() as u8,
            nanosecond: dt.nanosecond(),
        }
    }

    /// Convert to total seconds since epoch (1970-01-01T00:00:00)
    /// Note: This can overflow for extreme dates, so returns i128
    pub fn to_epoch_seconds(&self) -> i128 {
        let days = self.date.to_epoch_days() as i128;
        let time_secs = self.hour as i128 * 3600 + self.minute as i128 * 60 + self.second as i128;
        days * 86400 + time_secs
    }
}

impl std::fmt::Display for ExtendedLocalDateTimeValue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if self.nanosecond == 0 {
            write!(f, "{}T{:02}:{:02}:{:02}", self.date, self.hour, self.minute, self.second)
        } else {
            write!(f, "{}T{:02}:{:02}:{:02}.{}", self.date, self.hour, self.minute, self.second, format_nanoseconds(self.nanosecond))
        }
    }
}

/// Duration value (time period)
/// Represents a period of time with months, days, seconds, and nanoseconds
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DurationValue {
    pub months: i64,  // Months component (variable length)
    pub days: i64,    // Days component (variable length due to DST)
    pub seconds: i64, // Seconds component (fixed length)
    pub nanos: i32,   // Nanoseconds component (0-999,999,999)
}

impl DurationValue {
    pub fn new(months: i64, days: i64, seconds: i64, nanos: i32) -> Self {
        // Normalize nanoseconds to be in range [0, 999_999_999]
        // This uses floor division so that seconds absorbs any overflow/underflow
        let total_nanos = seconds as i128 * 1_000_000_000 + nanos as i128;
        let (norm_seconds, norm_nanos) = if total_nanos >= 0 {
            (
                (total_nanos / 1_000_000_000) as i64,
                (total_nanos % 1_000_000_000) as i32,
            )
        } else {
            // Floor division for negative numbers
            let floor_seconds = (total_nanos - 999_999_999) / 1_000_000_000;
            let floor_nanos = total_nanos - floor_seconds * 1_000_000_000;
            (floor_seconds as i64, floor_nanos as i32)
        };

        Self {
            months,
            days,
            seconds: norm_seconds,
            nanos: norm_nanos,
        }
    }

    pub fn years(&self) -> i64 {
        self.months / 12
    }

    pub fn months_of_year(&self) -> i64 {
        self.months % 12
    }

    pub fn weeks(&self) -> i64 {
        self.days / 7
    }

    pub fn days_of_week(&self) -> i64 {
        self.days % 7
    }

    pub fn hours(&self) -> i64 {
        self.seconds / 3600
    }

    pub fn minutes(&self) -> i64 {
        (self.seconds % 3600) / 60
    }

    pub fn seconds_of_minute(&self) -> i64 {
        self.seconds % 60
    }

    pub fn milliseconds(&self) -> i64 {
        (self.nanos / 1_000_000) as i64
    }

    pub fn microseconds(&self) -> i64 {
        (self.nanos / 1_000) as i64
    }

    pub fn nanoseconds(&self) -> i32 {
        self.nanos
    }
}

impl std::fmt::Display for DurationValue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "P")?;

        // Handle date part: years and months
        if self.months != 0 {
            let years = self.months / 12;
            let months = self.months % 12;
            if years != 0 {
                write!(f, "{}Y", years)?;
            }
            if months != 0 {
                write!(f, "{}M", months)?;
            }
        }

        // Handle days
        if self.days != 0 {
            write!(f, "{}D", self.days)?;
        }

        // Handle time part: hours, minutes, seconds
        if self.seconds != 0 || self.nanos != 0 {
            write!(f, "T")?;

            // Combine seconds and nanos into total nanoseconds for proper formatting
            let total_nanos = self.seconds as i128 * 1_000_000_000 + self.nanos as i128;

            if total_nanos == 0 {
                write!(f, "0S")?;
            } else {
                // Work with absolute value and track sign
                let is_negative = total_nanos < 0;
                let abs_nanos = total_nanos.abs();

                let total_seconds = abs_nanos / 1_000_000_000;
                let remaining_nanos = (abs_nanos % 1_000_000_000) as i64;

                let hours = total_seconds / 3600;
                let minutes = (total_seconds % 3600) / 60;
                let seconds = total_seconds % 60;

                let sign = if is_negative { "-" } else { "" };

                if hours != 0 {
                    write!(f, "{}{}H", sign, hours)?;
                }
                if minutes != 0 {
                    write!(f, "{}{}M", sign, minutes)?;
                }
                if seconds != 0 || remaining_nanos != 0 {
                    if remaining_nanos != 0 {
                        // Format with fractional seconds, trimming trailing zeros
                        let frac = format!("{:09}", remaining_nanos);
                        let frac = frac.trim_end_matches('0');
                        write!(f, "{}{}.{}S", sign, seconds, frac)?;
                    } else {
                        write!(f, "{}{}S", sign, seconds)?;
                    }
                } else if hours == 0 && minutes == 0 {
                    // Edge case: only fractional seconds
                    write!(f, "0S")?;
                }
            }
        } else if self.months == 0 && self.days == 0 {
            // Zero duration
            write!(f, "T0S")?;
        }

        Ok(())
    }
}

impl Ord for DurationValue {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        // Compare durations component-wise: months, then days, then seconds+nanos
        // This is an approximation since months and days have variable length
        match self.months.cmp(&other.months) {
            std::cmp::Ordering::Equal => match self.days.cmp(&other.days) {
                std::cmp::Ordering::Equal => {
                    // Compare total nanoseconds (seconds * 1e9 + nanos)
                    let self_total_nanos = self.seconds as i128 * 1_000_000_000 + self.nanos as i128;
                    let other_total_nanos =
                        other.seconds as i128 * 1_000_000_000 + other.nanos as i128;
                    self_total_nanos.cmp(&other_total_nanos)
                }
                ord => ord,
            },
            ord => ord,
        }
    }
}

impl PartialOrd for DurationValue {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

// Serde formats for chrono types
mod date_format {
    use chrono::NaiveDate;
    use serde::{self, Deserialize, Deserializer, Serializer};

    const FORMAT: &str = "%Y-%m-%d";

    pub fn serialize<S>(date: &NaiveDate, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let s = format!("{}", date.format(FORMAT));
        serializer.serialize_str(&s)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<NaiveDate, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        NaiveDate::parse_from_str(&s, FORMAT).map_err(serde::de::Error::custom)
    }
}

mod time_format {
    use chrono::NaiveTime;
    use serde::{self, Deserialize, Deserializer, Serializer};

    const FORMAT: &str = "%H:%M:%S%.f";

    pub fn serialize<S>(time: &NaiveTime, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let s = format!("{}", time.format(FORMAT));
        serializer.serialize_str(&s)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<NaiveTime, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        NaiveTime::parse_from_str(&s, FORMAT).map_err(serde::de::Error::custom)
    }
}

mod datetime_format {
    use chrono::{DateTime, FixedOffset};
    use serde::{self, Deserialize, Deserializer, Serializer};

    pub fn serialize<S>(datetime: &DateTime<FixedOffset>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let s = datetime.to_rfc3339();
        serializer.serialize_str(&s)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<FixedOffset>, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        DateTime::parse_from_rfc3339(&s).map_err(serde::de::Error::custom)
    }
}

mod local_datetime_format {
    use chrono::NaiveDateTime;
    use serde::{self, Deserialize, Deserializer, Serializer};

    const FORMAT: &str = "%Y-%m-%dT%H:%M:%S%.f";

    pub fn serialize<S>(datetime: &NaiveDateTime, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let s = format!("{}", datetime.format(FORMAT));
        serializer.serialize_str(&s)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<NaiveDateTime, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        NaiveDateTime::parse_from_str(&s, FORMAT).map_err(serde::de::Error::custom)
    }
}

mod offset_format {
    use chrono::FixedOffset;
    use serde::{self, Deserialize, Deserializer, Serializer};

    pub fn serialize<S>(offset: &FixedOffset, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let seconds = offset.local_minus_utc();
        serializer.serialize_i32(seconds)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<FixedOffset, D::Error>
    where
        D: Deserializer<'de>,
    {
        let seconds = i32::deserialize(deserializer)?;
        FixedOffset::east_opt(seconds).ok_or_else(|| serde::de::Error::custom("invalid timezone offset"))
    }
}

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

    #[test]
    fn test_date_display() {
        let date = DateValue::new(NaiveDate::from_ymd_opt(2020, 1, 15).unwrap());
        assert_eq!(date.to_string(), "2020-01-15");
    }

    #[test]
    fn test_date_accessors() {
        let date = DateValue::new(NaiveDate::from_ymd_opt(2020, 1, 15).unwrap());
        assert_eq!(date.year(), 2020);
        assert_eq!(date.month(), 1);
        assert_eq!(date.day(), 15);
    }

    #[test]
    fn test_duration_display() {
        let duration = DurationValue::new(2, 5, 3600, 0);
        assert_eq!(duration.to_string(), "P2M5DT1H"); // 3600 seconds = 1 hour
    }

    #[test]
    fn test_duration_accessors() {
        let duration = DurationValue::new(14, 10, 3661, 500_000_000);
        assert_eq!(duration.years(), 1);
        assert_eq!(duration.months_of_year(), 2);
        assert_eq!(duration.weeks(), 1);
        assert_eq!(duration.days_of_week(), 3);
        assert_eq!(duration.hours(), 1);
        assert_eq!(duration.minutes(), 1);
        assert_eq!(duration.seconds_of_minute(), 1);
        assert_eq!(duration.milliseconds(), 500);
    }
}
