proof_of_sql_parser/posql_time/
unit.rs

1use super::PoSQLTimestampError;
2use core::fmt;
3use serde::{Deserialize, Serialize};
4
5/// An intermediate type representing the time units from a parsed query
6#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize, PartialEq, Eq)]
7pub enum PoSQLTimeUnit {
8    /// Represents seconds with precision 0: ex "2024-06-20 12:34:56"
9    Second,
10    /// Represents milliseconds with precision 3: ex "2024-06-20 12:34:56.123"
11    Millisecond,
12    /// Represents microseconds with precision 6: ex "2024-06-20 12:34:56.123456"
13    Microsecond,
14    /// Represents nanoseconds with precision 9: ex "2024-06-20 12:34:56.123456789"
15    Nanosecond,
16}
17
18impl From<PoSQLTimeUnit> for u64 {
19    fn from(value: PoSQLTimeUnit) -> u64 {
20        match value {
21            PoSQLTimeUnit::Second => 0,
22            PoSQLTimeUnit::Millisecond => 3,
23            PoSQLTimeUnit::Microsecond => 6,
24            PoSQLTimeUnit::Nanosecond => 9,
25        }
26    }
27}
28
29impl TryFrom<&str> for PoSQLTimeUnit {
30    type Error = PoSQLTimestampError;
31    fn try_from(value: &str) -> Result<Self, PoSQLTimestampError> {
32        match value {
33            "0" => Ok(PoSQLTimeUnit::Second),
34            "3" => Ok(PoSQLTimeUnit::Millisecond),
35            "6" => Ok(PoSQLTimeUnit::Microsecond),
36            "9" => Ok(PoSQLTimeUnit::Nanosecond),
37            _ => Err(PoSQLTimestampError::UnsupportedPrecision {
38                error: value.into(),
39            }),
40        }
41    }
42}
43
44impl fmt::Display for PoSQLTimeUnit {
45    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46        match self {
47            PoSQLTimeUnit::Second => write!(f, "seconds (precision: 0)"),
48            PoSQLTimeUnit::Millisecond => write!(f, "milliseconds (precision: 3)"),
49            PoSQLTimeUnit::Microsecond => write!(f, "microseconds (precision: 6)"),
50            PoSQLTimeUnit::Nanosecond => write!(f, "nanoseconds (precision: 9)"),
51        }
52    }
53}
54
55// expect(deprecated) for the sole purpose of testing that
56// timestamp precision is parsed correctly.
57#[cfg(test)]
58#[expect(deprecated, clippy::missing_panics_doc)]
59mod time_unit_tests {
60    use super::*;
61    use crate::posql_time::{PoSQLTimestamp, PoSQLTimestampError};
62    use alloc::string::ToString;
63    use chrono::{TimeZone, Utc};
64
65    #[test]
66    #[expect(clippy::unnecessary_fallible_conversions)]
67    fn test_u64_conversion() {
68        assert_eq!(PoSQLTimeUnit::Second.try_into(), Ok(0));
69        assert_eq!(PoSQLTimeUnit::Millisecond.try_into(), Ok(3));
70        assert_eq!(PoSQLTimeUnit::Microsecond.try_into(), Ok(6));
71        assert_eq!(PoSQLTimeUnit::Nanosecond.try_into(), Ok(9));
72    }
73
74    #[test]
75    fn test_precision_display() {
76        let input = "2023-06-26T12:34:56Z";
77        let result = PoSQLTimestamp::try_from(input).unwrap();
78        assert_eq!(result.timeunit().to_string(), "seconds (precision: 0)");
79        let input = "2023-06-26T12:34:56.123456Z";
80        let result = PoSQLTimestamp::try_from(input).unwrap();
81        assert_eq!(result.timeunit().to_string(), "microseconds (precision: 6)");
82        let input = "2023-06-26T12:34:56.123Z";
83        let result = PoSQLTimestamp::try_from(input).unwrap();
84        assert_eq!(result.timeunit().to_string(), "milliseconds (precision: 3)");
85
86        let input = "2023-06-26T12:34:56.123456789Z";
87        let result = PoSQLTimestamp::try_from(input).unwrap();
88        assert_eq!(result.timeunit().to_string(), "nanoseconds (precision: 9)");
89    }
90
91    #[test]
92    fn test_valid_precisions() {
93        assert_eq!(PoSQLTimeUnit::try_from("0"), Ok(PoSQLTimeUnit::Second));
94        assert_eq!(PoSQLTimeUnit::try_from("3"), Ok(PoSQLTimeUnit::Millisecond));
95        assert_eq!(PoSQLTimeUnit::try_from("6"), Ok(PoSQLTimeUnit::Microsecond));
96        assert_eq!(PoSQLTimeUnit::try_from("9"), Ok(PoSQLTimeUnit::Nanosecond));
97    }
98
99    #[test]
100    fn test_invalid_precision() {
101        let invalid_precisions = [
102            "1", "2", "4", "5", "7", "8", "10", "zero", "three", "cat", "-1", "-2",
103        ]; // Testing all your various invalid inputs
104        for &value in &invalid_precisions {
105            let result = PoSQLTimeUnit::try_from(value);
106            assert!(matches!(
107                result,
108                Err(PoSQLTimestampError::UnsupportedPrecision { .. })
109            ));
110        }
111    }
112
113    #[test]
114    fn test_rfc3339_timestamp_with_milliseconds() {
115        let input = "2023-06-26T12:34:56.123Z";
116        let expected = Utc.ymd(2023, 6, 26).and_hms_milli(12, 34, 56, 123);
117        let result = PoSQLTimestamp::try_from(input).unwrap();
118        assert_eq!(result.timeunit(), PoSQLTimeUnit::Millisecond);
119        assert_eq!(
120            result.timestamp().timestamp_millis(),
121            expected.timestamp_millis()
122        );
123    }
124
125    #[test]
126    fn test_rfc3339_timestamp_with_microseconds() {
127        let input = "2023-06-26T12:34:56.123456Z";
128        let expected = Utc.ymd(2023, 6, 26).and_hms_micro(12, 34, 56, 123_456);
129        let result = PoSQLTimestamp::try_from(input).unwrap();
130        assert_eq!(result.timeunit(), PoSQLTimeUnit::Microsecond);
131        assert_eq!(
132            result.timestamp().timestamp_micros(),
133            expected.timestamp_micros()
134        );
135    }
136    #[test]
137    fn test_rfc3339_timestamp_with_nanoseconds() {
138        let input = "2023-06-26T12:34:56.123456789Z";
139        let expected = Utc.ymd(2023, 6, 26).and_hms_nano(12, 34, 56, 123_456_789);
140        let result = PoSQLTimestamp::try_from(input).unwrap();
141        assert_eq!(result.timeunit(), PoSQLTimeUnit::Nanosecond);
142        assert_eq!(
143            result.timestamp().timestamp_nanos_opt().unwrap(),
144            expected.timestamp_nanos_opt().unwrap()
145        );
146    }
147}