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