proof_of_sql_parser/posql_time/
timezone.rs1use super::PoSQLTimestampError;
2use alloc::{string::ToString, sync::Arc};
3use core::fmt;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize, PartialEq, Eq)]
8pub struct PoSQLTimeZone {
9 offset: i32,
10}
11
12impl PoSQLTimeZone {
13 #[must_use]
15 pub const fn new(offset: i32) -> Self {
16 PoSQLTimeZone { offset }
17 }
18 #[must_use]
19 pub const fn utc() -> Self {
21 PoSQLTimeZone::new(0)
22 }
23 #[must_use]
25 pub const fn offset(self) -> i32 {
26 self.offset
27 }
28}
29
30impl TryFrom<&Option<Arc<str>>> for PoSQLTimeZone {
31 type Error = PoSQLTimestampError;
32
33 fn try_from(value: &Option<Arc<str>>) -> Result<Self, Self::Error> {
34 match value {
35 Some(tz_str) => {
36 let tz = Arc::as_ref(tz_str).to_uppercase();
37 match tz.as_str() {
38 "Z" | "UTC" | "00:00" | "+00:00" | "0:00" | "+0:00" => Ok(PoSQLTimeZone::utc()),
39 tz if tz.chars().count() == 6
40 && (tz.starts_with('+') || tz.starts_with('-')) =>
41 {
42 let sign = if tz.starts_with('-') { -1 } else { 1 };
43 let hours = tz[1..3]
44 .parse::<i32>()
45 .map_err(|_| PoSQLTimestampError::InvalidTimezoneOffset)?;
46 let minutes = tz[4..6]
47 .parse::<i32>()
48 .map_err(|_| PoSQLTimestampError::InvalidTimezoneOffset)?;
49 if hours > 12 || minutes >= 60 {
50 return Err(PoSQLTimestampError::InvalidTimezoneOffset);
51 }
52 let total_seconds = sign * ((hours * 3600) + (minutes * 60));
53 Ok(PoSQLTimeZone::new(total_seconds))
54 }
55 _ => Err(PoSQLTimestampError::InvalidTimezone {
56 timezone: tz.to_string(),
57 }),
58 }
59 }
60 None => Ok(PoSQLTimeZone::utc()),
61 }
62 }
63}
64
65impl fmt::Display for PoSQLTimeZone {
66 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67 let seconds = self.offset();
68 let hours = seconds / 3600;
69 let minutes = (seconds.abs() % 3600) / 60;
70 if seconds < 0 {
71 write!(f, "-{:02}:{:02}", hours.abs(), minutes)
72 } else {
73 write!(f, "+{hours:02}:{minutes:02}")
74 }
75 }
76}
77
78#[cfg(test)]
79mod timezone_arc_str_parsing {
80
81 use super::*;
82 use crate::posql_time::{timezone, PoSQLTimestampError::InvalidTimezoneOffset};
83 use alloc::format;
84
85 #[test]
86 fn test_parsing_from_arc_str_fixed_offset() {
87 let ss = "00:00";
88 let timezone_arc: Arc<str> = Arc::from(ss);
89 let timezone = timezone::PoSQLTimeZone::try_from(&Some(timezone_arc)).unwrap(); assert_eq!(format!("{timezone}"), "+00:00");
91 }
92
93 #[test]
94 fn test_parsing_from_arc_str_fixed_offset_positive() {
95 let input_timezone = "+01:15";
96 let timezone_arc: Arc<str> = Arc::from(input_timezone);
97 let timezone = timezone::PoSQLTimeZone::try_from(&Some(timezone_arc)).unwrap(); assert_eq!(format!("{timezone}"), "+01:15");
99 }
100
101 #[test]
102 fn test_parsing_from_arc_str_fixed_offset_negative() {
103 let input_timezone = "-01:03";
104 let timezone_arc: Arc<str> = Arc::from(input_timezone);
105 let timezone = timezone::PoSQLTimeZone::try_from(&Some(timezone_arc)).unwrap(); assert_eq!(format!("{timezone}"), "-01:03");
107 }
108
109 #[test]
110 fn check_for_invalid_timezone_hour_offset() {
111 let input_timezone = "-0A:03";
112 let timezone_arc: Arc<str> = Arc::from(input_timezone);
113 let offset_error = timezone::PoSQLTimeZone::try_from(&Some(timezone_arc)); assert_eq!(offset_error, Err(InvalidTimezoneOffset));
115
116 let input_timezone = "-13:03";
117 let timezone_arc: Arc<str> = Arc::from(input_timezone);
118 let offset_error = timezone::PoSQLTimeZone::try_from(&Some(timezone_arc)); assert_eq!(offset_error, Err(InvalidTimezoneOffset));
120
121 let input_timezone = "-11:60";
122 let timezone_arc: Arc<str> = Arc::from(input_timezone);
123 let offset_error = timezone::PoSQLTimeZone::try_from(&Some(timezone_arc)); assert_eq!(offset_error, Err(InvalidTimezoneOffset));
125 }
126
127 #[test]
128 fn check_for_invalid_timezone_minute_offset() {
129 let input_timezone = "-00:B3";
130 let timezone_arc: Arc<str> = Arc::from(input_timezone);
131 let offset_error = timezone::PoSQLTimeZone::try_from(&Some(timezone_arc)); assert_eq!(offset_error, Err(InvalidTimezoneOffset));
133 let input_timezone = "-00:83";
134 let timezone_arc: Arc<str> = Arc::from(input_timezone);
135 let offset_error = timezone::PoSQLTimeZone::try_from(&Some(timezone_arc)); assert_eq!(offset_error, Err(InvalidTimezoneOffset));
137 }
138
139 #[test]
140 fn test_invalid_timezone() {
141 let expected = PoSQLTimestampError::InvalidTimezone {
142 timezone: "WRONG".to_string(),
143 };
144 let timezone_input = "WRONG";
145 let timezone_arc: Arc<str> = Arc::from(timezone_input);
146 let timezone_err = timezone::PoSQLTimeZone::try_from(&Some(timezone_arc)); assert_eq!(expected, timezone_err.err().unwrap());
148 }
149
150 #[test]
151 fn test_when_none() {
152 let timezone = timezone::PoSQLTimeZone::try_from(&None).unwrap(); assert_eq!(format!("{timezone}"), "+00:00");
154 }
155}
156
157#[cfg(test)]
158mod timezone_parsing_tests {
159 use crate::posql_time::timezone;
160 use alloc::format;
161
162 #[test]
163 fn test_display_fixed_offset_positive() {
164 let timezone = timezone::PoSQLTimeZone::new(4500); assert_eq!(format!("{timezone}"), "+01:15");
166 }
167
168 #[test]
169 fn test_display_fixed_offset_negative() {
170 let timezone = timezone::PoSQLTimeZone::new(-3780); assert_eq!(format!("{timezone}"), "-01:03");
172 }
173
174 #[test]
175 fn test_display_utc() {
176 let timezone = timezone::PoSQLTimeZone::utc();
177 assert_eq!(format!("{timezone}"), "+00:00");
178 }
179}
180
181#[cfg(test)]
182mod timezone_offset_tests {
183 use crate::posql_time::{timestamp::PoSQLTimestamp, timezone};
184
185 #[test]
186 fn test_utc_timezone() {
187 let input = "2023-06-26T12:34:56Z";
188 let expected_timezone = timezone::PoSQLTimeZone::utc();
189 let result = PoSQLTimestamp::try_from(input).unwrap();
190 assert_eq!(result.timezone(), expected_timezone);
191 }
192
193 #[test]
194 fn test_positive_offset_timezone() {
195 let input = "2023-06-26T12:34:56+03:30";
196 let expected_timezone = timezone::PoSQLTimeZone::new(12600); let result = PoSQLTimestamp::try_from(input).unwrap();
198 assert_eq!(result.timezone(), expected_timezone);
199 }
200
201 #[test]
202 fn test_negative_offset_timezone() {
203 let input = "2023-06-26T12:34:56-04:00";
204 let expected_timezone = timezone::PoSQLTimeZone::new(-14400); let result = PoSQLTimestamp::try_from(input).unwrap();
206 assert_eq!(result.timezone(), expected_timezone);
207 }
208
209 #[test]
210 fn test_zero_offset_timezone() {
211 let input = "2023-06-26T12:34:56+00:00";
212 let expected_timezone = timezone::PoSQLTimeZone::utc(); let result = PoSQLTimestamp::try_from(input).unwrap();
214 assert_eq!(result.timezone(), expected_timezone);
215 }
216}