Skip to main content

reifydb_type/value/
duration.rs

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