Skip to main content

reifydb_type/value/
time.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use std::fmt::{self, Display, Formatter};
5
6use serde::{
7	Deserialize, Deserializer, Serialize, Serializer,
8	de::{self, Visitor},
9};
10
11use crate::{
12	error::{TemporalKind, TypeError},
13	fragment::Fragment,
14};
15
16#[repr(transparent)]
17#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
18pub struct Time {
19	nanos_since_midnight: u64,
20}
21
22impl Time {
23	const MAX_NANOS_IN_DAY: u64 = 86_399_999_999_999;
24	const NANOS_PER_SECOND: u64 = 1_000_000_000;
25	const NANOS_PER_MINSVTE: u64 = 60 * Self::NANOS_PER_SECOND;
26	const NANOS_PER_HOUR: u64 = 60 * Self::NANOS_PER_MINSVTE;
27
28	fn overflow_err(message: impl Into<String>) -> TypeError {
29		TypeError::Temporal {
30			kind: TemporalKind::TimeOverflow {
31				message: message.into(),
32			},
33			message: "time overflow".to_string(),
34			fragment: Fragment::None,
35		}
36	}
37
38	pub fn new(hour: u32, min: u32, sec: u32, nano: u32) -> Option<Self> {
39		if hour >= 24 || min >= 60 || sec >= 60 || nano >= Self::NANOS_PER_SECOND as u32 {
40			return None;
41		}
42
43		let nanos = hour as u64 * Self::NANOS_PER_HOUR
44			+ min as u64 * Self::NANOS_PER_MINSVTE
45			+ sec as u64 * Self::NANOS_PER_SECOND
46			+ nano as u64;
47
48		Some(Self {
49			nanos_since_midnight: nanos,
50		})
51	}
52
53	pub fn from_hms(hour: u32, min: u32, sec: u32) -> Result<Self, Box<TypeError>> {
54		Self::new(hour, min, sec, 0).ok_or_else(|| {
55			Box::new(Self::overflow_err(format!("invalid time: {:02}:{:02}:{:02}", hour, min, sec)))
56		})
57	}
58
59	pub fn from_hms_nano(hour: u32, min: u32, sec: u32, nano: u32) -> Result<Self, Box<TypeError>> {
60		Self::new(hour, min, sec, nano).ok_or_else(|| {
61			Box::new(Self::overflow_err(format!(
62				"invalid time: {:02}:{:02}:{:02}.{:09}",
63				hour, min, sec, nano
64			)))
65		})
66	}
67
68	pub fn midnight() -> Self {
69		Self {
70			nanos_since_midnight: 0,
71		}
72	}
73
74	pub fn noon() -> Self {
75		Self {
76			nanos_since_midnight: 12 * Self::NANOS_PER_HOUR,
77		}
78	}
79
80	pub fn hour(&self) -> u32 {
81		(self.nanos_since_midnight / Self::NANOS_PER_HOUR) as u32
82	}
83
84	pub fn minute(&self) -> u32 {
85		((self.nanos_since_midnight % Self::NANOS_PER_HOUR) / Self::NANOS_PER_MINSVTE) as u32
86	}
87
88	pub fn second(&self) -> u32 {
89		((self.nanos_since_midnight % Self::NANOS_PER_MINSVTE) / Self::NANOS_PER_SECOND) as u32
90	}
91
92	pub fn nanosecond(&self) -> u32 {
93		(self.nanos_since_midnight % Self::NANOS_PER_SECOND) as u32
94	}
95
96	pub fn to_nanos_since_midnight(&self) -> u64 {
97		self.nanos_since_midnight
98	}
99
100	pub fn from_nanos_since_midnight(nanos: u64) -> Option<Self> {
101		if nanos > Self::MAX_NANOS_IN_DAY {
102			return None;
103		}
104		Some(Self {
105			nanos_since_midnight: nanos,
106		})
107	}
108}
109
110impl Display for Time {
111	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
112		let hours = self.hour();
113		let minutes = self.minute();
114		let seconds = self.second();
115		let nanos = self.nanosecond();
116
117		write!(f, "{:02}:{:02}:{:02}.{:09}", hours, minutes, seconds, nanos)
118	}
119}
120
121impl Serialize for Time {
122	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
123	where
124		S: Serializer,
125	{
126		serializer.serialize_str(&self.to_string())
127	}
128}
129
130struct TimeVisitor;
131
132impl<'de> Visitor<'de> for TimeVisitor {
133	type Value = Time;
134
135	fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
136		formatter.write_str("a time in ISO 8601 format (HH:MM:SS or HH:MM:SS.nnnnnnnnn)")
137	}
138
139	fn visit_str<E>(self, value: &str) -> Result<Time, E>
140	where
141		E: de::Error,
142	{
143		let (time_part, nano_part) = if let Some(dot_pos) = value.find('.') {
144			(&value[..dot_pos], Some(&value[dot_pos + 1..]))
145		} else {
146			(value, None)
147		};
148
149		let time_parts: Vec<&str> = time_part.split(':').collect();
150		if time_parts.len() != 3 {
151			return Err(E::custom(format!("invalid time format: {}", value)));
152		}
153
154		let hour = time_parts[0]
155			.parse::<u32>()
156			.map_err(|_| E::custom(format!("invalid hour: {}", time_parts[0])))?;
157		let minute = time_parts[1]
158			.parse::<u32>()
159			.map_err(|_| E::custom(format!("invalid minute: {}", time_parts[1])))?;
160		let second = time_parts[2]
161			.parse::<u32>()
162			.map_err(|_| E::custom(format!("invalid second: {}", time_parts[2])))?;
163
164		let nano = if let Some(nano_str) = nano_part {
165			let padded = if nano_str.len() < 9 {
166				format!("{:0<9}", nano_str)
167			} else {
168				nano_str[..9].to_string()
169			};
170			padded.parse::<u32>().map_err(|_| E::custom(format!("invalid nanoseconds: {}", nano_str)))?
171		} else {
172			0
173		};
174
175		Time::new(hour, minute, second, nano).ok_or_else(|| {
176			E::custom(format!("invalid time: {:02}:{:02}:{:02}.{:09}", hour, minute, second, nano))
177		})
178	}
179}
180
181impl<'de> Deserialize<'de> for Time {
182	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
183	where
184		D: Deserializer<'de>,
185	{
186		deserializer.deserialize_str(TimeVisitor)
187	}
188}
189
190#[cfg(test)]
191pub mod tests {
192	use std::fmt::Debug;
193
194	use serde_json::{from_str, to_string};
195
196	use super::*;
197	use crate::error::{TemporalKind, TypeError};
198
199	#[test]
200	fn test_time_display_standard_format() {
201		let time = Time::new(14, 30, 45, 123456789).unwrap();
202		assert_eq!(format!("{}", time), "14:30:45.123456789");
203
204		let time = Time::new(0, 0, 0, 0).unwrap();
205		assert_eq!(format!("{}", time), "00:00:00.000000000");
206
207		let time = Time::new(23, 59, 59, 999999999).unwrap();
208		assert_eq!(format!("{}", time), "23:59:59.999999999");
209	}
210
211	#[test]
212	fn test_time_display_millisecond_precision() {
213		// Test various millisecond values
214		let time = Time::new(14, 30, 45, 123000000).unwrap();
215		assert_eq!(format!("{}", time), "14:30:45.123000000");
216
217		let time = Time::new(14, 30, 45, 001000000).unwrap();
218		assert_eq!(format!("{}", time), "14:30:45.001000000");
219
220		let time = Time::new(14, 30, 45, 999000000).unwrap();
221		assert_eq!(format!("{}", time), "14:30:45.999000000");
222	}
223
224	#[test]
225	fn test_time_display_microsecond_precision() {
226		// Test various microsecond values
227		let time = Time::new(14, 30, 45, 123456000).unwrap();
228		assert_eq!(format!("{}", time), "14:30:45.123456000");
229
230		let time = Time::new(14, 30, 45, 000001000).unwrap();
231		assert_eq!(format!("{}", time), "14:30:45.000001000");
232
233		let time = Time::new(14, 30, 45, 999999000).unwrap();
234		assert_eq!(format!("{}", time), "14:30:45.999999000");
235	}
236
237	#[test]
238	fn test_time_display_nanosecond_precision() {
239		// Test various nanosecond values
240		let time = Time::new(14, 30, 45, 123456789).unwrap();
241		assert_eq!(format!("{}", time), "14:30:45.123456789");
242
243		let time = Time::new(14, 30, 45, 000000001).unwrap();
244		assert_eq!(format!("{}", time), "14:30:45.000000001");
245
246		let time = Time::new(14, 30, 45, 999999999).unwrap();
247		assert_eq!(format!("{}", time), "14:30:45.999999999");
248	}
249
250	#[test]
251	fn test_time_display_zero_fractional_seconds() {
252		let time = Time::new(14, 30, 45, 0).unwrap();
253		assert_eq!(format!("{}", time), "14:30:45.000000000");
254
255		let time = Time::new(0, 0, 0, 0).unwrap();
256		assert_eq!(format!("{}", time), "00:00:00.000000000");
257	}
258
259	#[test]
260	fn test_time_display_edge_times() {
261		// Midnight
262		let time = Time::new(0, 0, 0, 0).unwrap();
263		assert_eq!(format!("{}", time), "00:00:00.000000000");
264
265		// Almost midnight next day
266		let time = Time::new(23, 59, 59, 999999999).unwrap();
267		assert_eq!(format!("{}", time), "23:59:59.999999999");
268
269		// Noon
270		let time = Time::new(12, 0, 0, 0).unwrap();
271		assert_eq!(format!("{}", time), "12:00:00.000000000");
272
273		// One second before midnight
274		let time = Time::new(23, 59, 58, 999999999).unwrap();
275		assert_eq!(format!("{}", time), "23:59:58.999999999");
276
277		// One second after midnight
278		let time = Time::new(0, 0, 1, 0).unwrap();
279		assert_eq!(format!("{}", time), "00:00:01.000000000");
280	}
281
282	#[test]
283	fn test_time_display_special_times() {
284		// Test midnight and noon constructors
285		let midnight = Time::midnight();
286		assert_eq!(format!("{}", midnight), "00:00:00.000000000");
287
288		let noon = Time::noon();
289		assert_eq!(format!("{}", noon), "12:00:00.000000000");
290
291		// Test default
292		let default = Time::default();
293		assert_eq!(format!("{}", default), "00:00:00.000000000");
294	}
295
296	#[test]
297	fn test_time_display_all_hours() {
298		for hour in 0..24 {
299			let time = Time::new(hour, 30, 45, 123456789).unwrap();
300			let expected = format!("{:02}:30:45.123456789", hour);
301			assert_eq!(format!("{}", time), expected);
302		}
303	}
304
305	#[test]
306	fn test_time_display_all_minutes() {
307		for minute in 0..60 {
308			let time = Time::new(14, minute, 45, 123456789).unwrap();
309			let expected = format!("14:{:02}:45.123456789", minute);
310			assert_eq!(format!("{}", time), expected);
311		}
312	}
313
314	#[test]
315	fn test_time_display_all_seconds() {
316		for second in 0..60 {
317			let time = Time::new(14, 30, second, 123456789).unwrap();
318			let expected = format!("14:30:{:02}.123456789", second);
319			assert_eq!(format!("{}", time), expected);
320		}
321	}
322
323	#[test]
324	fn test_time_display_from_hms() {
325		let time = Time::from_hms(14, 30, 45).unwrap();
326		assert_eq!(format!("{}", time), "14:30:45.000000000");
327
328		let time = Time::from_hms(0, 0, 0).unwrap();
329		assert_eq!(format!("{}", time), "00:00:00.000000000");
330
331		let time = Time::from_hms(23, 59, 59).unwrap();
332		assert_eq!(format!("{}", time), "23:59:59.000000000");
333	}
334
335	#[test]
336	fn test_time_display_from_hms_nano() {
337		let time = Time::from_hms_nano(14, 30, 45, 123456789).unwrap();
338		assert_eq!(format!("{}", time), "14:30:45.123456789");
339
340		let time = Time::from_hms_nano(0, 0, 0, 0).unwrap();
341		assert_eq!(format!("{}", time), "00:00:00.000000000");
342
343		let time = Time::from_hms_nano(23, 59, 59, 999999999).unwrap();
344		assert_eq!(format!("{}", time), "23:59:59.999999999");
345	}
346
347	#[test]
348	fn test_time_display_from_nanos_since_midnight() {
349		// Test midnight
350		let time = Time::from_nanos_since_midnight(0).unwrap();
351		assert_eq!(format!("{}", time), "00:00:00.000000000");
352
353		// Test 1 second
354		let time = Time::from_nanos_since_midnight(1_000_000_000).unwrap();
355		assert_eq!(format!("{}", time), "00:00:01.000000000");
356
357		// Test 1 minute
358		let time = Time::from_nanos_since_midnight(60_000_000_000).unwrap();
359		assert_eq!(format!("{}", time), "00:01:00.000000000");
360
361		// Test 1 hour
362		let time = Time::from_nanos_since_midnight(3_600_000_000_000).unwrap();
363		assert_eq!(format!("{}", time), "01:00:00.000000000");
364
365		// Test complex time with nanoseconds
366		let nanos = 14 * 3600 * 1_000_000_000 + 30 * 60 * 1_000_000_000 + 45 * 1_000_000_000 + 123456789;
367		let time = Time::from_nanos_since_midnight(nanos).unwrap();
368		assert_eq!(format!("{}", time), "14:30:45.123456789");
369	}
370
371	#[test]
372	fn test_time_display_boundary_values() {
373		// Test the very last nanosecond of the day
374		let nanos = 24 * 3600 * 1_000_000_000 - 1;
375		let time = Time::from_nanos_since_midnight(nanos).unwrap();
376		assert_eq!(format!("{}", time), "23:59:59.999999999");
377
378		// Test the very first nanosecond of the day
379		let time = Time::from_nanos_since_midnight(1).unwrap();
380		assert_eq!(format!("{}", time), "00:00:00.000000001");
381	}
382
383	#[test]
384	fn test_time_display_precision_patterns() {
385		// Test different precision patterns
386		let time = Time::new(14, 30, 45, 100000000).unwrap(); // 0.1 seconds
387		assert_eq!(format!("{}", time), "14:30:45.100000000");
388
389		let time = Time::new(14, 30, 45, 010000000).unwrap(); // 0.01 seconds
390		assert_eq!(format!("{}", time), "14:30:45.010000000");
391
392		let time = Time::new(14, 30, 45, 001000000).unwrap(); // 0.001 seconds
393		assert_eq!(format!("{}", time), "14:30:45.001000000");
394
395		let time = Time::new(14, 30, 45, 000100000).unwrap(); // 0.0001 seconds
396		assert_eq!(format!("{}", time), "14:30:45.000100000");
397
398		let time = Time::new(14, 30, 45, 000010000).unwrap(); // 0.00001 seconds
399		assert_eq!(format!("{}", time), "14:30:45.000010000");
400
401		let time = Time::new(14, 30, 45, 000001000).unwrap(); // 0.000001 seconds
402		assert_eq!(format!("{}", time), "14:30:45.000001000");
403
404		let time = Time::new(14, 30, 45, 000000100).unwrap(); // 0.0000001 seconds
405		assert_eq!(format!("{}", time), "14:30:45.000000100");
406
407		let time = Time::new(14, 30, 45, 000000010).unwrap(); // 0.00000001 seconds
408		assert_eq!(format!("{}", time), "14:30:45.000000010");
409
410		let time = Time::new(14, 30, 45, 000000001).unwrap(); // 0.000000001 seconds
411		assert_eq!(format!("{}", time), "14:30:45.000000001");
412	}
413
414	#[test]
415	fn test_invalid_times() {
416		assert!(Time::new(24, 0, 0, 0).is_none()); // Invalid hour
417		assert!(Time::new(0, 60, 0, 0).is_none()); // Invalid minute
418		assert!(Time::new(0, 0, 60, 0).is_none()); // Invalid second
419		assert!(Time::new(0, 0, 0, 1_000_000_000).is_none()); // Invalid nanosecond
420	}
421
422	#[test]
423	fn test_time_roundtrip() {
424		let test_times = [(0, 0, 0, 0), (12, 30, 45, 123456789), (23, 59, 59, 999999999)];
425
426		for (h, m, s, n) in test_times {
427			let time = Time::new(h, m, s, n).unwrap();
428			let nanos = time.to_nanos_since_midnight();
429			let recovered = Time::from_nanos_since_midnight(nanos).unwrap();
430
431			assert_eq!(time.hour(), recovered.hour());
432			assert_eq!(time.minute(), recovered.minute());
433			assert_eq!(time.second(), recovered.second());
434			assert_eq!(time.nanosecond(), recovered.nanosecond());
435		}
436	}
437
438	#[test]
439	fn test_serde_roundtrip() {
440		let time = Time::new(14, 30, 45, 123456789).unwrap();
441		let json = to_string(&time).unwrap();
442		assert_eq!(json, "\"14:30:45.123456789\"");
443
444		let recovered: Time = from_str(&json).unwrap();
445		assert_eq!(time, recovered);
446	}
447
448	fn assert_time_overflow<T: Debug>(result: Result<T, Box<TypeError>>) {
449		let err = result.expect_err("expected TimeOverflow error");
450		match *err {
451			TypeError::Temporal {
452				kind: TemporalKind::TimeOverflow {
453					..
454				},
455				..
456			} => {}
457			other => panic!("expected TimeOverflow, got: {:?}", other),
458		}
459	}
460
461	#[test]
462	fn test_from_hms_invalid_hour() {
463		assert_time_overflow(Time::from_hms(24, 0, 0));
464	}
465
466	#[test]
467	fn test_from_hms_invalid_minute() {
468		assert_time_overflow(Time::from_hms(0, 60, 0));
469	}
470
471	#[test]
472	fn test_from_hms_invalid_second() {
473		assert_time_overflow(Time::from_hms(0, 0, 60));
474	}
475
476	#[test]
477	fn test_from_hms_nano_invalid_nano() {
478		assert_time_overflow(Time::from_hms_nano(0, 0, 0, 1_000_000_000));
479	}
480}