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