vibesql_types/temporal/
time.rs

1//! SQL TIME type implementation
2
3use std::{cmp::Ordering, fmt, str::FromStr};
4
5/// SQL TIME type - represents a time without date
6///
7/// Format: HH:MM:SS or HH:MM:SS.fff (e.g., '14:30:00' or '14:30:00.123')
8/// Stored as hour, minute, second, nanosecond components for correct comparison
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub struct Time {
11    pub hour: u8,        // 0-23
12    pub minute: u8,      // 0-59
13    pub second: u8,      // 0-59
14    pub nanosecond: u32, // 0-999999999
15}
16
17impl Time {
18    /// Create a new Time (validation is basic)
19    pub fn new(hour: u8, minute: u8, second: u8, nanosecond: u32) -> Result<Self, String> {
20        if hour > 23 {
21            return Err(format!("Invalid hour: {}", hour));
22        }
23        if minute > 59 {
24            return Err(format!("Invalid minute: {}", minute));
25        }
26        if second > 59 {
27            return Err(format!("Invalid second: {}", second));
28        }
29        if nanosecond > 999_999_999 {
30            return Err(format!("Invalid nanosecond: {}", nanosecond));
31        }
32        Ok(Time { hour, minute, second, nanosecond })
33    }
34}
35
36impl FromStr for Time {
37    type Err = String;
38
39    fn from_str(s: &str) -> Result<Self, Self::Err> {
40        // Parse format: HH:MM:SS or HH:MM:SS.fff
41        let (time_part, frac_part) = if let Some(dot_pos) = s.find('.') {
42            (&s[..dot_pos], Some(&s[dot_pos + 1..]))
43        } else {
44            (s, None)
45        };
46
47        let parts: Vec<&str> = time_part.split(':').collect();
48        if parts.len() != 3 {
49            return Err(format!("Invalid time format: '{}' (expected HH:MM:SS)", s));
50        }
51
52        let hour = parts[0].parse::<u8>().map_err(|_| format!("Invalid hour: '{}'", parts[0]))?;
53        let minute =
54            parts[1].parse::<u8>().map_err(|_| format!("Invalid minute: '{}'", parts[1]))?;
55        let second =
56            parts[2].parse::<u8>().map_err(|_| format!("Invalid second: '{}'", parts[2]))?;
57
58        // Parse fractional seconds if present
59        let nanosecond = if let Some(frac) = frac_part {
60            // Pad or truncate to 9 digits (nanoseconds)
61            let padded = format!("{:0<9}", frac);
62            let truncated = &padded[..9.min(padded.len())];
63            truncated
64                .parse::<u32>()
65                .map_err(|_| format!("Invalid fractional seconds: '{}'", frac))?
66        } else {
67            0
68        };
69
70        Time::new(hour, minute, second, nanosecond)
71    }
72}
73
74impl fmt::Display for Time {
75    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76        if self.nanosecond == 0 {
77            write!(f, "{:02}:{:02}:{:02}", self.hour, self.minute, self.second)
78        } else {
79            // Display fractional seconds, trimming trailing zeros for readability
80            let frac = format!("{:09}", self.nanosecond).trim_end_matches('0').to_string();
81            write!(f, "{:02}:{:02}:{:02}.{}", self.hour, self.minute, self.second, frac)
82        }
83    }
84}
85
86impl PartialOrd for Time {
87    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
88        Some(self.cmp(other))
89    }
90}
91
92impl Ord for Time {
93    fn cmp(&self, other: &Self) -> Ordering {
94        self.hour
95            .cmp(&other.hour)
96            .then_with(|| self.minute.cmp(&other.minute))
97            .then_with(|| self.second.cmp(&other.second))
98            .then_with(|| self.nanosecond.cmp(&other.nanosecond))
99    }
100}