vibesql_types/temporal/
date.rs

1//! SQL DATE type implementation
2
3use std::{cmp::Ordering, fmt, str::FromStr};
4
5/// SQL DATE type - represents a date without time
6///
7/// Format: YYYY-MM-DD (e.g., '2024-01-01')
8/// Stored as year, month, day components for correct comparison
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub struct Date {
11    pub year: i32,
12    pub month: u8, // 1-12
13    pub day: u8,   // 1-31
14}
15
16impl Date {
17    /// Create a new Date (validation is basic)
18    pub fn new(year: i32, month: u8, day: u8) -> Result<Self, String> {
19        if !(1..=12).contains(&month) {
20            return Err(format!("Invalid month: {}", month));
21        }
22        if !(1..=31).contains(&day) {
23            return Err(format!("Invalid day: {}", day));
24        }
25        Ok(Date { year, month, day })
26    }
27}
28
29impl FromStr for Date {
30    type Err = String;
31
32    fn from_str(s: &str) -> Result<Self, Self::Err> {
33        // Parse format: YYYY-MM-DD
34        let parts: Vec<&str> = s.split('-').collect();
35        if parts.len() != 3 {
36            return Err(format!("Invalid date format: '{}' (expected YYYY-MM-DD)", s));
37        }
38
39        let year = parts[0].parse::<i32>().map_err(|_| format!("Invalid year: '{}'", parts[0]))?;
40        let month = parts[1].parse::<u8>().map_err(|_| format!("Invalid month: '{}'", parts[1]))?;
41        let day = parts[2].parse::<u8>().map_err(|_| format!("Invalid day: '{}'", parts[2]))?;
42
43        Date::new(year, month, day)
44    }
45}
46
47impl fmt::Display for Date {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        write!(f, "{:04}-{:02}-{:02}", self.year, self.month, self.day)
50    }
51}
52
53impl PartialOrd for Date {
54    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
55        Some(self.cmp(other))
56    }
57}
58
59impl Ord for Date {
60    fn cmp(&self, other: &Self) -> Ordering {
61        self.year
62            .cmp(&other.year)
63            .then_with(|| self.month.cmp(&other.month))
64            .then_with(|| self.day.cmp(&other.day))
65    }
66}