reifydb_type/value/
duration.rs

1// Copyright (c) reifydb.com 2025
2// This file is licensed under the MIT, see license.md file
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 Display for Duration {
198	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
199		// ISO 8601 duration format: P[n]Y[n]M[n]DT[n]H[n]M[n.n]S
200		if self.months == 0 && self.days == 0 && self.nanos == 0 {
201			return write!(f, "PT0S");
202		}
203
204		write!(f, "P")?;
205
206		// Extract years and months
207		let years = self.months / 12;
208		let months = self.months % 12;
209
210		if years != 0 {
211			write!(f, "{}Y", years)?;
212		}
213
214		if months != 0 {
215			write!(f, "{}M", months)?;
216		}
217
218		// Time components from nanos with normalization
219		let total_seconds = self.nanos / 1_000_000_000;
220		let remaining_nanos = self.nanos % 1_000_000_000;
221
222		// Normalize to days if hours >= 24
223		let extra_days = total_seconds / 86400; // 24 * 60 * 60
224		let remaining_seconds = total_seconds % 86400;
225
226		let display_days = self.days + extra_days as i32;
227		let hours = remaining_seconds / 3600;
228		let minutes = (remaining_seconds % 3600) / 60;
229		let seconds = remaining_seconds % 60;
230
231		if display_days != 0 {
232			write!(f, "{}D", display_days)?;
233		}
234
235		if hours != 0 || minutes != 0 || seconds != 0 || remaining_nanos != 0 {
236			write!(f, "T")?;
237
238			if hours != 0 {
239				write!(f, "{}H", hours)?;
240			}
241
242			if minutes != 0 {
243				write!(f, "{}M", minutes)?;
244			}
245
246			if seconds != 0 || remaining_nanos != 0 {
247				if remaining_nanos != 0 {
248					// Format fractional seconds with
249					// trailing zeros removed
250					let fractional = remaining_nanos as f64 / 1_000_000_000.0;
251					let total_seconds_f = seconds as f64 + fractional;
252					// Remove trailing zeros from fractional
253					// part
254					let formatted_str = format!("{:.9}", total_seconds_f);
255					let formatted = formatted_str.trim_end_matches('0').trim_end_matches('.');
256					write!(f, "{}S", formatted)?;
257				} else {
258					write!(f, "{}S", seconds)?;
259				}
260			}
261		}
262
263		Ok(())
264	}
265}
266
267#[cfg(test)]
268mod tests {
269	use super::*;
270
271	#[test]
272	fn test_duration_display_zero() {
273		let duration = Duration::zero();
274		assert_eq!(format!("{}", duration), "PT0S");
275
276		let duration = Duration::from_seconds(0);
277		assert_eq!(format!("{}", duration), "PT0S");
278
279		let duration = Duration::from_nanoseconds(0);
280		assert_eq!(format!("{}", duration), "PT0S");
281
282		let duration = Duration::default();
283		assert_eq!(format!("{}", duration), "PT0S");
284	}
285
286	#[test]
287	fn test_duration_display_seconds_only() {
288		let duration = Duration::from_seconds(1);
289		assert_eq!(format!("{}", duration), "PT1S");
290
291		let duration = Duration::from_seconds(30);
292		assert_eq!(format!("{}", duration), "PT30S");
293
294		let duration = Duration::from_seconds(59);
295		assert_eq!(format!("{}", duration), "PT59S");
296	}
297
298	#[test]
299	fn test_duration_display_minutes_only() {
300		let duration = Duration::from_minutes(1);
301		assert_eq!(format!("{}", duration), "PT1M");
302
303		let duration = Duration::from_minutes(30);
304		assert_eq!(format!("{}", duration), "PT30M");
305
306		let duration = Duration::from_minutes(59);
307		assert_eq!(format!("{}", duration), "PT59M");
308	}
309
310	#[test]
311	fn test_duration_display_hours_only() {
312		let duration = Duration::from_hours(1);
313		assert_eq!(format!("{}", duration), "PT1H");
314
315		let duration = Duration::from_hours(12);
316		assert_eq!(format!("{}", duration), "PT12H");
317
318		let duration = Duration::from_hours(23);
319		assert_eq!(format!("{}", duration), "PT23H");
320	}
321
322	#[test]
323	fn test_duration_display_days_only() {
324		let duration = Duration::from_days(1);
325		assert_eq!(format!("{}", duration), "P1D");
326
327		let duration = Duration::from_days(7);
328		assert_eq!(format!("{}", duration), "P7D");
329
330		let duration = Duration::from_days(365);
331		assert_eq!(format!("{}", duration), "P365D");
332	}
333
334	#[test]
335	fn test_duration_display_weeks_only() {
336		let duration = Duration::from_weeks(1);
337		assert_eq!(format!("{}", duration), "P7D");
338
339		let duration = Duration::from_weeks(2);
340		assert_eq!(format!("{}", duration), "P14D");
341
342		let duration = Duration::from_weeks(52);
343		assert_eq!(format!("{}", duration), "P364D");
344	}
345
346	#[test]
347	fn test_duration_display_combined_time() {
348		// Hours and minutes
349		let duration = Duration::new(0, 0, (1 * 60 * 60 + 30 * 60) * 1_000_000_000);
350		assert_eq!(format!("{}", duration), "PT1H30M");
351
352		// Minutes and seconds
353		let duration = Duration::new(0, 0, (5 * 60 + 45) * 1_000_000_000);
354		assert_eq!(format!("{}", duration), "PT5M45S");
355
356		// Hours, minutes, and seconds
357		let duration = Duration::new(0, 0, (2 * 60 * 60 + 30 * 60 + 45) * 1_000_000_000);
358		assert_eq!(format!("{}", duration), "PT2H30M45S");
359	}
360
361	#[test]
362	fn test_duration_display_combined_date_time() {
363		// Days and hours
364		let duration = Duration::new(0, 1, 2 * 60 * 60 * 1_000_000_000);
365		assert_eq!(format!("{}", duration), "P1DT2H");
366
367		// Days and minutes
368		let duration = Duration::new(0, 1, 30 * 60 * 1_000_000_000);
369		assert_eq!(format!("{}", duration), "P1DT30M");
370
371		// Days, hours, and minutes
372		let duration = Duration::new(0, 1, (2 * 60 * 60 + 30 * 60) * 1_000_000_000);
373		assert_eq!(format!("{}", duration), "P1DT2H30M");
374
375		// Days, hours, minutes, and seconds
376		let duration = Duration::new(0, 1, (2 * 60 * 60 + 30 * 60 + 45) * 1_000_000_000);
377		assert_eq!(format!("{}", duration), "P1DT2H30M45S");
378	}
379
380	#[test]
381	fn test_duration_display_milliseconds() {
382		let duration = Duration::from_milliseconds(123);
383		assert_eq!(format!("{}", duration), "PT0.123S");
384
385		let duration = Duration::from_milliseconds(1);
386		assert_eq!(format!("{}", duration), "PT0.001S");
387
388		let duration = Duration::from_milliseconds(999);
389		assert_eq!(format!("{}", duration), "PT0.999S");
390
391		let duration = Duration::from_milliseconds(1500);
392		assert_eq!(format!("{}", duration), "PT1.5S");
393	}
394
395	#[test]
396	fn test_duration_display_microseconds() {
397		let duration = Duration::from_microseconds(123456);
398		assert_eq!(format!("{}", duration), "PT0.123456S");
399
400		let duration = Duration::from_microseconds(1);
401		assert_eq!(format!("{}", duration), "PT0.000001S");
402
403		let duration = Duration::from_microseconds(999999);
404		assert_eq!(format!("{}", duration), "PT0.999999S");
405
406		let duration = Duration::from_microseconds(1500000);
407		assert_eq!(format!("{}", duration), "PT1.5S");
408	}
409
410	#[test]
411	fn test_duration_display_nanoseconds() {
412		let duration = Duration::from_nanoseconds(123456789);
413		assert_eq!(format!("{}", duration), "PT0.123456789S");
414
415		let duration = Duration::from_nanoseconds(1);
416		assert_eq!(format!("{}", duration), "PT0.000000001S");
417
418		let duration = Duration::from_nanoseconds(999999999);
419		assert_eq!(format!("{}", duration), "PT0.999999999S");
420
421		let duration = Duration::from_nanoseconds(1500000000);
422		assert_eq!(format!("{}", duration), "PT1.5S");
423	}
424
425	#[test]
426	fn test_duration_display_fractional_seconds_with_integers() {
427		// Seconds with milliseconds
428		let duration = Duration::new(0, 0, 1 * 1_000_000_000 + 500 * 1_000_000);
429		assert_eq!(format!("{}", duration), "PT1.5S");
430
431		// Seconds with microseconds
432		let duration = Duration::new(0, 0, 2 * 1_000_000_000 + 123456 * 1_000);
433		assert_eq!(format!("{}", duration), "PT2.123456S");
434
435		// Seconds with nanoseconds
436		let duration = Duration::new(0, 0, 3 * 1_000_000_000 + 123456789);
437		assert_eq!(format!("{}", duration), "PT3.123456789S");
438	}
439
440	#[test]
441	fn test_duration_display_comptokenize_durations() {
442		// Comptokenize interval with all components
443		let duration = Duration::new(0, 1, (2 * 60 * 60 + 30 * 60 + 45) * 1_000_000_000 + 123 * 1_000_000);
444		assert_eq!(format!("{}", duration), "P1DT2H30M45.123S");
445
446		// Another comptokenize interval
447		let duration = Duration::new(0, 7, (12 * 60 * 60 + 45 * 60 + 30) * 1_000_000_000 + 456789 * 1_000);
448		assert_eq!(format!("{}", duration), "P7DT12H45M30.456789S");
449	}
450
451	#[test]
452	fn test_duration_display_trailing_zeros_removed() {
453		// Test that trailing zeros are removed from fractional seconds
454		let duration = Duration::from_nanoseconds(100000000); // 0.1 seconds
455		assert_eq!(format!("{}", duration), "PT0.1S");
456
457		let duration = Duration::from_nanoseconds(120000000); // 0.12 seconds
458		assert_eq!(format!("{}", duration), "PT0.12S");
459
460		let duration = Duration::from_nanoseconds(123000000); // 0.123 seconds
461		assert_eq!(format!("{}", duration), "PT0.123S");
462
463		let duration = Duration::from_nanoseconds(123400000); // 0.1234 seconds
464		assert_eq!(format!("{}", duration), "PT0.1234S");
465
466		let duration = Duration::from_nanoseconds(123450000); // 0.12345 seconds
467		assert_eq!(format!("{}", duration), "PT0.12345S");
468
469		let duration = Duration::from_nanoseconds(123456000); // 0.123456 seconds
470		assert_eq!(format!("{}", duration), "PT0.123456S");
471
472		let duration = Duration::from_nanoseconds(123456700); // 0.1234567 seconds
473		assert_eq!(format!("{}", duration), "PT0.1234567S");
474
475		let duration = Duration::from_nanoseconds(123456780); // 0.12345678 seconds
476		assert_eq!(format!("{}", duration), "PT0.12345678S");
477
478		let duration = Duration::from_nanoseconds(123456789); // 0.123456789 seconds
479		assert_eq!(format!("{}", duration), "PT0.123456789S");
480	}
481
482	#[test]
483	fn test_duration_display_negative_durations() {
484		// Test negative intervals
485		let duration = Duration::from_seconds(-30);
486		assert_eq!(format!("{}", duration), "PT-30S");
487
488		let duration = Duration::from_minutes(-5);
489		assert_eq!(format!("{}", duration), "PT-5M");
490
491		let duration = Duration::from_hours(-2);
492		assert_eq!(format!("{}", duration), "PT-2H");
493
494		let duration = Duration::from_days(-1);
495		assert_eq!(format!("{}", duration), "P-1D");
496	}
497
498	#[test]
499	fn test_duration_display_large_values() {
500		// Test large intervals
501		let duration = Duration::from_days(1000);
502		assert_eq!(format!("{}", duration), "P1000D");
503
504		let duration = Duration::from_hours(25);
505		assert_eq!(format!("{}", duration), "P1DT1H");
506
507		let duration = Duration::from_minutes(1500); // 25 hours
508		assert_eq!(format!("{}", duration), "P1DT1H");
509
510		let duration = Duration::from_seconds(90000); // 25 hours
511		assert_eq!(format!("{}", duration), "P1DT1H");
512	}
513
514	#[test]
515	fn test_duration_display_edge_cases() {
516		// Test edge cases with single nanosecond
517		let duration = Duration::from_nanoseconds(1);
518		assert_eq!(format!("{}", duration), "PT0.000000001S");
519
520		// Test maximum nanoseconds in a second
521		let duration = Duration::from_nanoseconds(999999999);
522		assert_eq!(format!("{}", duration), "PT0.999999999S");
523
524		// Test exactly 1 second
525		let duration = Duration::from_nanoseconds(1000000000);
526		assert_eq!(format!("{}", duration), "PT1S");
527
528		// Test exactly 1 minute
529		let duration = Duration::from_nanoseconds(60 * 1000000000);
530		assert_eq!(format!("{}", duration), "PT1M");
531
532		// Test exactly 1 hour
533		let duration = Duration::from_nanoseconds(3600 * 1000000000);
534		assert_eq!(format!("{}", duration), "PT1H");
535
536		// Test exactly 1 day
537		let duration = Duration::from_nanoseconds(86400 * 1000000000);
538		assert_eq!(format!("{}", duration), "P1D");
539	}
540
541	#[test]
542	fn test_duration_display_precision_boundaries() {
543		// Test precision boundaries
544		let duration = Duration::from_nanoseconds(100); // 0.0000001 seconds
545		assert_eq!(format!("{}", duration), "PT0.0000001S");
546
547		let duration = Duration::from_nanoseconds(10); // 0.00000001 seconds
548		assert_eq!(format!("{}", duration), "PT0.00000001S");
549
550		let duration = Duration::from_nanoseconds(1); // 0.000000001 seconds
551		assert_eq!(format!("{}", duration), "PT0.000000001S");
552	}
553
554	#[test]
555	fn test_duration_display_from_nanos() {
556		// Test the from_nanos method
557		let duration = Duration::from_nanoseconds(123456789);
558		assert_eq!(format!("{}", duration), "PT0.123456789S");
559
560		let duration = Duration::from_nanoseconds(3661000000000); // 1 hour 1 minute 1 second
561		assert_eq!(format!("{}", duration), "PT1H1M1S");
562	}
563
564	#[test]
565	fn test_duration_display_abs_and_negate() {
566		// Test absolute value
567		let duration = Duration::from_seconds(-30);
568		let abs_duration = duration.abs();
569		assert_eq!(format!("{}", abs_duration), "PT30S");
570
571		// Test negation
572		let duration = Duration::from_seconds(30);
573		let neg_duration = duration.negate();
574		assert_eq!(format!("{}", neg_duration), "PT-30S");
575	}
576}