reifydb_value/value/temporal/parse/
time.rs1use crate::{
5 error::{Error, TemporalKind, TypeError},
6 fragment::Fragment,
7 value::Time,
8};
9
10pub fn parse_time(fragment: Fragment) -> Result<Time, Error> {
11 let fragment_value = fragment.text();
12 let mut time_str = fragment_value;
13
14 if time_str.ends_with('z') {
15 let parts: Vec<&str> = time_str.split(':').collect();
16 if parts.len() == 3 {
17 let hours_len = parts[0].len();
18 let minutes_len = parts[1].len();
19 let offset = hours_len + 1 + minutes_len + 1;
20 let second_len = parts[2].len();
21 let sub_frag = fragment.sub_fragment(offset, second_len);
22 return Err(TypeError::Temporal {
23 kind: TemporalKind::InvalidSecond,
24 message: format!("invalid second value '{}'", sub_frag.text()),
25 fragment: sub_frag,
26 }
27 .into());
28 }
29 }
30
31 if time_str.ends_with('Z') {
32 time_str = &time_str[..time_str.len() - 1];
33 }
34
35 let time_fragment_parts: Vec<&str> = time_str.split(':').collect();
36
37 if time_fragment_parts.len() != 3 {
38 return Err(TypeError::Temporal {
39 kind: TemporalKind::InvalidTimeFormat,
40 message: "invalid time format".into(),
41 fragment,
42 }
43 .into());
44 }
45
46 let mut offset = 0;
47 if time_fragment_parts[0].trim().is_empty() {
48 let sub_frag = fragment.sub_fragment(offset, time_fragment_parts[0].len());
49 return Err(TypeError::Temporal {
50 kind: TemporalKind::EmptyTimeComponent,
51 message: "empty time component".into(),
52 fragment: sub_frag,
53 }
54 .into());
55 }
56 offset += time_fragment_parts[0].len() + 1;
57
58 if time_fragment_parts[1].trim().is_empty() {
59 let sub_frag = fragment.sub_fragment(offset, time_fragment_parts[1].len());
60 return Err(TypeError::Temporal {
61 kind: TemporalKind::EmptyTimeComponent,
62 message: "empty time component".into(),
63 fragment: sub_frag,
64 }
65 .into());
66 }
67 offset += time_fragment_parts[1].len() + 1;
68
69 if time_fragment_parts[2].trim().is_empty() {
70 let sub_frag = fragment.sub_fragment(offset, time_fragment_parts[2].len());
71 return Err(TypeError::Temporal {
72 kind: TemporalKind::EmptyTimeComponent,
73 message: "empty time component".into(),
74 fragment: sub_frag,
75 }
76 .into());
77 }
78
79 offset = 0;
80
81 if time_fragment_parts[0].len() != 2 {
82 let frag = fragment.sub_fragment(offset, time_fragment_parts[0].len());
83 return Err(TypeError::Temporal {
84 kind: TemporalKind::InvalidTimeComponentFormat {
85 component: "hour".to_string(),
86 },
87 message: format!("invalid {} format '{}'", "hour", frag.text()),
88 fragment: frag,
89 }
90 .into());
91 }
92 offset += time_fragment_parts[0].len() + 1;
93
94 if time_fragment_parts[1].len() != 2 {
95 let frag = fragment.sub_fragment(offset, time_fragment_parts[1].len());
96 return Err(TypeError::Temporal {
97 kind: TemporalKind::InvalidTimeComponentFormat {
98 component: "minute".to_string(),
99 },
100 message: format!("invalid {} format '{}'", "minute", frag.text()),
101 fragment: frag,
102 }
103 .into());
104 }
105 offset += time_fragment_parts[1].len() + 1;
106
107 let second_base = time_fragment_parts[2].split('.').next().unwrap();
108 if second_base.len() != 2 {
109 let frag = fragment.sub_fragment(offset, second_base.len());
110 return Err(TypeError::Temporal {
111 kind: TemporalKind::InvalidTimeComponentFormat {
112 component: "second".to_string(),
113 },
114 message: format!("invalid {} format '{}'", "second", frag.text()),
115 fragment: frag,
116 }
117 .into());
118 }
119
120 offset = 0;
121 let hour = time_fragment_parts[0].trim().parse::<u32>().map_err(|_| {
122 let sub_frag = fragment.sub_fragment(offset, time_fragment_parts[0].len());
123 Error::from(TypeError::Temporal {
124 kind: TemporalKind::InvalidHour,
125 message: format!("invalid hour value '{}'", sub_frag.text()),
126 fragment: sub_frag,
127 })
128 })?;
129 offset += time_fragment_parts[0].len() + 1;
130
131 let minute = time_fragment_parts[1].trim().parse::<u32>().map_err(|_| {
132 let sub_frag = fragment.sub_fragment(offset, time_fragment_parts[1].len());
133 Error::from(TypeError::Temporal {
134 kind: TemporalKind::InvalidMinute,
135 message: format!("invalid minute value '{}'", sub_frag.text()),
136 fragment: sub_frag,
137 })
138 })?;
139 offset += time_fragment_parts[1].len() + 1;
140
141 let seconds_with_fraction = time_fragment_parts[2].trim();
142 let (second, nanosecond) = if seconds_with_fraction.contains('.') {
143 let second_parts: Vec<&str> = seconds_with_fraction.split('.').collect();
144 if second_parts.len() != 2 {
145 let sub_frag = fragment.sub_fragment(offset, time_fragment_parts[2].len());
146 return Err(TypeError::Temporal {
147 kind: TemporalKind::InvalidFractionalSeconds,
148 message: format!("invalid fractional seconds value '{}'", sub_frag.text()),
149 fragment: sub_frag,
150 }
151 .into());
152 }
153
154 let second = second_parts[0].parse::<u32>().map_err(|_| {
155 let sub_frag = fragment.sub_fragment(offset, time_fragment_parts[2].len());
156 Error::from(TypeError::Temporal {
157 kind: TemporalKind::InvalidSecond,
158 message: format!("invalid second value '{}'", sub_frag.text()),
159 fragment: sub_frag,
160 })
161 })?;
162 let fraction_str = second_parts[1];
163
164 let padded_fraction = if fraction_str.len() < 9 {
165 format!("{:0<9}", fraction_str)
166 } else {
167 fraction_str[..9].to_string()
168 };
169
170 let nanosecond = padded_fraction.parse::<u32>().map_err(|_| {
171 let sub_frag = fragment.sub_fragment(offset, time_fragment_parts[2].len());
172 Error::from(TypeError::Temporal {
173 kind: TemporalKind::InvalidFractionalSeconds,
174 message: format!("invalid fractional seconds value '{}'", sub_frag.text()),
175 fragment: sub_frag,
176 })
177 })?;
178 (second, nanosecond)
179 } else {
180 let second = seconds_with_fraction.parse::<u32>().map_err(|_| {
181 let sub_frag = fragment.sub_fragment(offset, time_fragment_parts[2].len());
182 Error::from(TypeError::Temporal {
183 kind: TemporalKind::InvalidSecond,
184 message: format!("invalid second value '{}'", sub_frag.text()),
185 fragment: sub_frag,
186 })
187 })?;
188 (second, 0)
189 };
190
191 Time::new(hour, minute, second, nanosecond).ok_or_else(|| {
192 Error::from(TypeError::Temporal {
193 kind: TemporalKind::InvalidTimeValues,
194 message: "invalid time values".into(),
195 fragment,
196 })
197 })
198}
199
200#[cfg(test)]
201pub mod tests {
202 use super::parse_time;
203 use crate::fragment::Fragment;
204
205 #[test]
206 fn test_basic() {
207 let fragment = Fragment::testing("14:30:00");
208 let time = parse_time(fragment).unwrap();
209 assert_eq!(time.to_string(), "14:30:00.000000000");
210 }
211
212 #[test]
213 fn test_with_timezone_z() {
214 let fragment = Fragment::testing("14:30:00Z");
215 let time = parse_time(fragment).unwrap();
216 assert_eq!(time.to_string(), "14:30:00.000000000");
217 }
218
219 #[test]
220 fn test_with_milliseconds() {
221 let fragment = Fragment::testing("14:30:00.123");
222 let time = parse_time(fragment).unwrap();
223 assert_eq!(time.to_string(), "14:30:00.123000000");
224 }
225
226 #[test]
227 fn test_with_microseconds() {
228 let fragment = Fragment::testing("14:30:00.123456");
229 let time = parse_time(fragment).unwrap();
230 assert_eq!(time.to_string(), "14:30:00.123456000");
231 }
232
233 #[test]
234 fn test_with_nanoseconds() {
235 let fragment = Fragment::testing("14:30:00.123456789");
236 let time = parse_time(fragment).unwrap();
237 assert_eq!(time.to_string(), "14:30:00.123456789");
238 }
239
240 #[test]
241 fn test_with_utc_timezone() {
242 let fragment = Fragment::testing("14:30:00Z");
243 let time = parse_time(fragment).unwrap();
244 assert_eq!(time.to_string(), "14:30:00.000000000");
245 }
246
247 #[test]
248 fn test_boundaries() {
249 let fragment = Fragment::testing("00:00:00");
250 let time = parse_time(fragment).unwrap();
251 assert_eq!(time.to_string(), "00:00:00.000000000");
252
253 let fragment = Fragment::testing("23:59:59");
254 let time = parse_time(fragment).unwrap();
255 assert_eq!(time.to_string(), "23:59:59.000000000");
256 }
257
258 #[test]
259 fn test_invalid_format() {
260 let fragment = Fragment::testing("14:30");
261 let err = parse_time(fragment).unwrap_err();
262 assert_eq!(err.0.code, "TEMPORAL_003");
263 }
264
265 #[test]
266 fn test_invalid_hour() {
267 let fragment = Fragment::testing("invalid:30:00");
268 let result = parse_time(fragment);
269 assert!(result.is_err());
270 let err = result.unwrap_err();
271 assert_eq!(err.0.code, "TEMPORAL_005");
272 }
273
274 #[test]
275 fn test_invalid_minute() {
276 let fragment = Fragment::testing("14:invalid:00");
277 let result = parse_time(fragment);
278 assert!(result.is_err());
279 let err = result.unwrap_err();
280 assert_eq!(err.0.code, "TEMPORAL_005");
281 }
282
283 #[test]
284 fn test_invalid_second() {
285 let fragment = Fragment::testing("14:30:invalid");
286 let result = parse_time(fragment);
287 assert!(result.is_err());
288 let err = result.unwrap_err();
289 assert_eq!(err.0.code, "TEMPORAL_005");
290 }
291
292 #[test]
293 fn test_invalid_time_values() {
294 let fragment = Fragment::testing("25:70:80");
295 let result = parse_time(fragment);
296 assert!(result.is_err());
297 let err = result.unwrap_err();
298 assert_eq!(err.0.code, "TEMPORAL_013");
299 }
300
301 #[test]
302 fn test_invalid_fractional_seconds() {
303 let fragment = Fragment::testing("14:30:00.123.456");
304 let result = parse_time(fragment);
305 assert!(result.is_err());
306 let err = result.unwrap_err();
307 assert_eq!(err.0.code, "TEMPORAL_011");
308 }
309
310 #[test]
311 fn test_lowercase_z_rejected() {
312 let fragment = Fragment::testing("14:30:00z");
313 let result = parse_time(fragment);
314 assert!(result.is_err());
315 let err = result.unwrap_err();
316 assert_eq!(err.0.code, "TEMPORAL_010"); }
318}