Skip to main content

reifydb_type/value/
duration.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use std::{
5	cmp,
6	fmt::{self, Display, Formatter, Write},
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	/// Format as ISO 8601 duration string: `P[n]Y[n]M[n]DT[n]H[n]M[n.n]S`
176	pub fn to_iso_string(&self) -> String {
177		if self.months == 0 && self.days == 0 && self.nanos == 0 {
178			return "PT0S".to_string();
179		}
180
181		let mut result = String::from("P");
182
183		let years = self.months / 12;
184		let months = self.months % 12;
185
186		if years != 0 {
187			write!(result, "{}Y", years).unwrap();
188		}
189		if months != 0 {
190			write!(result, "{}M", months).unwrap();
191		}
192
193		let total_seconds = self.nanos / 1_000_000_000;
194		let remaining_nanos = self.nanos % 1_000_000_000;
195
196		let extra_days = total_seconds / 86400;
197		let remaining_seconds = total_seconds % 86400;
198
199		let display_days = self.days + extra_days as i32;
200		let hours = remaining_seconds / 3600;
201		let minutes = (remaining_seconds % 3600) / 60;
202		let seconds = remaining_seconds % 60;
203
204		if display_days != 0 {
205			write!(result, "{}D", display_days).unwrap();
206		}
207
208		if hours != 0 || minutes != 0 || seconds != 0 || remaining_nanos != 0 {
209			result.push('T');
210
211			if hours != 0 {
212				write!(result, "{}H", hours).unwrap();
213			}
214			if minutes != 0 {
215				write!(result, "{}M", minutes).unwrap();
216			}
217			if seconds != 0 || remaining_nanos != 0 {
218				if remaining_nanos != 0 {
219					let fractional = remaining_nanos as f64 / 1_000_000_000.0;
220					let total_seconds_f = seconds as f64 + fractional;
221					let formatted_str = format!("{:.9}", total_seconds_f);
222					let formatted = formatted_str.trim_end_matches('0').trim_end_matches('.');
223					write!(result, "{}S", formatted).unwrap();
224				} else {
225					write!(result, "{}S", seconds).unwrap();
226				}
227			}
228		}
229
230		result
231	}
232}
233
234impl PartialOrd for Duration {
235	fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
236		Some(self.cmp(other))
237	}
238}
239
240impl Ord for Duration {
241	fn cmp(&self, other: &Self) -> cmp::Ordering {
242		// Compare months first
243		match self.months.cmp(&other.months) {
244			cmp::Ordering::Equal => {
245				// Then days
246				match self.days.cmp(&other.days) {
247					cmp::Ordering::Equal => {
248						// Finally nanos
249						self.nanos.cmp(&other.nanos)
250					}
251					other_order => other_order,
252				}
253			}
254			other_order => other_order,
255		}
256	}
257}
258
259impl ops::Add for Duration {
260	type Output = Self;
261	fn add(self, rhs: Self) -> Self {
262		Self {
263			months: self.months + rhs.months,
264			days: self.days + rhs.days,
265			nanos: self.nanos + rhs.nanos,
266		}
267	}
268}
269
270impl ops::Sub for Duration {
271	type Output = Self;
272	fn sub(self, rhs: Self) -> Self {
273		Self {
274			months: self.months - rhs.months,
275			days: self.days - rhs.days,
276			nanos: self.nanos - rhs.nanos,
277		}
278	}
279}
280
281impl ops::Mul<i64> for Duration {
282	type Output = Self;
283	fn mul(self, rhs: i64) -> Self {
284		Self {
285			months: self.months * rhs as i32,
286			days: self.days * rhs as i32,
287			nanos: self.nanos * rhs,
288		}
289	}
290}
291
292impl Display for Duration {
293	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
294		if self.months == 0 && self.days == 0 && self.nanos == 0 {
295			return write!(f, "0s");
296		}
297
298		let years = self.months / 12;
299		let months = self.months % 12;
300
301		let total_seconds = self.nanos / 1_000_000_000;
302		let remaining_nanos = self.nanos % 1_000_000_000;
303
304		let extra_days = total_seconds / 86400;
305		let remaining_seconds = total_seconds % 86400;
306
307		let display_days = self.days + extra_days as i32;
308		let hours = remaining_seconds / 3600;
309		let minutes = (remaining_seconds % 3600) / 60;
310		let seconds = remaining_seconds % 60;
311
312		let abs_remaining = remaining_nanos.abs();
313		let ms = abs_remaining / 1_000_000;
314		let us = (abs_remaining % 1_000_000) / 1_000;
315		let ns = abs_remaining % 1_000;
316
317		if years != 0 {
318			write!(f, "{}y", years)?;
319		}
320		if months != 0 {
321			write!(f, "{}mo", months)?;
322		}
323		if display_days != 0 {
324			write!(f, "{}d", display_days)?;
325		}
326		if hours != 0 {
327			write!(f, "{}h", hours)?;
328		}
329		if minutes != 0 {
330			write!(f, "{}m", minutes)?;
331		}
332		if seconds != 0 {
333			write!(f, "{}s", seconds)?;
334		}
335
336		if ms != 0 || us != 0 || ns != 0 {
337			if remaining_nanos < 0
338				&& seconds == 0 && hours == 0
339				&& minutes == 0 && display_days == 0
340				&& years == 0 && months == 0
341			{
342				write!(f, "-")?;
343			}
344			if ms != 0 {
345				write!(f, "{}ms", ms)?;
346			}
347			if us != 0 {
348				write!(f, "{}us", us)?;
349			}
350			if ns != 0 {
351				write!(f, "{}ns", ns)?;
352			}
353		}
354
355		Ok(())
356	}
357}
358
359#[cfg(test)]
360pub mod tests {
361	use super::*;
362
363	// ---- to_iso_string tests (ISO 8601 format) ----
364
365	#[test]
366	fn test_duration_iso_string_zero() {
367		assert_eq!(Duration::zero().to_iso_string(), "PT0S");
368		assert_eq!(Duration::from_seconds(0).to_iso_string(), "PT0S");
369		assert_eq!(Duration::from_nanoseconds(0).to_iso_string(), "PT0S");
370		assert_eq!(Duration::default().to_iso_string(), "PT0S");
371	}
372
373	#[test]
374	fn test_duration_iso_string_seconds() {
375		assert_eq!(Duration::from_seconds(1).to_iso_string(), "PT1S");
376		assert_eq!(Duration::from_seconds(30).to_iso_string(), "PT30S");
377		assert_eq!(Duration::from_seconds(59).to_iso_string(), "PT59S");
378	}
379
380	#[test]
381	fn test_duration_iso_string_minutes() {
382		assert_eq!(Duration::from_minutes(1).to_iso_string(), "PT1M");
383		assert_eq!(Duration::from_minutes(30).to_iso_string(), "PT30M");
384		assert_eq!(Duration::from_minutes(59).to_iso_string(), "PT59M");
385	}
386
387	#[test]
388	fn test_duration_iso_string_hours() {
389		assert_eq!(Duration::from_hours(1).to_iso_string(), "PT1H");
390		assert_eq!(Duration::from_hours(12).to_iso_string(), "PT12H");
391		assert_eq!(Duration::from_hours(23).to_iso_string(), "PT23H");
392	}
393
394	#[test]
395	fn test_duration_iso_string_days() {
396		assert_eq!(Duration::from_days(1).to_iso_string(), "P1D");
397		assert_eq!(Duration::from_days(7).to_iso_string(), "P7D");
398		assert_eq!(Duration::from_days(365).to_iso_string(), "P365D");
399	}
400
401	#[test]
402	fn test_duration_iso_string_weeks() {
403		assert_eq!(Duration::from_weeks(1).to_iso_string(), "P7D");
404		assert_eq!(Duration::from_weeks(2).to_iso_string(), "P14D");
405		assert_eq!(Duration::from_weeks(52).to_iso_string(), "P364D");
406	}
407
408	#[test]
409	fn test_duration_iso_string_combined_time() {
410		let d = Duration::new(0, 0, (1 * 60 * 60 + 30 * 60) * 1_000_000_000);
411		assert_eq!(d.to_iso_string(), "PT1H30M");
412
413		let d = Duration::new(0, 0, (5 * 60 + 45) * 1_000_000_000);
414		assert_eq!(d.to_iso_string(), "PT5M45S");
415
416		let d = Duration::new(0, 0, (2 * 60 * 60 + 30 * 60 + 45) * 1_000_000_000);
417		assert_eq!(d.to_iso_string(), "PT2H30M45S");
418	}
419
420	#[test]
421	fn test_duration_iso_string_combined_date_time() {
422		assert_eq!(Duration::new(0, 1, 2 * 60 * 60 * 1_000_000_000).to_iso_string(), "P1DT2H");
423		assert_eq!(Duration::new(0, 1, 30 * 60 * 1_000_000_000).to_iso_string(), "P1DT30M");
424		assert_eq!(Duration::new(0, 1, (2 * 60 * 60 + 30 * 60) * 1_000_000_000).to_iso_string(), "P1DT2H30M");
425		assert_eq!(
426			Duration::new(0, 1, (2 * 60 * 60 + 30 * 60 + 45) * 1_000_000_000).to_iso_string(),
427			"P1DT2H30M45S"
428		);
429	}
430
431	#[test]
432	fn test_duration_iso_string_milliseconds() {
433		assert_eq!(Duration::from_milliseconds(123).to_iso_string(), "PT0.123S");
434		assert_eq!(Duration::from_milliseconds(1).to_iso_string(), "PT0.001S");
435		assert_eq!(Duration::from_milliseconds(999).to_iso_string(), "PT0.999S");
436		assert_eq!(Duration::from_milliseconds(1500).to_iso_string(), "PT1.5S");
437	}
438
439	#[test]
440	fn test_duration_iso_string_microseconds() {
441		assert_eq!(Duration::from_microseconds(123456).to_iso_string(), "PT0.123456S");
442		assert_eq!(Duration::from_microseconds(1).to_iso_string(), "PT0.000001S");
443		assert_eq!(Duration::from_microseconds(999999).to_iso_string(), "PT0.999999S");
444		assert_eq!(Duration::from_microseconds(1500000).to_iso_string(), "PT1.5S");
445	}
446
447	#[test]
448	fn test_duration_iso_string_nanoseconds() {
449		assert_eq!(Duration::from_nanoseconds(123456789).to_iso_string(), "PT0.123456789S");
450		assert_eq!(Duration::from_nanoseconds(1).to_iso_string(), "PT0.000000001S");
451		assert_eq!(Duration::from_nanoseconds(999999999).to_iso_string(), "PT0.999999999S");
452		assert_eq!(Duration::from_nanoseconds(1500000000).to_iso_string(), "PT1.5S");
453	}
454
455	#[test]
456	fn test_duration_iso_string_fractional_seconds() {
457		let d = Duration::new(0, 0, 1 * 1_000_000_000 + 500 * 1_000_000);
458		assert_eq!(d.to_iso_string(), "PT1.5S");
459
460		let d = Duration::new(0, 0, 2 * 1_000_000_000 + 123456 * 1_000);
461		assert_eq!(d.to_iso_string(), "PT2.123456S");
462
463		let d = Duration::new(0, 0, 3 * 1_000_000_000 + 123456789);
464		assert_eq!(d.to_iso_string(), "PT3.123456789S");
465	}
466
467	#[test]
468	fn test_duration_iso_string_complex() {
469		let d = Duration::new(0, 1, (2 * 60 * 60 + 30 * 60 + 45) * 1_000_000_000 + 123 * 1_000_000);
470		assert_eq!(d.to_iso_string(), "P1DT2H30M45.123S");
471
472		let d = Duration::new(0, 7, (12 * 60 * 60 + 45 * 60 + 30) * 1_000_000_000 + 456789 * 1_000);
473		assert_eq!(d.to_iso_string(), "P7DT12H45M30.456789S");
474	}
475
476	#[test]
477	fn test_duration_iso_string_trailing_zeros() {
478		assert_eq!(Duration::from_nanoseconds(100000000).to_iso_string(), "PT0.1S");
479		assert_eq!(Duration::from_nanoseconds(120000000).to_iso_string(), "PT0.12S");
480		assert_eq!(Duration::from_nanoseconds(123000000).to_iso_string(), "PT0.123S");
481		assert_eq!(Duration::from_nanoseconds(123400000).to_iso_string(), "PT0.1234S");
482		assert_eq!(Duration::from_nanoseconds(123450000).to_iso_string(), "PT0.12345S");
483		assert_eq!(Duration::from_nanoseconds(123456000).to_iso_string(), "PT0.123456S");
484		assert_eq!(Duration::from_nanoseconds(123456700).to_iso_string(), "PT0.1234567S");
485		assert_eq!(Duration::from_nanoseconds(123456780).to_iso_string(), "PT0.12345678S");
486		assert_eq!(Duration::from_nanoseconds(123456789).to_iso_string(), "PT0.123456789S");
487	}
488
489	#[test]
490	fn test_duration_iso_string_negative() {
491		assert_eq!(Duration::from_seconds(-30).to_iso_string(), "PT-30S");
492		assert_eq!(Duration::from_minutes(-5).to_iso_string(), "PT-5M");
493		assert_eq!(Duration::from_hours(-2).to_iso_string(), "PT-2H");
494		assert_eq!(Duration::from_days(-1).to_iso_string(), "P-1D");
495	}
496
497	#[test]
498	fn test_duration_iso_string_large() {
499		assert_eq!(Duration::from_days(1000).to_iso_string(), "P1000D");
500		assert_eq!(Duration::from_hours(25).to_iso_string(), "P1DT1H");
501		assert_eq!(Duration::from_minutes(1500).to_iso_string(), "P1DT1H");
502		assert_eq!(Duration::from_seconds(90000).to_iso_string(), "P1DT1H");
503	}
504
505	#[test]
506	fn test_duration_iso_string_edge_cases() {
507		assert_eq!(Duration::from_nanoseconds(1).to_iso_string(), "PT0.000000001S");
508		assert_eq!(Duration::from_nanoseconds(999999999).to_iso_string(), "PT0.999999999S");
509		assert_eq!(Duration::from_nanoseconds(1000000000).to_iso_string(), "PT1S");
510		assert_eq!(Duration::from_nanoseconds(60 * 1000000000).to_iso_string(), "PT1M");
511		assert_eq!(Duration::from_nanoseconds(3600 * 1000000000).to_iso_string(), "PT1H");
512		assert_eq!(Duration::from_nanoseconds(86400 * 1000000000).to_iso_string(), "P1D");
513	}
514
515	#[test]
516	fn test_duration_iso_string_precision() {
517		assert_eq!(Duration::from_nanoseconds(100).to_iso_string(), "PT0.0000001S");
518		assert_eq!(Duration::from_nanoseconds(10).to_iso_string(), "PT0.00000001S");
519		assert_eq!(Duration::from_nanoseconds(1).to_iso_string(), "PT0.000000001S");
520	}
521
522	// ---- Display tests (human-readable format) ----
523
524	#[test]
525	fn test_duration_display_zero() {
526		assert_eq!(format!("{}", Duration::zero()), "0s");
527		assert_eq!(format!("{}", Duration::from_seconds(0)), "0s");
528		assert_eq!(format!("{}", Duration::from_nanoseconds(0)), "0s");
529		assert_eq!(format!("{}", Duration::default()), "0s");
530	}
531
532	#[test]
533	fn test_duration_display_seconds_only() {
534		assert_eq!(format!("{}", Duration::from_seconds(1)), "1s");
535		assert_eq!(format!("{}", Duration::from_seconds(30)), "30s");
536		assert_eq!(format!("{}", Duration::from_seconds(59)), "59s");
537	}
538
539	#[test]
540	fn test_duration_display_minutes_only() {
541		assert_eq!(format!("{}", Duration::from_minutes(1)), "1m");
542		assert_eq!(format!("{}", Duration::from_minutes(30)), "30m");
543		assert_eq!(format!("{}", Duration::from_minutes(59)), "59m");
544	}
545
546	#[test]
547	fn test_duration_display_hours_only() {
548		assert_eq!(format!("{}", Duration::from_hours(1)), "1h");
549		assert_eq!(format!("{}", Duration::from_hours(12)), "12h");
550		assert_eq!(format!("{}", Duration::from_hours(23)), "23h");
551	}
552
553	#[test]
554	fn test_duration_display_days_only() {
555		assert_eq!(format!("{}", Duration::from_days(1)), "1d");
556		assert_eq!(format!("{}", Duration::from_days(7)), "7d");
557		assert_eq!(format!("{}", Duration::from_days(365)), "365d");
558	}
559
560	#[test]
561	fn test_duration_display_weeks_only() {
562		assert_eq!(format!("{}", Duration::from_weeks(1)), "7d");
563		assert_eq!(format!("{}", Duration::from_weeks(2)), "14d");
564		assert_eq!(format!("{}", Duration::from_weeks(52)), "364d");
565	}
566
567	#[test]
568	fn test_duration_display_months_only() {
569		assert_eq!(format!("{}", Duration::from_months(1)), "1mo");
570		assert_eq!(format!("{}", Duration::from_months(6)), "6mo");
571		assert_eq!(format!("{}", Duration::from_months(11)), "11mo");
572	}
573
574	#[test]
575	fn test_duration_display_years_only() {
576		assert_eq!(format!("{}", Duration::from_years(1)), "1y");
577		assert_eq!(format!("{}", Duration::from_years(10)), "10y");
578		assert_eq!(format!("{}", Duration::from_years(100)), "100y");
579	}
580
581	#[test]
582	fn test_duration_display_combined_time() {
583		let d = Duration::new(0, 0, (1 * 60 * 60 + 30 * 60) * 1_000_000_000);
584		assert_eq!(format!("{}", d), "1h30m");
585
586		let d = Duration::new(0, 0, (5 * 60 + 45) * 1_000_000_000);
587		assert_eq!(format!("{}", d), "5m45s");
588
589		let d = Duration::new(0, 0, (2 * 60 * 60 + 30 * 60 + 45) * 1_000_000_000);
590		assert_eq!(format!("{}", d), "2h30m45s");
591	}
592
593	#[test]
594	fn test_duration_display_combined_date_time() {
595		assert_eq!(format!("{}", Duration::new(0, 1, 2 * 60 * 60 * 1_000_000_000)), "1d2h");
596		assert_eq!(format!("{}", Duration::new(0, 1, 30 * 60 * 1_000_000_000)), "1d30m");
597		assert_eq!(format!("{}", Duration::new(0, 1, (2 * 60 * 60 + 30 * 60) * 1_000_000_000)), "1d2h30m");
598		assert_eq!(
599			format!("{}", Duration::new(0, 1, (2 * 60 * 60 + 30 * 60 + 45) * 1_000_000_000)),
600			"1d2h30m45s"
601		);
602	}
603
604	#[test]
605	fn test_duration_display_years_months() {
606		assert_eq!(format!("{}", Duration::new(13, 0, 0)), "1y1mo");
607		assert_eq!(format!("{}", Duration::new(27, 0, 0)), "2y3mo");
608	}
609
610	#[test]
611	fn test_duration_display_full_components() {
612		let nanos = (4 * 60 * 60 + 5 * 60 + 6) * 1_000_000_000i64;
613		assert_eq!(format!("{}", Duration::new(14, 3, nanos)), "1y2mo3d4h5m6s");
614	}
615
616	#[test]
617	fn test_duration_display_milliseconds() {
618		assert_eq!(format!("{}", Duration::from_milliseconds(123)), "123ms");
619		assert_eq!(format!("{}", Duration::from_milliseconds(1)), "1ms");
620		assert_eq!(format!("{}", Duration::from_milliseconds(999)), "999ms");
621		assert_eq!(format!("{}", Duration::from_milliseconds(1500)), "1s500ms");
622	}
623
624	#[test]
625	fn test_duration_display_microseconds() {
626		assert_eq!(format!("{}", Duration::from_microseconds(123456)), "123ms456us");
627		assert_eq!(format!("{}", Duration::from_microseconds(1)), "1us");
628		assert_eq!(format!("{}", Duration::from_microseconds(999999)), "999ms999us");
629		assert_eq!(format!("{}", Duration::from_microseconds(1500000)), "1s500ms");
630	}
631
632	#[test]
633	fn test_duration_display_nanoseconds() {
634		assert_eq!(format!("{}", Duration::from_nanoseconds(123456789)), "123ms456us789ns");
635		assert_eq!(format!("{}", Duration::from_nanoseconds(1)), "1ns");
636		assert_eq!(format!("{}", Duration::from_nanoseconds(999999999)), "999ms999us999ns");
637		assert_eq!(format!("{}", Duration::from_nanoseconds(1500000000)), "1s500ms");
638	}
639
640	#[test]
641	fn test_duration_display_sub_second_decomposition() {
642		let d = Duration::new(0, 0, 1 * 1_000_000_000 + 500 * 1_000_000);
643		assert_eq!(format!("{}", d), "1s500ms");
644
645		let d = Duration::new(0, 0, 2 * 1_000_000_000 + 123456 * 1_000);
646		assert_eq!(format!("{}", d), "2s123ms456us");
647
648		let d = Duration::new(0, 0, 3 * 1_000_000_000 + 123456789);
649		assert_eq!(format!("{}", d), "3s123ms456us789ns");
650	}
651
652	#[test]
653	fn test_duration_display_complex() {
654		let d = Duration::new(0, 1, (2 * 60 * 60 + 30 * 60 + 45) * 1_000_000_000 + 123 * 1_000_000);
655		assert_eq!(format!("{}", d), "1d2h30m45s123ms");
656
657		let d = Duration::new(0, 7, (12 * 60 * 60 + 45 * 60 + 30) * 1_000_000_000 + 456789 * 1_000);
658		assert_eq!(format!("{}", d), "7d12h45m30s456ms789us");
659	}
660
661	#[test]
662	fn test_duration_display_sub_second_only() {
663		assert_eq!(format!("{}", Duration::from_nanoseconds(100000000)), "100ms");
664		assert_eq!(format!("{}", Duration::from_nanoseconds(120000000)), "120ms");
665		assert_eq!(format!("{}", Duration::from_nanoseconds(123000000)), "123ms");
666		assert_eq!(format!("{}", Duration::from_nanoseconds(100)), "100ns");
667		assert_eq!(format!("{}", Duration::from_nanoseconds(10)), "10ns");
668		assert_eq!(format!("{}", Duration::from_nanoseconds(1000)), "1us");
669	}
670
671	#[test]
672	fn test_duration_display_negative() {
673		assert_eq!(format!("{}", Duration::from_seconds(-30)), "-30s");
674		assert_eq!(format!("{}", Duration::from_minutes(-5)), "-5m");
675		assert_eq!(format!("{}", Duration::from_hours(-2)), "-2h");
676		assert_eq!(format!("{}", Duration::from_days(-1)), "-1d");
677	}
678
679	#[test]
680	fn test_duration_display_negative_sub_second() {
681		assert_eq!(format!("{}", Duration::from_milliseconds(-500)), "-500ms");
682		assert_eq!(format!("{}", Duration::from_microseconds(-100)), "-100us");
683		assert_eq!(format!("{}", Duration::from_nanoseconds(-50)), "-50ns");
684	}
685
686	#[test]
687	fn test_duration_display_large() {
688		assert_eq!(format!("{}", Duration::from_days(1000)), "1000d");
689		assert_eq!(format!("{}", Duration::from_hours(25)), "1d1h");
690		assert_eq!(format!("{}", Duration::from_minutes(1500)), "1d1h");
691		assert_eq!(format!("{}", Duration::from_seconds(90000)), "1d1h");
692	}
693
694	#[test]
695	fn test_duration_display_edge_cases() {
696		assert_eq!(format!("{}", Duration::from_nanoseconds(1)), "1ns");
697		assert_eq!(format!("{}", Duration::from_nanoseconds(999999999)), "999ms999us999ns");
698		assert_eq!(format!("{}", Duration::from_nanoseconds(1000000000)), "1s");
699		assert_eq!(format!("{}", Duration::from_nanoseconds(60 * 1000000000)), "1m");
700		assert_eq!(format!("{}", Duration::from_nanoseconds(3600 * 1000000000)), "1h");
701		assert_eq!(format!("{}", Duration::from_nanoseconds(86400 * 1000000000)), "1d");
702	}
703
704	#[test]
705	fn test_duration_display_abs_and_negate() {
706		let d = Duration::from_seconds(-30);
707		assert_eq!(format!("{}", d.abs()), "30s");
708
709		let d = Duration::from_seconds(30);
710		assert_eq!(format!("{}", d.negate()), "-30s");
711	}
712}