Skip to main content

reifydb_type/value/
time.rs

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