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 let err: Error = TypeError::Temporal {
124 kind: TemporalKind::InvalidHour,
125 message: format!("invalid hour value '{}'", sub_frag.text()),
126 fragment: sub_frag,
127 }
128 .into();
129 err
130 })?;
131 offset += time_fragment_parts[0].len() + 1;
132
133 let minute = time_fragment_parts[1].trim().parse::<u32>().map_err(|_| {
134 let sub_frag = fragment.sub_fragment(offset, time_fragment_parts[1].len());
135 let err: Error = TypeError::Temporal {
136 kind: TemporalKind::InvalidMinute,
137 message: format!("invalid minute value '{}'", sub_frag.text()),
138 fragment: sub_frag,
139 }
140 .into();
141 err
142 })?;
143 offset += time_fragment_parts[1].len() + 1;
144
145 let seconds_with_fraction = time_fragment_parts[2].trim();
146 let (second, nanosecond) = if seconds_with_fraction.contains('.') {
147 let second_parts: Vec<&str> = seconds_with_fraction.split('.').collect();
148 if second_parts.len() != 2 {
149 let sub_frag = fragment.sub_fragment(offset, time_fragment_parts[2].len());
150 return Err(TypeError::Temporal {
151 kind: TemporalKind::InvalidFractionalSeconds,
152 message: format!("invalid fractional seconds value '{}'", sub_frag.text()),
153 fragment: sub_frag,
154 }
155 .into());
156 }
157
158 let second = second_parts[0].parse::<u32>().map_err(|_| {
159 let sub_frag = fragment.sub_fragment(offset, time_fragment_parts[2].len());
160 let err: Error = TypeError::Temporal {
161 kind: TemporalKind::InvalidSecond,
162 message: format!("invalid second value '{}'", sub_frag.text()),
163 fragment: sub_frag,
164 }
165 .into();
166 err
167 })?;
168 let fraction_str = second_parts[1];
169
170 let padded_fraction = if fraction_str.len() < 9 {
171 format!("{:0<9}", fraction_str)
172 } else {
173 fraction_str[..9].to_string()
174 };
175
176 let nanosecond = padded_fraction.parse::<u32>().map_err(|_| {
177 let sub_frag = fragment.sub_fragment(offset, time_fragment_parts[2].len());
178 let err: Error = TypeError::Temporal {
179 kind: TemporalKind::InvalidFractionalSeconds,
180 message: format!("invalid fractional seconds value '{}'", sub_frag.text()),
181 fragment: sub_frag,
182 }
183 .into();
184 err
185 })?;
186 (second, nanosecond)
187 } else {
188 let second = seconds_with_fraction.parse::<u32>().map_err(|_| {
189 let sub_frag = fragment.sub_fragment(offset, time_fragment_parts[2].len());
190 let err: Error = TypeError::Temporal {
191 kind: TemporalKind::InvalidSecond,
192 message: format!("invalid second value '{}'", sub_frag.text()),
193 fragment: sub_frag,
194 }
195 .into();
196 err
197 })?;
198 (second, 0)
199 };
200
201 Time::new(hour, minute, second, nanosecond).ok_or_else(|| {
202 let err: Error = TypeError::Temporal {
203 kind: TemporalKind::InvalidTimeValues,
204 message: "invalid time values".into(),
205 fragment,
206 }
207 .into();
208 err
209 })
210}
211
212#[cfg(test)]
213pub mod tests {
214 use super::parse_time;
215 use crate::fragment::Fragment;
216
217 #[test]
218 fn test_basic() {
219 let fragment = Fragment::testing("14:30:00");
220 let time = parse_time(fragment).unwrap();
221 assert_eq!(time.to_string(), "14:30:00.000000000");
222 }
223
224 #[test]
225 fn test_with_timezone_z() {
226 let fragment = Fragment::testing("14:30:00Z");
227 let time = parse_time(fragment).unwrap();
228 assert_eq!(time.to_string(), "14:30:00.000000000");
229 }
230
231 #[test]
232 fn test_with_milliseconds() {
233 let fragment = Fragment::testing("14:30:00.123");
234 let time = parse_time(fragment).unwrap();
235 assert_eq!(time.to_string(), "14:30:00.123000000");
236 }
237
238 #[test]
239 fn test_with_microseconds() {
240 let fragment = Fragment::testing("14:30:00.123456");
241 let time = parse_time(fragment).unwrap();
242 assert_eq!(time.to_string(), "14:30:00.123456000");
243 }
244
245 #[test]
246 fn test_with_nanoseconds() {
247 let fragment = Fragment::testing("14:30:00.123456789");
248 let time = parse_time(fragment).unwrap();
249 assert_eq!(time.to_string(), "14:30:00.123456789");
250 }
251
252 #[test]
253 fn test_with_utc_timezone() {
254 let fragment = Fragment::testing("14:30:00Z");
255 let time = parse_time(fragment).unwrap();
256 assert_eq!(time.to_string(), "14:30:00.000000000");
257 }
258
259 #[test]
260 fn test_boundaries() {
261 let fragment = Fragment::testing("00:00:00");
262 let time = parse_time(fragment).unwrap();
263 assert_eq!(time.to_string(), "00:00:00.000000000");
264
265 let fragment = Fragment::testing("23:59:59");
266 let time = parse_time(fragment).unwrap();
267 assert_eq!(time.to_string(), "23:59:59.000000000");
268 }
269
270 #[test]
271 fn test_invalid_format() {
272 let fragment = Fragment::testing("14:30");
273 let err = parse_time(fragment).unwrap_err();
274 assert_eq!(err.0.code, "TEMPORAL_003");
275 }
276
277 #[test]
278 fn test_invalid_hour() {
279 let fragment = Fragment::testing("invalid:30:00");
280 let result = parse_time(fragment);
281 assert!(result.is_err());
282 let err = result.unwrap_err();
283 assert_eq!(err.0.code, "TEMPORAL_005");
284 }
285
286 #[test]
287 fn test_invalid_minute() {
288 let fragment = Fragment::testing("14:invalid:00");
289 let result = parse_time(fragment);
290 assert!(result.is_err());
291 let err = result.unwrap_err();
292 assert_eq!(err.0.code, "TEMPORAL_005");
293 }
294
295 #[test]
296 fn test_invalid_second() {
297 let fragment = Fragment::testing("14:30:invalid");
298 let result = parse_time(fragment);
299 assert!(result.is_err());
300 let err = result.unwrap_err();
301 assert_eq!(err.0.code, "TEMPORAL_005");
302 }
303
304 #[test]
305 fn test_invalid_time_values() {
306 let fragment = Fragment::testing("25:70:80");
307 let result = parse_time(fragment);
308 assert!(result.is_err());
309 let err = result.unwrap_err();
310 assert_eq!(err.0.code, "TEMPORAL_013");
311 }
312
313 #[test]
314 fn test_invalid_fractional_seconds() {
315 let fragment = Fragment::testing("14:30:00.123.456");
316 let result = parse_time(fragment);
317 assert!(result.is_err());
318 let err = result.unwrap_err();
319 assert_eq!(err.0.code, "TEMPORAL_011");
320 }
321
322 #[test]
323 fn test_lowercase_z_rejected() {
324 let fragment = Fragment::testing("14:30:00z");
325 let result = parse_time(fragment);
326 assert!(result.is_err());
327 let err = result.unwrap_err();
328 assert_eq!(err.0.code, "TEMPORAL_010"); }
330}