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