Skip to main content

reifydb_type/value/
duration.rs

1// SPDX-License-Identifier: MIT
2// Copyright (c) 2025 ReifyDB
3
4use std::{
5	cmp,
6	fmt::{self, Display, Formatter},
7	ops,
8};
9
10use serde::{Deserialize, Serialize};
11
12/// A duration value representing a duration between two points in time.
13#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
14pub struct Duration {
15	months: i32, // Store years*12 + months
16	days: i32,   // Separate days
17	nanos: i64,  // All time components as nanoseconds
18}
19
20impl Default for Duration {
21	fn default() -> Self {
22		Self::zero()
23	}
24}
25
26impl Duration {
27	pub fn new(months: i32, days: i32, nanos: i64) -> Self {
28		Self {
29			months,
30			days,
31			nanos,
32		}
33	}
34
35	pub fn from_seconds(seconds: i64) -> Self {
36		Self {
37			months: 0,
38			days: 0,
39			nanos: seconds * 1_000_000_000,
40		}
41	}
42
43	pub fn from_milliseconds(milliseconds: i64) -> Self {
44		Self {
45			months: 0,
46			days: 0,
47			nanos: milliseconds * 1_000_000,
48		}
49	}
50
51	pub fn from_microseconds(microseconds: i64) -> Self {
52		Self {
53			months: 0,
54			days: 0,
55			nanos: microseconds * 1_000,
56		}
57	}
58
59	pub fn from_nanoseconds(nanoseconds: i64) -> Self {
60		Self {
61			months: 0,
62			days: 0,
63			nanos: nanoseconds,
64		}
65	}
66
67	pub fn from_minutes(minutes: i64) -> Self {
68		Self {
69			months: 0,
70			days: 0,
71			nanos: minutes * 60 * 1_000_000_000,
72		}
73	}
74
75	pub fn from_hours(hours: i64) -> Self {
76		Self {
77			months: 0,
78			days: 0,
79			nanos: hours * 60 * 60 * 1_000_000_000,
80		}
81	}
82
83	pub fn from_days(days: i64) -> Self {
84		Self {
85			months: 0,
86			days: days as i32,
87			nanos: 0,
88		}
89	}
90
91	pub fn from_weeks(weeks: i64) -> Self {
92		Self {
93			months: 0,
94			days: (weeks * 7) as i32,
95			nanos: 0,
96		}
97	}
98
99	pub fn from_months(months: i64) -> Self {
100		Self {
101			months: months as i32,
102			days: 0,
103			nanos: 0,
104		}
105	}
106
107	pub fn from_years(years: i64) -> Self {
108		Self {
109			months: (years * 12) as i32,
110			days: 0,
111			nanos: 0,
112		}
113	}
114
115	pub fn zero() -> Self {
116		Self {
117			months: 0,
118			days: 0,
119			nanos: 0,
120		}
121	}
122
123	pub fn seconds(&self) -> i64 {
124		self.nanos / 1_000_000_000
125	}
126
127	pub fn milliseconds(&self) -> i64 {
128		self.nanos / 1_000_000
129	}
130
131	pub fn microseconds(&self) -> i64 {
132		self.nanos / 1_000
133	}
134
135	pub fn nanoseconds(&self) -> i64 {
136		self.nanos
137	}
138
139	pub fn get_months(&self) -> i32 {
140		self.months
141	}
142
143	pub fn get_days(&self) -> i32 {
144		self.days
145	}
146
147	pub fn get_nanos(&self) -> i64 {
148		self.nanos
149	}
150
151	pub fn is_positive(&self) -> bool {
152		self.months > 0 || self.days > 0 || self.nanos > 0
153	}
154
155	pub fn is_negative(&self) -> bool {
156		self.months < 0 || self.days < 0 || self.nanos < 0
157	}
158
159	pub fn abs(&self) -> Self {
160		Self {
161			months: self.months.abs(),
162			days: self.days.abs(),
163			nanos: self.nanos.abs(),
164		}
165	}
166
167	pub fn negate(&self) -> Self {
168		Self {
169			months: -self.months,
170			days: -self.days,
171			nanos: -self.nanos,
172		}
173	}
174}
175
176impl PartialOrd for Duration {
177	fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
178		Some(self.cmp(other))
179	}
180}
181
182impl Ord for Duration {
183	fn cmp(&self, other: &Self) -> cmp::Ordering {
184		// Compare months first
185		match self.months.cmp(&other.months) {
186			cmp::Ordering::Equal => {
187				// Then days
188				match self.days.cmp(&other.days) {
189					cmp::Ordering::Equal => {
190						// Finally nanos
191						self.nanos.cmp(&other.nanos)
192					}
193					other_order => other_order,
194				}
195			}
196			other_order => other_order,
197		}
198	}
199}
200
201impl ops::Add for Duration {
202	type Output = Self;
203	fn add(self, rhs: Self) -> Self {
204		Self {
205			months: self.months + rhs.months,
206			days: self.days + rhs.days,
207			nanos: self.nanos + rhs.nanos,
208		}
209	}
210}
211
212impl ops::Sub for Duration {
213	type Output = Self;
214	fn sub(self, rhs: Self) -> Self {
215		Self {
216			months: self.months - rhs.months,
217			days: self.days - rhs.days,
218			nanos: self.nanos - rhs.nanos,
219		}
220	}
221}
222
223impl ops::Mul<i64> for Duration {
224	type Output = Self;
225	fn mul(self, rhs: i64) -> Self {
226		Self {
227			months: self.months * rhs as i32,
228			days: self.days * rhs as i32,
229			nanos: self.nanos * rhs,
230		}
231	}
232}
233
234impl Display for Duration {
235	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
236		// ISO 8601 duration format: P[n]Y[n]M[n]DT[n]H[n]M[n.n]S
237		if self.months == 0 && self.days == 0 && self.nanos == 0 {
238			return write!(f, "PT0S");
239		}
240
241		write!(f, "P")?;
242
243		// Extract years and months
244		let years = self.months / 12;
245		let months = self.months % 12;
246
247		if years != 0 {
248			write!(f, "{}Y", years)?;
249		}
250
251		if months != 0 {
252			write!(f, "{}M", months)?;
253		}
254
255		// Time components from nanos with normalization
256		let total_seconds = self.nanos / 1_000_000_000;
257		let remaining_nanos = self.nanos % 1_000_000_000;
258
259		// Normalize to days if hours >= 24
260		let extra_days = total_seconds / 86400; // 24 * 60 * 60
261		let remaining_seconds = total_seconds % 86400;
262
263		let display_days = self.days + extra_days as i32;
264		let hours = remaining_seconds / 3600;
265		let minutes = (remaining_seconds % 3600) / 60;
266		let seconds = remaining_seconds % 60;
267
268		if display_days != 0 {
269			write!(f, "{}D", display_days)?;
270		}
271
272		if hours != 0 || minutes != 0 || seconds != 0 || remaining_nanos != 0 {
273			write!(f, "T")?;
274
275			if hours != 0 {
276				write!(f, "{}H", hours)?;
277			}
278
279			if minutes != 0 {
280				write!(f, "{}M", minutes)?;
281			}
282
283			if seconds != 0 || remaining_nanos != 0 {
284				if remaining_nanos != 0 {
285					// Format fractional seconds with
286					// trailing zeros removed
287					let fractional = remaining_nanos as f64 / 1_000_000_000.0;
288					let total_seconds_f = seconds as f64 + fractional;
289					// Remove trailing zeros from fractional
290					// part
291					let formatted_str = format!("{:.9}", total_seconds_f);
292					let formatted = formatted_str.trim_end_matches('0').trim_end_matches('.');
293					write!(f, "{}S", formatted)?;
294				} else {
295					write!(f, "{}S", seconds)?;
296				}
297			}
298		}
299
300		Ok(())
301	}
302}
303
304#[cfg(test)]
305pub mod tests {
306	use super::*;
307
308	#[test]
309	fn test_duration_display_zero() {
310		let duration = Duration::zero();
311		assert_eq!(format!("{}", duration), "PT0S");
312
313		let duration = Duration::from_seconds(0);
314		assert_eq!(format!("{}", duration), "PT0S");
315
316		let duration = Duration::from_nanoseconds(0);
317		assert_eq!(format!("{}", duration), "PT0S");
318
319		let duration = Duration::default();
320		assert_eq!(format!("{}", duration), "PT0S");
321	}
322
323	#[test]
324	fn test_duration_display_seconds_only() {
325		let duration = Duration::from_seconds(1);
326		assert_eq!(format!("{}", duration), "PT1S");
327
328		let duration = Duration::from_seconds(30);
329		assert_eq!(format!("{}", duration), "PT30S");
330
331		let duration = Duration::from_seconds(59);
332		assert_eq!(format!("{}", duration), "PT59S");
333	}
334
335	#[test]
336	fn test_duration_display_minutes_only() {
337		let duration = Duration::from_minutes(1);
338		assert_eq!(format!("{}", duration), "PT1M");
339
340		let duration = Duration::from_minutes(30);
341		assert_eq!(format!("{}", duration), "PT30M");
342
343		let duration = Duration::from_minutes(59);
344		assert_eq!(format!("{}", duration), "PT59M");
345	}
346
347	#[test]
348	fn test_duration_display_hours_only() {
349		let duration = Duration::from_hours(1);
350		assert_eq!(format!("{}", duration), "PT1H");
351
352		let duration = Duration::from_hours(12);
353		assert_eq!(format!("{}", duration), "PT12H");
354
355		let duration = Duration::from_hours(23);
356		assert_eq!(format!("{}", duration), "PT23H");
357	}
358
359	#[test]
360	fn test_duration_display_days_only() {
361		let duration = Duration::from_days(1);
362		assert_eq!(format!("{}", duration), "P1D");
363
364		let duration = Duration::from_days(7);
365		assert_eq!(format!("{}", duration), "P7D");
366
367		let duration = Duration::from_days(365);
368		assert_eq!(format!("{}", duration), "P365D");
369	}
370
371	#[test]
372	fn test_duration_display_weeks_only() {
373		let duration = Duration::from_weeks(1);
374		assert_eq!(format!("{}", duration), "P7D");
375
376		let duration = Duration::from_weeks(2);
377		assert_eq!(format!("{}", duration), "P14D");
378
379		let duration = Duration::from_weeks(52);
380		assert_eq!(format!("{}", duration), "P364D");
381	}
382
383	#[test]
384	fn test_duration_display_combined_time() {
385		// Hours and minutes
386		let duration = Duration::new(0, 0, (1 * 60 * 60 + 30 * 60) * 1_000_000_000);
387		assert_eq!(format!("{}", duration), "PT1H30M");
388
389		// Minutes and seconds
390		let duration = Duration::new(0, 0, (5 * 60 + 45) * 1_000_000_000);
391		assert_eq!(format!("{}", duration), "PT5M45S");
392
393		// Hours, minutes, and seconds
394		let duration = Duration::new(0, 0, (2 * 60 * 60 + 30 * 60 + 45) * 1_000_000_000);
395		assert_eq!(format!("{}", duration), "PT2H30M45S");
396	}
397
398	#[test]
399	fn test_duration_display_combined_date_time() {
400		// Days and hours
401		let duration = Duration::new(0, 1, 2 * 60 * 60 * 1_000_000_000);
402		assert_eq!(format!("{}", duration), "P1DT2H");
403
404		// Days and minutes
405		let duration = Duration::new(0, 1, 30 * 60 * 1_000_000_000);
406		assert_eq!(format!("{}", duration), "P1DT30M");
407
408		// Days, hours, and minutes
409		let duration = Duration::new(0, 1, (2 * 60 * 60 + 30 * 60) * 1_000_000_000);
410		assert_eq!(format!("{}", duration), "P1DT2H30M");
411
412		// Days, hours, minutes, and seconds
413		let duration = Duration::new(0, 1, (2 * 60 * 60 + 30 * 60 + 45) * 1_000_000_000);
414		assert_eq!(format!("{}", duration), "P1DT2H30M45S");
415	}
416
417	#[test]
418	fn test_duration_display_milliseconds() {
419		let duration = Duration::from_milliseconds(123);
420		assert_eq!(format!("{}", duration), "PT0.123S");
421
422		let duration = Duration::from_milliseconds(1);
423		assert_eq!(format!("{}", duration), "PT0.001S");
424
425		let duration = Duration::from_milliseconds(999);
426		assert_eq!(format!("{}", duration), "PT0.999S");
427
428		let duration = Duration::from_milliseconds(1500);
429		assert_eq!(format!("{}", duration), "PT1.5S");
430	}
431
432	#[test]
433	fn test_duration_display_microseconds() {
434		let duration = Duration::from_microseconds(123456);
435		assert_eq!(format!("{}", duration), "PT0.123456S");
436
437		let duration = Duration::from_microseconds(1);
438		assert_eq!(format!("{}", duration), "PT0.000001S");
439
440		let duration = Duration::from_microseconds(999999);
441		assert_eq!(format!("{}", duration), "PT0.999999S");
442
443		let duration = Duration::from_microseconds(1500000);
444		assert_eq!(format!("{}", duration), "PT1.5S");
445	}
446
447	#[test]
448	fn test_duration_display_nanoseconds() {
449		let duration = Duration::from_nanoseconds(123456789);
450		assert_eq!(format!("{}", duration), "PT0.123456789S");
451
452		let duration = Duration::from_nanoseconds(1);
453		assert_eq!(format!("{}", duration), "PT0.000000001S");
454
455		let duration = Duration::from_nanoseconds(999999999);
456		assert_eq!(format!("{}", duration), "PT0.999999999S");
457
458		let duration = Duration::from_nanoseconds(1500000000);
459		assert_eq!(format!("{}", duration), "PT1.5S");
460	}
461
462	#[test]
463	fn test_duration_display_fractional_seconds_with_integers() {
464		// Seconds with milliseconds
465		let duration = Duration::new(0, 0, 1 * 1_000_000_000 + 500 * 1_000_000);
466		assert_eq!(format!("{}", duration), "PT1.5S");
467
468		// Seconds with microseconds
469		let duration = Duration::new(0, 0, 2 * 1_000_000_000 + 123456 * 1_000);
470		assert_eq!(format!("{}", duration), "PT2.123456S");
471
472		// Seconds with nanoseconds
473		let duration = Duration::new(0, 0, 3 * 1_000_000_000 + 123456789);
474		assert_eq!(format!("{}", duration), "PT3.123456789S");
475	}
476
477	#[test]
478	fn test_duration_display_comptokenize_durations() {
479		// Comptokenize interval with all components
480		let duration = Duration::new(0, 1, (2 * 60 * 60 + 30 * 60 + 45) * 1_000_000_000 + 123 * 1_000_000);
481		assert_eq!(format!("{}", duration), "P1DT2H30M45.123S");
482
483		// Another comptokenize interval
484		let duration = Duration::new(0, 7, (12 * 60 * 60 + 45 * 60 + 30) * 1_000_000_000 + 456789 * 1_000);
485		assert_eq!(format!("{}", duration), "P7DT12H45M30.456789S");
486	}
487
488	#[test]
489	fn test_duration_display_trailing_zeros_removed() {
490		// Test that trailing zeros are removed from fractional seconds
491		let duration = Duration::from_nanoseconds(100000000); // 0.1 seconds
492		assert_eq!(format!("{}", duration), "PT0.1S");
493
494		let duration = Duration::from_nanoseconds(120000000); // 0.12 seconds
495		assert_eq!(format!("{}", duration), "PT0.12S");
496
497		let duration = Duration::from_nanoseconds(123000000); // 0.123 seconds
498		assert_eq!(format!("{}", duration), "PT0.123S");
499
500		let duration = Duration::from_nanoseconds(123400000); // 0.1234 seconds
501		assert_eq!(format!("{}", duration), "PT0.1234S");
502
503		let duration = Duration::from_nanoseconds(123450000); // 0.12345 seconds
504		assert_eq!(format!("{}", duration), "PT0.12345S");
505
506		let duration = Duration::from_nanoseconds(123456000); // 0.123456 seconds
507		assert_eq!(format!("{}", duration), "PT0.123456S");
508
509		let duration = Duration::from_nanoseconds(123456700); // 0.1234567 seconds
510		assert_eq!(format!("{}", duration), "PT0.1234567S");
511
512		let duration = Duration::from_nanoseconds(123456780); // 0.12345678 seconds
513		assert_eq!(format!("{}", duration), "PT0.12345678S");
514
515		let duration = Duration::from_nanoseconds(123456789); // 0.123456789 seconds
516		assert_eq!(format!("{}", duration), "PT0.123456789S");
517	}
518
519	#[test]
520	fn test_duration_display_negative_durations() {
521		// Test negative intervals
522		let duration = Duration::from_seconds(-30);
523		assert_eq!(format!("{}", duration), "PT-30S");
524
525		let duration = Duration::from_minutes(-5);
526		assert_eq!(format!("{}", duration), "PT-5M");
527
528		let duration = Duration::from_hours(-2);
529		assert_eq!(format!("{}", duration), "PT-2H");
530
531		let duration = Duration::from_days(-1);
532		assert_eq!(format!("{}", duration), "P-1D");
533	}
534
535	#[test]
536	fn test_duration_display_large_values() {
537		// Test large intervals
538		let duration = Duration::from_days(1000);
539		assert_eq!(format!("{}", duration), "P1000D");
540
541		let duration = Duration::from_hours(25);
542		assert_eq!(format!("{}", duration), "P1DT1H");
543
544		let duration = Duration::from_minutes(1500); // 25 hours
545		assert_eq!(format!("{}", duration), "P1DT1H");
546
547		let duration = Duration::from_seconds(90000); // 25 hours
548		assert_eq!(format!("{}", duration), "P1DT1H");
549	}
550
551	#[test]
552	fn test_duration_display_edge_cases() {
553		// Test edge cases with single nanosecond
554		let duration = Duration::from_nanoseconds(1);
555		assert_eq!(format!("{}", duration), "PT0.000000001S");
556
557		// Test maximum nanoseconds in a second
558		let duration = Duration::from_nanoseconds(999999999);
559		assert_eq!(format!("{}", duration), "PT0.999999999S");
560
561		// Test exactly 1 second
562		let duration = Duration::from_nanoseconds(1000000000);
563		assert_eq!(format!("{}", duration), "PT1S");
564
565		// Test exactly 1 minute
566		let duration = Duration::from_nanoseconds(60 * 1000000000);
567		assert_eq!(format!("{}", duration), "PT1M");
568
569		// Test exactly 1 hour
570		let duration = Duration::from_nanoseconds(3600 * 1000000000);
571		assert_eq!(format!("{}", duration), "PT1H");
572
573		// Test exactly 1 day
574		let duration = Duration::from_nanoseconds(86400 * 1000000000);
575		assert_eq!(format!("{}", duration), "P1D");
576	}
577
578	#[test]
579	fn test_duration_display_precision_boundaries() {
580		// Test precision boundaries
581		let duration = Duration::from_nanoseconds(100); // 0.0000001 seconds
582		assert_eq!(format!("{}", duration), "PT0.0000001S");
583
584		let duration = Duration::from_nanoseconds(10); // 0.00000001 seconds
585		assert_eq!(format!("{}", duration), "PT0.00000001S");
586
587		let duration = Duration::from_nanoseconds(1); // 0.000000001 seconds
588		assert_eq!(format!("{}", duration), "PT0.000000001S");
589	}
590
591	#[test]
592	fn test_duration_display_from_nanos() {
593		// Test the from_nanos method
594		let duration = Duration::from_nanoseconds(123456789);
595		assert_eq!(format!("{}", duration), "PT0.123456789S");
596
597		let duration = Duration::from_nanoseconds(3661000000000); // 1 hour 1 minute 1 second
598		assert_eq!(format!("{}", duration), "PT1H1M1S");
599	}
600
601	#[test]
602	fn test_duration_display_abs_and_negate() {
603		// Test absolute value
604		let duration = Duration::from_seconds(-30);
605		let abs_duration = duration.abs();
606		assert_eq!(format!("{}", abs_duration), "PT30S");
607
608		// Test negation
609		let duration = Duration::from_seconds(30);
610		let neg_duration = duration.negate();
611		assert_eq!(format!("{}", neg_duration), "PT-30S");
612	}
613}