jiff/fmt/friendly/
printer.rs

1use crate::{
2    fmt::{
3        util::{DecimalFormatter, FractionalFormatter},
4        Write, WriteExt,
5    },
6    Error, SignedDuration, Span, Unit,
7};
8
9const SECS_PER_HOUR: u64 = MINS_PER_HOUR * SECS_PER_MIN;
10const SECS_PER_MIN: u64 = 60;
11const MINS_PER_HOUR: u64 = 60;
12const NANOS_PER_HOUR: u128 =
13    (SECS_PER_MIN * MINS_PER_HOUR * NANOS_PER_SEC) as u128;
14const NANOS_PER_MIN: u128 = (SECS_PER_MIN * NANOS_PER_SEC) as u128;
15const NANOS_PER_SEC: u64 = 1_000_000_000;
16const NANOS_PER_MILLI: u32 = 1_000_000;
17const NANOS_PER_MICRO: u32 = 1_000;
18
19/// Configuration for [`SpanPrinter::designator`].
20///
21/// This controls which kinds of designators to use when formatting a
22/// "friendly" duration. Generally, this only provides one axis of control:
23/// the length of each designator.
24///
25/// # Example
26///
27/// ```
28/// use jiff::{fmt::friendly::{Designator, SpanPrinter}, ToSpan};
29///
30/// let span = 1.year().months(2);
31///
32/// let printer = SpanPrinter::new();
33/// assert_eq!(printer.span_to_string(&span), "1y 2mo");
34///
35/// let printer = SpanPrinter::new().designator(Designator::Short);
36/// assert_eq!(printer.span_to_string(&span), "1yr 2mos");
37///
38/// let printer = SpanPrinter::new().designator(Designator::Verbose);
39/// assert_eq!(printer.span_to_string(&span), "1year 2months");
40///
41/// let printer = SpanPrinter::new().designator(Designator::HumanTime);
42/// assert_eq!(printer.span_to_string(&span), "1y 2months");
43/// ```
44#[derive(Clone, Copy, Debug)]
45#[non_exhaustive]
46pub enum Designator {
47    /// This writes out the full word of each unit designation. For example,
48    /// `year`.
49    Verbose,
50    /// This writes out a short but not minimal label for each unit. For
51    /// example, `yr` for `year` and `yrs` for `years`.
52    Short,
53    /// This writes out the shortest possible label for each unit that is still
54    /// generally recognizable. For example, `y`. Note that in the compact
55    /// representation, and unlike the verbose and short representations, there
56    /// is no distinction between singular or plural.
57    Compact,
58    /// A special mode that uses designator labels that are known to be
59    /// compatible with the `humantime` crate.
60    ///
61    /// None of `Verbose`, `Short` or `Compact` are compatible with
62    /// `humantime`.
63    ///
64    /// `Compact` is, on its own, nearly compatible. When using `Compact`, all
65    /// designator labels are parsable by `humantime` except for months and
66    /// microseconds. For months, Jiff uses `mo` and `mos`, but `humantime`
67    /// only parses `months`, `month` and `M`. Jiff specifically doesn't
68    /// support `M` for months because of the confusability with minutes.
69    /// For microseconds, Jiff uses `µs` which `humantime` does not support
70    /// parsing.
71    ///
72    /// Most of the designator labels Jiff uses for `Short` aren't supported
73    /// by `humantime`. And even when they are, `humantime` is inconsistent.
74    /// For example, `humantime` supports `sec` and `secs`, but only `nsec`
75    /// and not `nsecs`.
76    ///
77    /// Finally, for `Verbose`, humantime supports spelling out some units
78    /// in their entirety (e.g., `seconds`) but not others (e.g., `nanoseconds`
79    /// is not supported by `humantime`).
80    ///
81    /// Therefore, this custom variant is provided so that designator labels
82    /// that are compatible with both Jiff and `humantime`, even when there
83    /// isn't a coherent concept otherwise connecting their style.
84    HumanTime,
85}
86
87/// Configuration for [`SpanPrinter::spacing`].
88///
89/// This controls how much or how little whitespace is inserted into a
90/// "friendly" formatted duration. Typically, one wants less whitespace when
91/// using short unit designators (i.e., `y` instead of `years`), and more
92/// whitespace when using longer unit designators.
93///
94/// # Example
95///
96/// ```
97/// use jiff::{
98///     fmt::friendly::{Designator, Spacing, SpanPrinter},
99///     ToSpan,
100/// };
101///
102/// let span = 1.year().months(2);
103///
104/// // The default tries to balance spacing with compact
105/// // unit designators.
106/// let printer = SpanPrinter::new();
107/// assert_eq!(printer.span_to_string(&span), "1y 2mo");
108///
109/// // But you can use slightly more descriptive
110/// // designators without being too verbose.
111/// let printer = SpanPrinter::new()
112///     .designator(Designator::Short);
113/// assert_eq!(printer.span_to_string(&span), "1yr 2mos");
114///
115/// // When spacing is removed, it usually looks nicer
116/// // to use compact unit designators.
117/// let printer = SpanPrinter::new()
118///     .spacing(Spacing::None)
119///     .designator(Designator::Compact);
120/// assert_eq!(printer.span_to_string(&span), "1y2mo");
121///
122/// // Conversely, when using more spacing, it usually
123/// // looks nicer to use verbose unit designators.
124/// let printer = SpanPrinter::new()
125///     .spacing(Spacing::BetweenUnitsAndDesignators)
126///     .designator(Designator::Verbose);
127/// assert_eq!(printer.span_to_string(&span), "1 year 2 months");
128/// ```
129#[derive(Clone, Copy, Debug)]
130#[non_exhaustive]
131pub enum Spacing {
132    /// Does not insert any ASCII whitespace.
133    ///
134    /// Except in the case that [`SpanPrinter::hours_minutes_seconds`] is
135    /// enabled and one is formatting a span with non-zero calendar units, then
136    /// an ASCII whitespace is inserted between the calendar and non-calendar
137    /// units even when `Spacing::None` is used.
138    None,
139    /// Inserts one ASCII whitespace between the unit designator and the next
140    /// unit value.
141    ///
142    /// For example, `1year 2months`.
143    BetweenUnits,
144    /// Inserts one ASCII whitespace between the unit value and the unit
145    /// designator, in addition to inserting one ASCII whitespace between the
146    /// unit designator and the next unit value.
147    ///
148    /// For example, `1 year 2 months`.
149    BetweenUnitsAndDesignators,
150}
151
152impl Spacing {
153    fn between_units(self) -> &'static str {
154        match self {
155            Spacing::None => "",
156            Spacing::BetweenUnits => " ",
157            Spacing::BetweenUnitsAndDesignators => " ",
158        }
159    }
160
161    fn between_units_and_designators(self) -> &'static str {
162        match self {
163            Spacing::None => "",
164            Spacing::BetweenUnits => "",
165            Spacing::BetweenUnitsAndDesignators => " ",
166        }
167    }
168}
169
170/// Configuration for [`SpanPrinter::direction`].
171///
172/// This controls how the sign, if at all, is included in the formatted
173/// duration.
174///
175/// When using the "hours-minutes-seconds" format, `Auto` and `Suffix` are
176/// both treated as equivalent to `Sign` when all calendar units (days and
177/// greater) are zero.
178///
179/// # Example
180///
181/// ```
182/// use jiff::{fmt::friendly::{Direction, SpanPrinter}, SignedDuration};
183///
184/// let duration = SignedDuration::from_secs(-1);
185///
186/// let printer = SpanPrinter::new();
187/// assert_eq!(printer.duration_to_string(&duration), "1s ago");
188///
189/// let printer = SpanPrinter::new().direction(Direction::Sign);
190/// assert_eq!(printer.duration_to_string(&duration), "-1s");
191/// ```
192#[derive(Clone, Copy, Debug)]
193#[non_exhaustive]
194pub enum Direction {
195    /// Sets the sign format based on other configuration options.
196    ///
197    /// When [`SpanPrinter::spacing`] is set to [`Spacing::None`], then
198    /// `Auto` is equivalent to `Sign`.
199    ///
200    /// When using the "hours-minutes-seconds" format, `Auto` is equivalent to
201    /// `Sign` when all calendar units (days and greater) are zero.
202    ///
203    /// Otherwise, `Auto` is equivalent to `Suffix`.
204    ///
205    /// This is the default used by [`SpanPrinter`].
206    Auto,
207    /// When set, a sign is only written when the span or duration is negative.
208    /// And when it is written, it is written as a prefix of the formatted
209    /// duration.
210    Sign,
211    /// A sign is always written, with `-` for negative spans and `+` for all
212    /// non-negative spans. The sign is always written as a prefix of the
213    /// formatted duration.
214    ForceSign,
215    /// When set, a sign is only written when the span or duration is negative.
216    /// And when it is written, it is written as a suffix via a trailing ` ago`
217    /// string.
218    Suffix,
219}
220
221impl Direction {
222    /// Returns the sign string to use (as either a prefix or a suffix) based
223    /// on the given parameters.
224    ///
225    /// This lets us do the case analysis for how to write the sign exactly
226    /// once.
227    fn sign(
228        self,
229        printer: &SpanPrinter,
230        has_calendar: bool,
231        signum: i8,
232    ) -> Option<DirectionSign> {
233        match self {
234            Direction::Auto => match printer.spacing {
235                Spacing::None => {
236                    if signum < 0 {
237                        Some(DirectionSign::Prefix("-"))
238                    } else {
239                        None
240                    }
241                }
242                Spacing::BetweenUnits
243                | Spacing::BetweenUnitsAndDesignators => {
244                    if signum < 0 {
245                        if printer.hms && !has_calendar {
246                            Some(DirectionSign::Prefix("-"))
247                        } else {
248                            Some(DirectionSign::Suffix(" ago"))
249                        }
250                    } else {
251                        None
252                    }
253                }
254            },
255            Direction::Sign => {
256                if signum < 0 {
257                    Some(DirectionSign::Prefix("-"))
258                } else {
259                    None
260                }
261            }
262            Direction::ForceSign => {
263                Some(DirectionSign::Prefix(if signum < 0 { "-" } else { "+" }))
264            }
265            Direction::Suffix => {
266                if signum < 0 {
267                    Some(DirectionSign::Suffix(" ago"))
268                } else {
269                    None
270                }
271            }
272        }
273    }
274}
275
276/// The sign to write and whether it should be a prefix or a suffix.
277#[derive(Clone, Copy, Debug)]
278enum DirectionSign {
279    Prefix(&'static str),
280    Suffix(&'static str),
281}
282
283/// Configuration for [`SpanPrinter::fractional`].
284///
285/// This controls what kind of fractional unit to use when printing a duration.
286/// The default, unless [`SpanPrinter::hours_minutes_seconds`] is enabled, is
287/// to not write any fractional numbers at all.
288///
289/// The fractional unit set refers to the smallest whole integer that can occur
290/// in a "friendly" formatted duration. If there are any non-zero units less
291/// than the fractional unit in the duration, then they are formatted as a
292/// fraction.
293///
294/// # Example
295///
296/// This example shows how to write the same duration with different
297/// fractional settings:
298///
299/// ```
300/// use jiff::{fmt::friendly::{FractionalUnit, SpanPrinter}, SignedDuration};
301///
302/// let duration = SignedDuration::from_secs(3663);
303///
304/// let printer = SpanPrinter::new()
305///     .fractional(Some(FractionalUnit::Hour));
306/// assert_eq!(printer.duration_to_string(&duration), "1.0175h");
307///
308/// let printer = SpanPrinter::new()
309///     .fractional(Some(FractionalUnit::Minute));
310/// assert_eq!(printer.duration_to_string(&duration), "1h 1.05m");
311///
312/// let printer = SpanPrinter::new()
313///     .fractional(Some(FractionalUnit::Second));
314/// assert_eq!(printer.duration_to_string(&duration), "1h 1m 3s");
315/// ```
316#[derive(Clone, Copy, Debug)]
317#[non_exhaustive]
318pub enum FractionalUnit {
319    /// The smallest whole integer unit allowed is hours.
320    ///
321    /// **WARNING**: Since fractional units are limited to 9 decimal places,
322    /// using this setting could result in precision loss.
323    Hour,
324    /// The smallest whole integer unit allowed is minutes.
325    ///
326    /// **WARNING**: Since fractional units are limited to 9 decimal places,
327    /// using this setting could result in precision loss.
328    Minute,
329    /// The smallest whole integer unit allowed is seconds.
330    Second,
331    /// The smallest whole integer unit allowed is milliseconds.
332    Millisecond,
333    /// The smallest whole integer unit allowed is microseconds.
334    Microsecond,
335}
336
337impl From<FractionalUnit> for Unit {
338    fn from(u: FractionalUnit) -> Unit {
339        match u {
340            FractionalUnit::Hour => Unit::Hour,
341            FractionalUnit::Minute => Unit::Minute,
342            FractionalUnit::Second => Unit::Second,
343            FractionalUnit::Millisecond => Unit::Millisecond,
344            FractionalUnit::Microsecond => Unit::Microsecond,
345        }
346    }
347}
348
349/// A printer for Jiff's "friendly" duration format.
350///
351/// This printer provides a lot of different knobs for controlling how
352/// durations are formatted. It supports formatting both [`SignedDuration`]
353/// and [`Span`].
354///
355/// # Example: automatic use through `Display`
356///
357/// The default configuration of this printer is used for "alternate" display
358/// formatting for both [`SignedDuration`] and [`Span`]:
359///
360/// ```
361/// use jiff::{SignedDuration, ToSpan};
362///
363/// let span = 1.year().months(2).hours(15).seconds(30).nanoseconds(1);
364/// assert_eq!(format!("{span:#}"), "1y 2mo 15h 30s 1ns");
365///
366/// let sdur = SignedDuration::new(15 * 60 * 60 + 30, 1);
367/// assert_eq!(format!("{sdur:#}"), "15h 30s 1ns");
368/// ```
369///
370/// # Example: variety of formatting configurations
371///
372/// This example shows a few different ways of formatting the same `Span`:
373///
374/// ```
375/// use jiff::{
376///     fmt::friendly::{Designator, Spacing, SpanPrinter},
377///     ToSpan,
378/// };
379///
380/// let span = 1.year().months(2).hours(15).seconds(30).nanoseconds(1);
381///
382/// let printer = SpanPrinter::new();
383/// assert_eq!(
384///     printer.span_to_string(&span),
385///     "1y 2mo 15h 30s 1ns",
386/// );
387///
388/// let printer = SpanPrinter::new()
389///     .designator(Designator::Short);
390/// assert_eq!(
391///     printer.span_to_string(&span),
392///     "1yr 2mos 15hrs 30secs 1nsec",
393/// );
394///
395/// let printer = SpanPrinter::new()
396///     .spacing(Spacing::None)
397///     .designator(Designator::Compact);
398/// assert_eq!(
399///     printer.span_to_string(&span),
400///     "1y2mo15h30s1ns",
401/// );
402///
403/// let printer = SpanPrinter::new()
404///     .spacing(Spacing::BetweenUnitsAndDesignators)
405///     .comma_after_designator(true)
406///     .designator(Designator::Verbose);
407/// assert_eq!(
408///     printer.span_to_string(&span),
409///     "1 year, 2 months, 15 hours, 30 seconds, 1 nanosecond",
410/// );
411///
412/// let printer = SpanPrinter::new()
413///     .hours_minutes_seconds(true)
414///     .spacing(Spacing::BetweenUnitsAndDesignators)
415///     .comma_after_designator(true)
416///     .designator(Designator::Verbose);
417/// assert_eq!(
418///     printer.span_to_string(&span),
419///     "1 year, 2 months, 15:00:30.000000001",
420/// );
421/// ```
422///
423/// # Example: negative durations
424///
425/// By default, a negative duration will be represented with an ` ago` suffix:
426///
427/// ```
428/// use jiff::{fmt::friendly::SpanPrinter, ToSpan};
429///
430/// let span = -1.year().months(2).hours(15).seconds(30).nanoseconds(1);
431///
432/// let printer = SpanPrinter::new();
433/// assert_eq!(
434///     printer.span_to_string(&span),
435///     "1y 2mo 15h 30s 1ns ago",
436/// );
437/// ```
438///
439/// But one can also use a prefix `-` sign instead. Usually this works better
440/// without any spacing and compact designators:
441///
442/// ```
443/// use jiff::{fmt::friendly::{Designator, Spacing, SpanPrinter}, ToSpan};
444///
445/// let span = -1.year().months(2).hours(15).seconds(30).nanoseconds(1);
446///
447/// let printer = SpanPrinter::new()
448///     .spacing(Spacing::None)
449///     .designator(Designator::Compact);
450/// assert_eq!(
451///     printer.span_to_string(&span),
452///     "-1y2mo15h30s1ns",
453/// );
454/// ```
455#[derive(Clone, Debug)]
456pub struct SpanPrinter {
457    designator: Designator,
458    spacing: Spacing,
459    direction: Direction,
460    fractional: Option<FractionalUnit>,
461    comma_after_designator: bool,
462    hms: bool,
463    padding: Option<u8>,
464    precision: Option<u8>,
465    zero_unit: Unit,
466}
467
468impl SpanPrinter {
469    /// Creates a new printer for the "friendly" duration format.
470    ///
471    /// The printer returned uses the default configuration. This is
472    /// identical to `SpanPrinter::default`, but it can be used in a `const`
473    /// context.
474    ///
475    /// # Example
476    ///
477    /// This example shows how to format a duration directly to a `Vec<u8>`.
478    ///
479    /// ```
480    /// use jiff::{fmt::friendly::SpanPrinter, ToSpan};
481    ///
482    /// static PRINTER: SpanPrinter = SpanPrinter::new();
483    ///
484    /// let span = 1.year().months(2);
485    /// let mut buf = vec![];
486    /// // Writing to a `Vec<u8>` never fails (aside from OOM).
487    /// PRINTER.print_span(&span, &mut buf).unwrap();
488    /// assert_eq!(buf, b"1y 2mo");
489    /// ```
490    #[inline]
491    pub const fn new() -> SpanPrinter {
492        SpanPrinter {
493            designator: Designator::Compact,
494            spacing: Spacing::BetweenUnits,
495            direction: Direction::Auto,
496            fractional: None,
497            comma_after_designator: false,
498            hms: false,
499            padding: None,
500            precision: None,
501            zero_unit: Unit::Second,
502        }
503    }
504
505    /// Configures the kind of unit designators to use.
506    ///
507    /// There are no specific advantages or disadvantages to the kind
508    /// of designator you pick other than aesthetic preference. Shorter
509    /// designators are also likely faster to parse and print.
510    ///
511    /// The default is [`Designator::Compact`], which uses things like `yr`
512    /// instead of `year` (verbose) or `y` (compact).
513    ///
514    /// # Example
515    ///
516    /// ```
517    /// use jiff::{
518    ///     fmt::friendly::{Designator, SpanPrinter},
519    ///     ToSpan,
520    /// };
521    ///
522    /// let span = 1.year().months(2);
523    ///
524    /// let printer = SpanPrinter::new();
525    /// assert_eq!(printer.span_to_string(&span), "1y 2mo");
526    ///
527    /// let printer = SpanPrinter::new().designator(Designator::Short);
528    /// assert_eq!(printer.span_to_string(&span), "1yr 2mos");
529    ///
530    /// let printer = SpanPrinter::new().designator(Designator::Verbose);
531    /// assert_eq!(printer.span_to_string(&span), "1year 2months");
532    /// ```
533    #[inline]
534    pub const fn designator(self, designator: Designator) -> SpanPrinter {
535        SpanPrinter { designator, ..self }
536    }
537
538    /// Configures the spacing between the units and the designator labels.
539    ///
540    /// The default is [`Spacing::BetweenUnits`], which results in durations
541    /// like `1y 2mo`. `Spacing::None` would result in `1y2mo` and
542    /// `Spacing::BetweenUnitsAndDesignators` would result in `1 y 2 mo`.
543    ///
544    /// # Example
545    ///
546    /// ```
547    /// use jiff::{
548    ///     fmt::friendly::{Designator, Spacing, SpanPrinter},
549    ///     ToSpan,
550    /// };
551    ///
552    /// let span = 1.year().months(2);
553    ///
554    /// // The default tries to balance spacing with compact
555    /// // unit designators.
556    /// let printer = SpanPrinter::new();
557    /// assert_eq!(printer.span_to_string(&span), "1y 2mo");
558    ///
559    /// // But you can use slightly more descriptive
560    /// // designators without being too verbose.
561    /// let printer = SpanPrinter::new()
562    ///     .designator(Designator::Short);
563    /// assert_eq!(printer.span_to_string(&span), "1yr 2mos");
564    ///
565    /// // When spacing is removed, it usually looks nicer
566    /// // to use compact unit designators.
567    /// let printer = SpanPrinter::new()
568    ///     .spacing(Spacing::None)
569    ///     .designator(Designator::Compact);
570    /// assert_eq!(printer.span_to_string(&span), "1y2mo");
571    ///
572    /// // Conversely, when using more spacing, it usually
573    /// // looks nicer to use verbose unit designators.
574    /// let printer = SpanPrinter::new()
575    ///     .spacing(Spacing::BetweenUnitsAndDesignators)
576    ///     .designator(Designator::Verbose);
577    /// assert_eq!(printer.span_to_string(&span), "1 year 2 months");
578    /// ```
579    ///
580    /// # Example: `Spacing::None` can still result in whitespace
581    ///
582    /// In the case that [`SpanPrinter::hours_minutes_seconds`] is enabled
583    /// and one is formatting a span with non-zero calendar units, then an
584    /// ASCII whitespace is inserted between the calendar and non-calendar
585    /// units even when `Spacing::None` is used:
586    ///
587    /// ```
588    /// use jiff::{fmt::friendly::{Spacing, SpanPrinter}, ToSpan};
589    ///
590    /// let span = 1.year().months(2).hours(15);
591    ///
592    /// let printer = SpanPrinter::new()
593    ///     .spacing(Spacing::None)
594    ///     .hours_minutes_seconds(true);
595    /// assert_eq!(printer.span_to_string(&span), "1y2mo 15:00:00");
596    /// ```
597    #[inline]
598    pub const fn spacing(self, spacing: Spacing) -> SpanPrinter {
599        SpanPrinter { spacing, ..self }
600    }
601
602    /// Configures how and when the sign for the duration is written.
603    ///
604    /// The default is [`Direction::Auto`]. In most cases, this results in
605    /// writing the suffix ` ago` after printing the duration units when the
606    /// sign of the duration is negative. And when the sign is positive, there
607    /// is no suffix. However, this can vary based on other settings. For
608    /// example, when [`SpanPrinter::spacing`] is set to [`Spacing::None`],
609    /// then `Direction::Auto` is treated as if it were [`Direction::Sign`].
610    ///
611    /// # Example
612    ///
613    /// ```
614    /// use jiff::{fmt::friendly::{Direction, SpanPrinter}, SignedDuration};
615    ///
616    /// let duration = SignedDuration::from_secs(-1);
617    ///
618    /// let printer = SpanPrinter::new();
619    /// assert_eq!(printer.duration_to_string(&duration), "1s ago");
620    ///
621    /// let printer = SpanPrinter::new().direction(Direction::Sign);
622    /// assert_eq!(printer.duration_to_string(&duration), "-1s");
623    /// ```
624    #[inline]
625    pub const fn direction(self, direction: Direction) -> SpanPrinter {
626        SpanPrinter { direction, ..self }
627    }
628
629    /// Enable fractional formatting for the given unit.
630    ///
631    /// When [`SpanPrinter::hours_minutes_seconds`] is enabled, then this
632    /// setting is automatically set to [`FractionalUnit::Second`]. Otherwise,
633    /// it defaults to `None`, which means no fractions are ever written.
634    ///
635    /// # Example
636    ///
637    /// This example shows how to write the same duration with different
638    /// fractional settings:
639    ///
640    /// ```
641    /// use jiff::{fmt::friendly::{FractionalUnit, SpanPrinter}, SignedDuration};
642    ///
643    /// let duration = SignedDuration::from_secs(3663);
644    ///
645    /// let printer = SpanPrinter::new()
646    ///     .fractional(Some(FractionalUnit::Hour));
647    /// assert_eq!(printer.duration_to_string(&duration), "1.0175h");
648    ///
649    /// let printer = SpanPrinter::new()
650    ///     .fractional(Some(FractionalUnit::Minute));
651    /// assert_eq!(printer.duration_to_string(&duration), "1h 1.05m");
652    ///
653    /// let printer = SpanPrinter::new()
654    ///     .fractional(Some(FractionalUnit::Second));
655    /// assert_eq!(printer.duration_to_string(&duration), "1h 1m 3s");
656    /// ```
657    ///
658    /// # Example: precision loss
659    ///
660    /// Because the "friendly" format is limited to 9 decimal places, when
661    /// using `FractionalUnit::Hour` or `FractionalUnit::Minute`, it is
662    /// possible for precision loss to occur.
663    ///
664    /// ```
665    /// use jiff::{fmt::friendly::{FractionalUnit, SpanPrinter}, SignedDuration};
666    ///
667    /// // one nanosecond
668    /// let duration = SignedDuration::new(0, 1);
669    ///
670    /// let printer = SpanPrinter::new()
671    ///     .fractional(Some(FractionalUnit::Hour));
672    /// assert_eq!(printer.duration_to_string(&duration), "0h");
673    ///
674    /// let printer = SpanPrinter::new()
675    ///     .fractional(Some(FractionalUnit::Minute));
676    /// assert_eq!(printer.duration_to_string(&duration), "0m");
677    /// ```
678    #[inline]
679    pub const fn fractional(
680        self,
681        unit: Option<FractionalUnit>,
682    ) -> SpanPrinter {
683        SpanPrinter { fractional: unit, ..self }
684    }
685
686    /// When enabled, commas are written after unit designators.
687    ///
688    /// This is disabled by default.
689    ///
690    /// # Example
691    ///
692    /// ```
693    /// use jiff::{fmt::friendly::{Designator, Spacing, SpanPrinter}, ToSpan};
694    ///
695    /// static PRINTER: SpanPrinter = SpanPrinter::new()
696    ///     .designator(Designator::Verbose)
697    ///     .spacing(Spacing::BetweenUnitsAndDesignators)
698    ///     .comma_after_designator(true);
699    ///
700    /// let span = 5.years().months(3).milliseconds(123);
701    /// assert_eq!(
702    ///     PRINTER.span_to_string(&span),
703    ///     "5 years, 3 months, 123 milliseconds",
704    /// );
705    /// ```
706    #[inline]
707    pub const fn comma_after_designator(self, yes: bool) -> SpanPrinter {
708        SpanPrinter { comma_after_designator: yes, ..self }
709    }
710
711    /// Formats the span or duration into a `HH:MM:SS[.fffffffff]` format.
712    ///
713    /// When formatting a `Span` with non-zero calendar units (units of days
714    /// or greater), then the calendar units are formatted as typical with
715    /// their corresponding designators. For example, `1d 01:00:00`. Note
716    /// that when formatting a `SignedDuration`, calendar units are never used.
717    ///
718    /// When this is enabled, many of the other options are either ignored or
719    /// fixed to a specific setting:
720    ///
721    /// * Since this format does not use any unit designators for units of
722    /// hours or smaller, the [`SpanPrinter::designator`] setting is ignored
723    /// for hours or smaller. It is still used when formatting a `Span` with
724    /// non-zero calendar units.
725    /// * [`SpanPrinter::spacing`] setting is ignored for units of hours or
726    /// smaller.
727    /// * The [`SpanPrinter::fractional`] setting is forcefully set to
728    /// [`FractionalUnit::Second`]. It cannot be changed.
729    /// * The [`SpanPrinter::comma_after_designator`] setting is ignored for
730    /// units of hours or smaller.
731    /// * When the padding is not specified, it defaults to `2` for hours,
732    /// minutes and seconds and `0` for any calendar units present.
733    /// * The precision setting is respected as documented.
734    ///
735    /// This format is useful in contexts for interfacing with existing systems
736    /// that require this style of format, or if the `HH:MM:SS` is just in
737    /// general preferred.
738    ///
739    /// # Loss of fidelity
740    ///
741    /// When using this format with a `Span`, sub-second units are formatted
742    /// as a fractional second. This means that `1000 milliseconds` and
743    /// `1 second` format to precisely the same string. This is similar to the
744    /// loss of fidelity when using [`fmt::temporal`](crate::fmt::temporal)
745    /// to format spans in the ISO 8601 duration format.
746    ///
747    /// # Example
748    ///
749    /// This shows how to format a `Span` in `HH:MM:SS` format:
750    ///
751    /// ```
752    /// use jiff::{fmt::friendly::SpanPrinter, ToSpan};
753    ///
754    /// static PRINTER: SpanPrinter =
755    ///     SpanPrinter::new().hours_minutes_seconds(true);
756    ///
757    /// let span = 2.hours().minutes(59).seconds(15).milliseconds(123);
758    /// assert_eq!(PRINTER.span_to_string(&span), "02:59:15.123");
759    /// assert_eq!(PRINTER.span_to_string(&-span), "-02:59:15.123");
760    ///
761    /// // This shows what happens with calendar units.
762    /// let span = 15.days().hours(2).minutes(59).seconds(15).milliseconds(123);
763    /// assert_eq!(PRINTER.span_to_string(&span), "15d 02:59:15.123");
764    /// // Notice that because calendar units are specified and the sign
765    /// // setting is set to "auto" by default, it has switched to a suffix.
766    /// assert_eq!(PRINTER.span_to_string(&-span), "15d 02:59:15.123 ago");
767    /// ```
768    ///
769    /// And this shows the same, but with a [`SignedDuration`]:
770    ///
771    /// ```
772    /// use jiff::{fmt::friendly::SpanPrinter, SignedDuration};
773    ///
774    /// static PRINTER: SpanPrinter =
775    ///     SpanPrinter::new().hours_minutes_seconds(true);
776    ///
777    /// let duration = SignedDuration::new(
778    ///     2 * 60 * 60 + 59 * 60 + 15,
779    ///     123_000_000,
780    /// );
781    /// assert_eq!(PRINTER.duration_to_string(&duration), "02:59:15.123");
782    /// assert_eq!(PRINTER.duration_to_string(&-duration), "-02:59:15.123");
783    /// ```
784    ///
785    /// # Example: `Span` versus `SignedDuration`
786    ///
787    /// The main advantage of a `Span` is that, except for fractional
788    /// components, the unit values emitted correspond precisely to the values
789    /// in the `Span`. Where as for a `SignedDuration`, the units are always
790    /// computed from a single absolute duration in a way that is always
791    /// balanced:
792    ///
793    /// ```
794    /// use jiff::{fmt::friendly::SpanPrinter, SignedDuration, ToSpan};
795    ///
796    /// static PRINTER: SpanPrinter =
797    ///     SpanPrinter::new().hours_minutes_seconds(true);
798    ///
799    /// let span = 120.minutes();
800    /// assert_eq!(PRINTER.span_to_string(&span), "00:120:00");
801    ///
802    /// let duration = SignedDuration::from_mins(120);
803    /// assert_eq!(PRINTER.duration_to_string(&duration), "02:00:00");
804    /// ```
805    ///
806    /// Of course, a balanced duration is sometimes what you want. But `Span`
807    /// affords the flexibility of controlling precisely what the unit values
808    /// are.
809    #[inline]
810    pub const fn hours_minutes_seconds(self, yes: bool) -> SpanPrinter {
811        SpanPrinter { hms: yes, ..self }
812    }
813
814    /// The padding to use when writing unit values.
815    ///
816    /// If a unit value has fewer digits than specified here, it is padded to
817    /// the left with zeroes. (To control precision, i.e., padding to the right
818    /// when writing fractional values, use [`SpanPrinter::precision`].)
819    ///
820    /// By default, when writing in the hours-minutes-seconds format, a padding
821    /// of `2` is used for units of hours, minutes and seconds. Otherwise, a
822    /// padding of `0` is used.
823    ///
824    /// # Example
825    ///
826    /// This shows some examples of configuring padding when writing in default
827    /// format with unit designators:
828    ///
829    /// ```
830    /// use jiff::{fmt::friendly::SpanPrinter, ToSpan};
831    ///
832    /// let printer = SpanPrinter::new();
833    /// assert_eq!(printer.span_to_string(&1.hour()), "1h");
834    /// let printer = SpanPrinter::new().padding(3);
835    /// assert_eq!(printer.span_to_string(&1.hour()), "001h");
836    /// ```
837    ///
838    /// And this shows some examples with the hours-minutes-seconds format.
839    /// Notice how padding is enabled by default.
840    ///
841    /// ```
842    /// use jiff::{fmt::friendly::SpanPrinter, ToSpan};
843    ///
844    /// let printer = SpanPrinter::new().hours_minutes_seconds(true);
845    /// assert_eq!(printer.span_to_string(&1.hour()), "01:00:00");
846    /// let printer = SpanPrinter::new().hours_minutes_seconds(true).padding(0);
847    /// assert_eq!(printer.span_to_string(&1.hour()), "1:0:0");
848    ///
849    /// // In this case, under the default configuration, the padding
850    /// // for calendar units is 0 but the padding for time units is 2.
851    /// let printer = SpanPrinter::new().hours_minutes_seconds(true);
852    /// assert_eq!(printer.span_to_string(&1.day().hours(1)), "1d 01:00:00");
853    /// ```
854    #[inline]
855    pub const fn padding(self, digits: u8) -> SpanPrinter {
856        SpanPrinter { padding: Some(digits), ..self }
857    }
858
859    /// The precision to use when writing fractional unit values.
860    ///
861    /// This setting has no effect if fractional formatting isn't enabled.
862    /// Fractional formatting is only enabled when [`SpanPrinter::fractional`]
863    /// is set or if [`SpanPrinter::hours_minutes_seconds`] are enabled.
864    /// Neither are enabled by default.
865    ///
866    /// A precision of `Some(0)` implies that truncation of any fractional
867    /// component always occurs.
868    ///
869    /// The default value is `None`, which means the precision is automatically
870    /// determined from the value. If no fractional component is needed, then
871    /// none will be printed.
872    ///
873    /// # Example
874    ///
875    /// ```
876    /// use jiff::{fmt::friendly::{FractionalUnit, SpanPrinter}, ToSpan};
877    ///
878    /// // No effect, because fractions aren't enabled.
879    /// let printer = SpanPrinter::new().precision(Some(2));
880    /// assert_eq!(printer.span_to_string(&1.hour()), "1h");
881    ///
882    /// // Precision setting takes effect!
883    /// let printer = SpanPrinter::new()
884    ///     .precision(Some(2))
885    ///     .fractional(Some(FractionalUnit::Hour));
886    /// assert_eq!(printer.span_to_string(&1.hour()), "1.00h");
887    ///
888    /// // The HH:MM:SS format automatically enables fractional
889    /// // second values.
890    /// let printer = SpanPrinter::new()
891    ///     // Truncate to millisecond precision.
892    ///     .precision(Some(3))
893    ///     .hours_minutes_seconds(true);
894    /// let span = 1.second().milliseconds(1).microseconds(1).nanoseconds(1);
895    /// assert_eq!(printer.span_to_string(&span), "00:00:01.001");
896    ///
897    /// // Same as above, but with the designator or "expanded"
898    /// // format. This requires explicitly enabling fractional
899    /// // units.
900    /// let printer = SpanPrinter::new()
901    ///     // Truncate to millisecond precision.
902    ///     .precision(Some(3))
903    ///     .fractional(Some(FractionalUnit::Second));
904    /// let span = 1.second().milliseconds(1).microseconds(1).nanoseconds(1);
905    /// assert_eq!(printer.span_to_string(&span), "1.001s");
906    /// ```
907    #[inline]
908    pub const fn precision(self, precision: Option<u8>) -> SpanPrinter {
909        SpanPrinter { precision, ..self }
910    }
911
912    /// Sets the unit to use when printing a duration that is zero.
913    ///
914    /// When [`SpanPrinter::fractional`] is set, then this setting is ignored
915    /// and the zero unit corresponds to the fractional unit specified.
916    ///
917    /// This defaults to [`Unit::Second`].
918    ///
919    /// # Example
920    ///
921    /// ```
922    /// use jiff::{fmt::friendly::{FractionalUnit, SpanPrinter}, ToSpan, Unit};
923    ///
924    /// // The default just always uses seconds.
925    /// let printer = SpanPrinter::new();
926    /// assert_eq!(printer.span_to_string(&0.years()), "0s");
927    ///
928    /// // We can set our own unit.
929    /// let printer = SpanPrinter::new().zero_unit(Unit::Year);
930    /// assert_eq!(printer.span_to_string(&0.years()), "0y");
931    ///
932    /// // But it's overridden if fractional units are set.
933    /// let printer = SpanPrinter::new()
934    ///     .zero_unit(Unit::Year)
935    ///     .fractional(Some(FractionalUnit::Minute));
936    /// assert_eq!(printer.span_to_string(&0.years()), "0m");
937    ///
938    /// // One use case for this option is if you're rounding
939    /// // spans and want the zero unit to reflect the smallest
940    /// // unit you're using.
941    /// let printer = SpanPrinter::new().zero_unit(Unit::Minute);
942    /// let span = 5.hours().minutes(30).seconds(59);
943    /// let rounded = span.round(Unit::Minute)?;
944    /// assert_eq!(printer.span_to_string(&rounded), "5h 31m");
945    ///
946    /// let span = 5.seconds();
947    /// let rounded = span.round(Unit::Minute)?;
948    /// assert_eq!(printer.span_to_string(&rounded), "0m");
949    ///
950    /// # Ok::<(), Box<dyn std::error::Error>>(())
951    /// ```
952    ///
953    /// The same applies for `SignedDuration`:
954    ///
955    /// ```
956    /// use jiff::{fmt::friendly::SpanPrinter, SignedDuration, Unit};
957    ///
958    /// // The default just always uses seconds.
959    /// let printer = SpanPrinter::new();
960    /// assert_eq!(printer.duration_to_string(&SignedDuration::ZERO), "0s");
961    ///
962    /// // We can set our own unit.
963    /// let printer = SpanPrinter::new().zero_unit(Unit::Minute);
964    /// assert_eq!(printer.duration_to_string(&SignedDuration::ZERO), "0m");
965    /// ```
966    #[inline]
967    pub const fn zero_unit(self, unit: Unit) -> SpanPrinter {
968        SpanPrinter { zero_unit: unit, ..self }
969    }
970
971    /// Format a `Span` into a string using the "friendly" format.
972    ///
973    /// This is a convenience routine for [`SpanPrinter::print_span`] with a
974    /// `String`.
975    ///
976    /// # Example
977    ///
978    /// ```
979    /// use jiff::{fmt::friendly::SpanPrinter, ToSpan};
980    ///
981    /// static PRINTER: SpanPrinter = SpanPrinter::new();
982    ///
983    /// let span = 3.years().months(5);
984    /// assert_eq!(PRINTER.span_to_string(&span), "3y 5mo");
985    /// ```
986    #[cfg(any(test, feature = "alloc"))]
987    pub fn span_to_string(&self, span: &Span) -> alloc::string::String {
988        let mut buf = alloc::string::String::with_capacity(4);
989        // OK because writing to `String` never fails.
990        self.print_span(span, &mut buf).unwrap();
991        buf
992    }
993
994    /// Format a `SignedDuration` into a string using the "friendly" format.
995    ///
996    /// This balances the units of the duration up to at most hours
997    /// automatically.
998    ///
999    /// This is a convenience routine for [`SpanPrinter::print_duration`] with
1000    /// a `String`.
1001    ///
1002    /// # Example
1003    ///
1004    /// ```
1005    /// use jiff::{fmt::friendly::{FractionalUnit, SpanPrinter}, SignedDuration};
1006    ///
1007    /// static PRINTER: SpanPrinter = SpanPrinter::new();
1008    ///
1009    /// let dur = SignedDuration::new(86_525, 123_000_789);
1010    /// assert_eq!(
1011    ///     PRINTER.duration_to_string(&dur),
1012    ///     "24h 2m 5s 123ms 789ns",
1013    /// );
1014    /// assert_eq!(
1015    ///     PRINTER.duration_to_string(&-dur),
1016    ///     "24h 2m 5s 123ms 789ns ago",
1017    /// );
1018    ///
1019    /// // Or, if you prefer fractional seconds:
1020    /// static PRINTER_FRACTIONAL: SpanPrinter = SpanPrinter::new()
1021    ///     .fractional(Some(FractionalUnit::Second));
1022    /// assert_eq!(
1023    ///     PRINTER_FRACTIONAL.duration_to_string(&-dur),
1024    ///     "24h 2m 5.123000789s ago",
1025    /// );
1026    /// ```
1027    #[cfg(any(test, feature = "alloc"))]
1028    pub fn duration_to_string(
1029        &self,
1030        duration: &SignedDuration,
1031    ) -> alloc::string::String {
1032        let mut buf = alloc::string::String::with_capacity(4);
1033        // OK because writing to `String` never fails.
1034        self.print_duration(duration, &mut buf).unwrap();
1035        buf
1036    }
1037
1038    /// Format a `std::time::Duration` into a string using the "friendly"
1039    /// format.
1040    ///
1041    /// This balances the units of the duration up to at most hours
1042    /// automatically.
1043    ///
1044    /// This is a convenience routine for
1045    /// [`SpanPrinter::print_unsigned_duration`] with a `String`.
1046    ///
1047    /// # Example
1048    ///
1049    /// ```
1050    /// use std::time::Duration;
1051    ///
1052    /// use jiff::fmt::friendly::{FractionalUnit, SpanPrinter};
1053    ///
1054    /// static PRINTER: SpanPrinter = SpanPrinter::new();
1055    ///
1056    /// let dur = Duration::new(86_525, 123_000_789);
1057    /// assert_eq!(
1058    ///     PRINTER.unsigned_duration_to_string(&dur),
1059    ///     "24h 2m 5s 123ms 789ns",
1060    /// );
1061    ///
1062    /// // Or, if you prefer fractional seconds:
1063    /// static PRINTER_FRACTIONAL: SpanPrinter = SpanPrinter::new()
1064    ///     .fractional(Some(FractionalUnit::Second));
1065    /// assert_eq!(
1066    ///     PRINTER_FRACTIONAL.unsigned_duration_to_string(&dur),
1067    ///     "24h 2m 5.123000789s",
1068    /// );
1069    /// ```
1070    #[cfg(any(test, feature = "alloc"))]
1071    pub fn unsigned_duration_to_string(
1072        &self,
1073        duration: &core::time::Duration,
1074    ) -> alloc::string::String {
1075        let mut buf = alloc::string::String::with_capacity(4);
1076        // OK because writing to `String` never fails.
1077        self.print_unsigned_duration(duration, &mut buf).unwrap();
1078        buf
1079    }
1080
1081    /// Print a `Span` to the given writer using the "friendly" format.
1082    ///
1083    /// # Errors
1084    ///
1085    /// This only returns an error when writing to the given [`Write`]
1086    /// implementation would fail. Some such implementations, like for `String`
1087    /// and `Vec<u8>`, never fail (unless memory allocation fails). In such
1088    /// cases, it would be appropriate to call `unwrap()` on the result.
1089    ///
1090    /// # Example
1091    ///
1092    /// ```
1093    /// use jiff::{fmt::friendly::SpanPrinter, ToSpan};
1094    ///
1095    /// static PRINTER: SpanPrinter = SpanPrinter::new();
1096    ///
1097    /// let span = 3.years().months(5);
1098    ///
1099    /// let mut buf = String::new();
1100    /// // Printing to a `String` can never fail.
1101    /// PRINTER.print_span(&span, &mut buf).unwrap();
1102    /// assert_eq!(buf, "3y 5mo");
1103    /// ```
1104    pub fn print_span<W: Write>(
1105        &self,
1106        span: &Span,
1107        wtr: W,
1108    ) -> Result<(), Error> {
1109        if self.hms {
1110            return self.print_span_hms(span, wtr);
1111        }
1112        self.print_span_designators(span, wtr)
1113    }
1114
1115    /// Print a `SignedDuration` to the given writer using the "friendly"
1116    /// format.
1117    ///
1118    /// This balances the units of the duration up to at most hours
1119    /// automatically.
1120    ///
1121    /// # Errors
1122    ///
1123    /// This only returns an error when writing to the given [`Write`]
1124    /// implementation would fail. Some such implementations, like for `String`
1125    /// and `Vec<u8>`, never fail (unless memory allocation fails). In such
1126    /// cases, it would be appropriate to call `unwrap()` on the result.
1127    ///
1128    /// # Example
1129    ///
1130    /// ```
1131    /// use jiff::{fmt::friendly::SpanPrinter, SignedDuration};
1132    ///
1133    /// static PRINTER: SpanPrinter = SpanPrinter::new();
1134    ///
1135    /// let dur = SignedDuration::new(86_525, 123_000_789);
1136    ///
1137    /// let mut buf = String::new();
1138    /// // Printing to a `String` can never fail.
1139    /// PRINTER.print_duration(&dur, &mut buf).unwrap();
1140    /// assert_eq!(buf, "24h 2m 5s 123ms 789ns");
1141    ///
1142    /// // Negative durations are supported.
1143    /// buf.clear();
1144    /// PRINTER.print_duration(&-dur, &mut buf).unwrap();
1145    /// assert_eq!(buf, "24h 2m 5s 123ms 789ns ago");
1146    /// ```
1147    pub fn print_duration<W: Write>(
1148        &self,
1149        duration: &SignedDuration,
1150        wtr: W,
1151    ) -> Result<(), Error> {
1152        if self.hms {
1153            return self.print_signed_duration_hms(duration, wtr);
1154        }
1155        self.print_signed_duration_designators(duration, wtr)
1156    }
1157
1158    /// Print a `std::time::Duration` to the given writer using the "friendly"
1159    /// format.
1160    ///
1161    /// This balances the units of the duration up to at most hours
1162    /// automatically.
1163    ///
1164    /// # Errors
1165    ///
1166    /// This only returns an error when writing to the given [`Write`]
1167    /// implementation would fail. Some such implementations, like for `String`
1168    /// and `Vec<u8>`, never fail (unless memory allocation fails). In such
1169    /// cases, it would be appropriate to call `unwrap()` on the result.
1170    ///
1171    /// # Example
1172    ///
1173    /// ```
1174    /// use std::time::Duration;
1175    ///
1176    /// use jiff::fmt::friendly::SpanPrinter;
1177    ///
1178    /// static PRINTER: SpanPrinter = SpanPrinter::new();
1179    ///
1180    /// let dur = Duration::new(86_525, 123_000_789);
1181    ///
1182    /// let mut buf = String::new();
1183    /// // Printing to a `String` can never fail.
1184    /// PRINTER.print_unsigned_duration(&dur, &mut buf).unwrap();
1185    /// assert_eq!(buf, "24h 2m 5s 123ms 789ns");
1186    /// ```
1187    pub fn print_unsigned_duration<W: Write>(
1188        &self,
1189        duration: &core::time::Duration,
1190        wtr: W,
1191    ) -> Result<(), Error> {
1192        if self.hms {
1193            return self.print_unsigned_duration_hms(duration, wtr);
1194        }
1195        self.print_unsigned_duration_designators(duration, wtr)
1196    }
1197
1198    fn print_span_designators<W: Write>(
1199        &self,
1200        span: &Span,
1201        mut wtr: W,
1202    ) -> Result<(), Error> {
1203        let mut wtr =
1204            DesignatorWriter::new(self, &mut wtr, false, span.signum());
1205        wtr.maybe_write_prefix_sign()?;
1206        match self.fractional {
1207            None => {
1208                self.print_span_designators_non_fraction(span, &mut wtr)?;
1209            }
1210            Some(unit) => {
1211                self.print_span_designators_fractional(span, unit, &mut wtr)?;
1212            }
1213        }
1214        wtr.maybe_write_zero()?;
1215        wtr.maybe_write_suffix_sign()?;
1216        Ok(())
1217    }
1218
1219    fn print_span_designators_non_fraction<'p, 'w, W: Write>(
1220        &self,
1221        span: &Span,
1222        wtr: &mut DesignatorWriter<'p, 'w, W>,
1223    ) -> Result<(), Error> {
1224        let span = span.abs();
1225        if span.get_years() != 0 {
1226            wtr.write(Unit::Year, span.get_years().unsigned_abs())?;
1227        }
1228        if span.get_months() != 0 {
1229            wtr.write(Unit::Month, span.get_months().unsigned_abs())?;
1230        }
1231        if span.get_weeks() != 0 {
1232            wtr.write(Unit::Week, span.get_weeks().unsigned_abs())?;
1233        }
1234        if span.get_days() != 0 {
1235            wtr.write(Unit::Day, span.get_days().unsigned_abs())?;
1236        }
1237        if span.get_hours() != 0 {
1238            wtr.write(Unit::Hour, span.get_hours().unsigned_abs())?;
1239        }
1240        if span.get_minutes() != 0 {
1241            wtr.write(Unit::Minute, span.get_minutes().unsigned_abs())?;
1242        }
1243        if span.get_seconds() != 0 {
1244            wtr.write(Unit::Second, span.get_seconds().unsigned_abs())?;
1245        }
1246        if span.get_milliseconds() != 0 {
1247            wtr.write(
1248                Unit::Millisecond,
1249                span.get_milliseconds().unsigned_abs(),
1250            )?;
1251        }
1252        if span.get_microseconds() != 0 {
1253            wtr.write(
1254                Unit::Microsecond,
1255                span.get_microseconds().unsigned_abs(),
1256            )?;
1257        }
1258        if span.get_nanoseconds() != 0 {
1259            wtr.write(
1260                Unit::Nanosecond,
1261                span.get_nanoseconds().unsigned_abs(),
1262            )?;
1263        }
1264        Ok(())
1265    }
1266
1267    #[inline(never)]
1268    fn print_span_designators_fractional<'p, 'w, W: Write>(
1269        &self,
1270        span: &Span,
1271        unit: FractionalUnit,
1272        wtr: &mut DesignatorWriter<'p, 'w, W>,
1273    ) -> Result<(), Error> {
1274        // OK because the biggest FractionalUnit is Hour, and there is always
1275        // a Unit bigger than hour.
1276        let split_at = Unit::from(unit).next().unwrap();
1277        let non_fractional = span.without_lower(split_at);
1278        let fractional = span.only_lower(split_at);
1279        self.print_span_designators_non_fraction(&non_fractional, wtr)?;
1280        wtr.write_fractional_duration(
1281            unit,
1282            &fractional.to_duration_invariant().unsigned_abs(),
1283        )?;
1284        Ok(())
1285    }
1286
1287    fn print_span_hms<W: Write>(
1288        &self,
1289        span: &Span,
1290        mut wtr: W,
1291    ) -> Result<(), Error> {
1292        let span_cal = span.only_calendar();
1293        let mut span_time = span.only_time();
1294        let has_cal = !span_cal.is_zero();
1295
1296        let mut wtr =
1297            DesignatorWriter::new(self, &mut wtr, has_cal, span.signum());
1298        wtr.maybe_write_prefix_sign()?;
1299        if has_cal {
1300            self.print_span_designators_non_fraction(&span_cal, &mut wtr)?;
1301            wtr.finish_preceding()?;
1302            // When spacing is disabled, then `finish_preceding` won't write
1303            // any spaces. But this would result in, e.g., `1yr15:00:00`, which
1304            // is just totally wrong. So detect that case here and insert a
1305            // space forcefully.
1306            if matches!(self.spacing, Spacing::None) {
1307                wtr.wtr.write_str(" ")?;
1308            }
1309        }
1310        span_time = span_time.abs();
1311
1312        let fmtint =
1313            DecimalFormatter::new().padding(self.padding.unwrap_or(2));
1314        let fmtfraction = FractionalFormatter::new().precision(self.precision);
1315        wtr.wtr.write_int(&fmtint, span_time.get_hours_ranged().get())?;
1316        wtr.wtr.write_str(":")?;
1317        wtr.wtr.write_int(&fmtint, span_time.get_minutes_ranged().get())?;
1318        wtr.wtr.write_str(":")?;
1319        let fp = FractionalPrinter::from_span(
1320            &span_time.only_lower(Unit::Minute),
1321            FractionalUnit::Second,
1322            fmtint,
1323            fmtfraction,
1324        );
1325        fp.print(&mut wtr.wtr)?;
1326        wtr.maybe_write_suffix_sign()?;
1327        Ok(())
1328    }
1329
1330    fn print_signed_duration_designators<W: Write>(
1331        &self,
1332        dur: &SignedDuration,
1333        mut wtr: W,
1334    ) -> Result<(), Error> {
1335        let mut wtr =
1336            DesignatorWriter::new(self, &mut wtr, false, dur.signum());
1337        wtr.maybe_write_prefix_sign()?;
1338        self.print_duration_designators(&dur.unsigned_abs(), &mut wtr)?;
1339        wtr.maybe_write_zero()?;
1340        wtr.maybe_write_suffix_sign()?;
1341        Ok(())
1342    }
1343
1344    fn print_unsigned_duration_designators<W: Write>(
1345        &self,
1346        dur: &core::time::Duration,
1347        mut wtr: W,
1348    ) -> Result<(), Error> {
1349        let mut wtr = DesignatorWriter::new(self, &mut wtr, false, 1);
1350        wtr.maybe_write_prefix_sign()?;
1351        self.print_duration_designators(dur, &mut wtr)?;
1352        wtr.maybe_write_zero()?;
1353        Ok(())
1354    }
1355
1356    fn print_duration_designators<W: Write>(
1357        &self,
1358        dur: &core::time::Duration,
1359        wtr: &mut DesignatorWriter<W>,
1360    ) -> Result<(), Error> {
1361        match self.fractional {
1362            None => {
1363                let mut secs = dur.as_secs();
1364                wtr.write(Unit::Hour, secs / SECS_PER_HOUR)?;
1365                secs %= MINS_PER_HOUR * SECS_PER_MIN;
1366                wtr.write(Unit::Minute, secs / SECS_PER_MIN)?;
1367                wtr.write(Unit::Second, secs % SECS_PER_MIN)?;
1368                let mut nanos = dur.subsec_nanos();
1369                wtr.write(Unit::Millisecond, nanos / NANOS_PER_MILLI)?;
1370                nanos %= NANOS_PER_MILLI;
1371                wtr.write(Unit::Microsecond, nanos / NANOS_PER_MICRO)?;
1372                wtr.write(Unit::Nanosecond, nanos % NANOS_PER_MICRO)?;
1373            }
1374            Some(FractionalUnit::Hour) => {
1375                wtr.write_fractional_duration(FractionalUnit::Hour, &dur)?;
1376            }
1377            Some(FractionalUnit::Minute) => {
1378                let mut secs = dur.as_secs();
1379                wtr.write(Unit::Hour, secs / SECS_PER_HOUR)?;
1380                secs %= MINS_PER_HOUR * SECS_PER_MIN;
1381
1382                let leftovers =
1383                    core::time::Duration::new(secs, dur.subsec_nanos());
1384                wtr.write_fractional_duration(
1385                    FractionalUnit::Minute,
1386                    &leftovers,
1387                )?;
1388            }
1389            Some(FractionalUnit::Second) => {
1390                let mut secs = dur.as_secs();
1391                wtr.write(Unit::Hour, secs / SECS_PER_HOUR)?;
1392                secs %= MINS_PER_HOUR * SECS_PER_MIN;
1393                wtr.write(Unit::Minute, secs / SECS_PER_MIN)?;
1394                secs %= SECS_PER_MIN;
1395
1396                let leftovers =
1397                    core::time::Duration::new(secs, dur.subsec_nanos());
1398                wtr.write_fractional_duration(
1399                    FractionalUnit::Second,
1400                    &leftovers,
1401                )?;
1402            }
1403            Some(FractionalUnit::Millisecond) => {
1404                let mut secs = dur.as_secs();
1405                wtr.write(Unit::Hour, secs / SECS_PER_HOUR)?;
1406                secs %= MINS_PER_HOUR * SECS_PER_MIN;
1407                wtr.write(Unit::Minute, secs / SECS_PER_MIN)?;
1408                wtr.write(Unit::Second, secs % SECS_PER_MIN)?;
1409
1410                let leftovers =
1411                    core::time::Duration::new(0, dur.subsec_nanos());
1412                wtr.write_fractional_duration(
1413                    FractionalUnit::Millisecond,
1414                    &leftovers,
1415                )?;
1416            }
1417            Some(FractionalUnit::Microsecond) => {
1418                let mut secs = dur.as_secs();
1419                wtr.write(Unit::Hour, secs / SECS_PER_HOUR)?;
1420                secs %= MINS_PER_HOUR * SECS_PER_MIN;
1421                wtr.write(Unit::Minute, secs / SECS_PER_MIN)?;
1422                wtr.write(Unit::Second, secs % SECS_PER_MIN)?;
1423                let mut nanos = dur.subsec_nanos();
1424                wtr.write(Unit::Millisecond, nanos / NANOS_PER_MILLI)?;
1425                nanos %= NANOS_PER_MILLI;
1426
1427                let leftovers = core::time::Duration::new(0, nanos);
1428                wtr.write_fractional_duration(
1429                    FractionalUnit::Microsecond,
1430                    &leftovers,
1431                )?;
1432            }
1433        }
1434        Ok(())
1435    }
1436
1437    fn print_signed_duration_hms<W: Write>(
1438        &self,
1439        dur: &SignedDuration,
1440        mut wtr: W,
1441    ) -> Result<(), Error> {
1442        if dur.is_negative() {
1443            if !matches!(self.direction, Direction::Suffix) {
1444                wtr.write_str("-")?;
1445            }
1446        } else if let Direction::ForceSign = self.direction {
1447            wtr.write_str("+")?;
1448        }
1449        self.print_duration_hms(&dur.unsigned_abs(), &mut wtr)?;
1450        if dur.is_negative() {
1451            if matches!(self.direction, Direction::Suffix) {
1452                wtr.write_str(" ago")?;
1453            }
1454        }
1455        Ok(())
1456    }
1457
1458    fn print_unsigned_duration_hms<W: Write>(
1459        &self,
1460        dur: &core::time::Duration,
1461        mut wtr: W,
1462    ) -> Result<(), Error> {
1463        if let Direction::ForceSign = self.direction {
1464            wtr.write_str("+")?;
1465        }
1466        self.print_duration_hms(dur, &mut wtr)?;
1467        Ok(())
1468    }
1469
1470    fn print_duration_hms<W: Write>(
1471        &self,
1472        udur: &core::time::Duration,
1473        mut wtr: W,
1474    ) -> Result<(), Error> {
1475        // N.B. It should be technically correct to convert a `SignedDuration`
1476        // (or `core::time::Duration`) to `Span` (since this process balances)
1477        // and then format the `Span` as-is. But this doesn't work because the
1478        // range of a `SignedDuration` (and `core::time::Duration`) is much
1479        // bigger.
1480
1481        let fmtint =
1482            DecimalFormatter::new().padding(self.padding.unwrap_or(2));
1483        let fmtfraction = FractionalFormatter::new().precision(self.precision);
1484
1485        let mut secs = udur.as_secs();
1486        // OK because guaranteed to be bigger than i64::MIN.
1487        let hours = secs / (MINS_PER_HOUR * SECS_PER_MIN);
1488        secs %= MINS_PER_HOUR * SECS_PER_MIN;
1489        // OK because guaranteed to be bigger than i64::MIN.
1490        let minutes = secs / SECS_PER_MIN;
1491        // OK because guaranteed to be bigger than i64::MIN.
1492        secs = secs % SECS_PER_MIN;
1493
1494        wtr.write_uint(&fmtint, hours)?;
1495        wtr.write_str(":")?;
1496        wtr.write_uint(&fmtint, minutes)?;
1497        wtr.write_str(":")?;
1498        let fp = FractionalPrinter::from_duration(
1499            // OK because -999_999_999 <= nanos <= 999_999_999 and secs < 60.
1500            &core::time::Duration::new(secs, udur.subsec_nanos()),
1501            FractionalUnit::Second,
1502            fmtint,
1503            fmtfraction,
1504        );
1505        fp.print(&mut wtr)?;
1506
1507        Ok(())
1508    }
1509}
1510
1511impl Default for SpanPrinter {
1512    fn default() -> SpanPrinter {
1513        SpanPrinter::new()
1514    }
1515}
1516
1517/// A type that represents the designator choice.
1518///
1519/// Basically, whether we want verbose, short or compact designators. This in
1520/// turn permits lookups based on `Unit`, which makes writing generic code for
1521/// writing designators a bit nicer and still fast.
1522#[derive(Debug)]
1523struct Designators {
1524    singular: &'static [&'static str],
1525    plural: &'static [&'static str],
1526}
1527
1528impl Designators {
1529    const VERBOSE_SINGULAR: &'static [&'static str] = &[
1530        "nanosecond",
1531        "microsecond",
1532        "millisecond",
1533        "second",
1534        "minute",
1535        "hour",
1536        "day",
1537        "week",
1538        "month",
1539        "year",
1540    ];
1541    const VERBOSE_PLURAL: &'static [&'static str] = &[
1542        "nanoseconds",
1543        "microseconds",
1544        "milliseconds",
1545        "seconds",
1546        "minutes",
1547        "hours",
1548        "days",
1549        "weeks",
1550        "months",
1551        "years",
1552    ];
1553
1554    const SHORT_SINGULAR: &'static [&'static str] =
1555        &["nsec", "µsec", "msec", "sec", "min", "hr", "day", "wk", "mo", "yr"];
1556    const SHORT_PLURAL: &'static [&'static str] = &[
1557        "nsecs", "µsecs", "msecs", "secs", "mins", "hrs", "days", "wks",
1558        "mos", "yrs",
1559    ];
1560
1561    const COMPACT: &'static [&'static str] =
1562        &["ns", "µs", "ms", "s", "m", "h", "d", "w", "mo", "y"];
1563
1564    const HUMAN_TIME_SINGULAR: &'static [&'static str] =
1565        &["ns", "us", "ms", "s", "m", "h", "d", "w", "month", "y"];
1566    const HUMAN_TIME_PLURAL: &'static [&'static str] =
1567        &["ns", "us", "ms", "s", "m", "h", "d", "w", "months", "y"];
1568
1569    fn new(config: Designator) -> Designators {
1570        match config {
1571            Designator::Verbose => Designators {
1572                singular: Designators::VERBOSE_SINGULAR,
1573                plural: Designators::VERBOSE_PLURAL,
1574            },
1575            Designator::Short => Designators {
1576                singular: Designators::SHORT_SINGULAR,
1577                plural: Designators::SHORT_PLURAL,
1578            },
1579            Designator::Compact => Designators {
1580                singular: Designators::COMPACT,
1581                plural: Designators::COMPACT,
1582            },
1583            Designator::HumanTime => Designators {
1584                singular: Designators::HUMAN_TIME_SINGULAR,
1585                plural: Designators::HUMAN_TIME_PLURAL,
1586            },
1587        }
1588    }
1589
1590    fn designator(&self, unit: impl Into<Unit>, plural: bool) -> &'static str {
1591        let unit = unit.into();
1592        let index = unit as usize;
1593        if plural {
1594            self.plural[index]
1595        } else {
1596            self.singular[index]
1597        }
1598    }
1599}
1600
1601/// An abstraction for writing the "designator" variant of the friendly format.
1602///
1603/// This takes care of computing some initial state and keeping track of some
1604/// mutable state that influences printing. For example, whether to write a
1605/// delimiter or not (one should only come after a unit that has been written).
1606#[derive(Debug)]
1607struct DesignatorWriter<'p, 'w, W> {
1608    printer: &'p SpanPrinter,
1609    wtr: &'w mut W,
1610    desig: Designators,
1611    sign: Option<DirectionSign>,
1612    fmtint: DecimalFormatter,
1613    fmtfraction: FractionalFormatter,
1614    written_non_zero_unit: bool,
1615}
1616
1617impl<'p, 'w, W: Write> DesignatorWriter<'p, 'w, W> {
1618    fn new(
1619        printer: &'p SpanPrinter,
1620        wtr: &'w mut W,
1621        has_calendar: bool,
1622        signum: i8,
1623    ) -> DesignatorWriter<'p, 'w, W> {
1624        let desig = Designators::new(printer.designator);
1625        let sign = printer.direction.sign(printer, has_calendar, signum);
1626        let fmtint =
1627            DecimalFormatter::new().padding(printer.padding.unwrap_or(0));
1628        let fmtfraction =
1629            FractionalFormatter::new().precision(printer.precision);
1630        DesignatorWriter {
1631            printer,
1632            wtr,
1633            desig,
1634            sign,
1635            fmtint,
1636            fmtfraction,
1637            written_non_zero_unit: false,
1638        }
1639    }
1640
1641    fn maybe_write_prefix_sign(&mut self) -> Result<(), Error> {
1642        if let Some(DirectionSign::Prefix(sign)) = self.sign {
1643            self.wtr.write_str(sign)?;
1644        }
1645        Ok(())
1646    }
1647
1648    fn maybe_write_suffix_sign(&mut self) -> Result<(), Error> {
1649        if let Some(DirectionSign::Suffix(sign)) = self.sign {
1650            self.wtr.write_str(sign)?;
1651        }
1652        Ok(())
1653    }
1654
1655    fn maybe_write_zero(&mut self) -> Result<(), Error> {
1656        if self.written_non_zero_unit {
1657            return Ok(());
1658        }
1659        // If a fractional unit is set, then we should use that unit
1660        // specifically to express "zero."
1661        let unit = self
1662            .printer
1663            .fractional
1664            .map(Unit::from)
1665            .unwrap_or(self.printer.zero_unit);
1666        self.wtr.write_uint(&self.fmtint, 0u32)?;
1667        self.wtr
1668            .write_str(self.printer.spacing.between_units_and_designators())?;
1669        self.wtr.write_str(self.desig.designator(unit, true))?;
1670        Ok(())
1671    }
1672
1673    fn write(
1674        &mut self,
1675        unit: Unit,
1676        value: impl Into<u64>,
1677    ) -> Result<(), Error> {
1678        let value = value.into();
1679        if value == 0 {
1680            return Ok(());
1681        }
1682        self.finish_preceding()?;
1683        self.written_non_zero_unit = true;
1684        self.wtr.write_uint(&self.fmtint, value)?;
1685        self.wtr
1686            .write_str(self.printer.spacing.between_units_and_designators())?;
1687        self.wtr.write_str(self.desig.designator(unit, value != 1))?;
1688        Ok(())
1689    }
1690
1691    fn write_fractional_duration(
1692        &mut self,
1693        unit: FractionalUnit,
1694        duration: &core::time::Duration,
1695    ) -> Result<(), Error> {
1696        let fp = FractionalPrinter::from_duration(
1697            duration,
1698            unit,
1699            self.fmtint,
1700            self.fmtfraction,
1701        );
1702        if !fp.must_write_digits() {
1703            return Ok(());
1704        }
1705        self.finish_preceding()?;
1706        self.written_non_zero_unit = true;
1707        fp.print(&mut *self.wtr)?;
1708        self.wtr
1709            .write_str(self.printer.spacing.between_units_and_designators())?;
1710        self.wtr.write_str(self.desig.designator(unit, fp.is_plural()))?;
1711        Ok(())
1712    }
1713
1714    fn finish_preceding(&mut self) -> Result<(), Error> {
1715        if self.written_non_zero_unit {
1716            if self.printer.comma_after_designator {
1717                self.wtr.write_str(",")?;
1718            }
1719            self.wtr.write_str(self.printer.spacing.between_units())?;
1720        }
1721        Ok(())
1722    }
1723}
1724
1725/// A printer for a fraction with an integer and fraction component.
1726///
1727/// This also includes the formatter for the integer component and the
1728/// formatter for the fractional component.
1729struct FractionalPrinter {
1730    integer: u64,
1731    fraction: u32,
1732    fmtint: DecimalFormatter,
1733    fmtfraction: FractionalFormatter,
1734}
1735
1736impl FractionalPrinter {
1737    /// Build a fractional printer for the `Span` given. This includes the `.`.
1738    ///
1739    /// Callers must ensure that all units greater than `FractionalUnit` are
1740    /// zero in the span given.
1741    ///
1742    /// Note that the printer returned only prints a fractional component
1743    /// if necessary. For example, if the fractional component is zero and
1744    /// precision is `None`, or if `precision` is `Some(0)`, then no fractional
1745    /// component will be emitted.
1746    fn from_span(
1747        span: &Span,
1748        unit: FractionalUnit,
1749        fmtint: DecimalFormatter,
1750        fmtfraction: FractionalFormatter,
1751    ) -> FractionalPrinter {
1752        debug_assert!(span.largest_unit() <= Unit::from(unit));
1753        let dur = span.to_duration_invariant().unsigned_abs();
1754        FractionalPrinter::from_duration(&dur, unit, fmtint, fmtfraction)
1755    }
1756
1757    /// Like `from_span`, but for `SignedDuration`.
1758    fn from_duration(
1759        dur: &core::time::Duration,
1760        unit: FractionalUnit,
1761        fmtint: DecimalFormatter,
1762        fmtfraction: FractionalFormatter,
1763    ) -> FractionalPrinter {
1764        match unit {
1765            FractionalUnit::Hour => {
1766                let integer = dur.as_secs() / SECS_PER_HOUR;
1767                let mut fraction = dur.as_nanos() % NANOS_PER_HOUR;
1768                // Drop precision since we're only allowed 9 decimal places.
1769                fraction /= u128::from(SECS_PER_HOUR);
1770                // OK because NANOS_PER_HOUR / SECS_PER_HOUR fits in a u32.
1771                let fraction = u32::try_from(fraction).unwrap();
1772                FractionalPrinter { integer, fraction, fmtint, fmtfraction }
1773            }
1774            FractionalUnit::Minute => {
1775                let integer = dur.as_secs() / SECS_PER_MIN;
1776                let mut fraction = dur.as_nanos() % NANOS_PER_MIN;
1777                // Drop precision since we're only allowed 9 decimal places.
1778                fraction /= u128::from(SECS_PER_MIN);
1779                // OK because NANOS_PER_MIN fits in an u32.
1780                let fraction = u32::try_from(fraction).unwrap();
1781                FractionalPrinter { integer, fraction, fmtint, fmtfraction }
1782            }
1783            FractionalUnit::Second => {
1784                let integer = dur.as_secs();
1785                let fraction = u32::from(dur.subsec_nanos());
1786                FractionalPrinter { integer, fraction, fmtint, fmtfraction }
1787            }
1788            FractionalUnit::Millisecond => {
1789                // Unwrap is OK, but this is subtle. For printing a
1790                // SignedDuration, as_millis() can never return anything
1791                // bigger than 1 second, because the duration given is reduced
1792                // in a balanced fashion before hitting this routine. But
1793                // for printing a Span, it can, since spans can be totally
1794                // unbalanced. But Spans have limits on their units such that
1795                // each will fit into an i64. So this is also okay in that case
1796                // too.
1797                let integer = u64::try_from(dur.as_millis()).unwrap();
1798                let fraction =
1799                    u32::from((dur.subsec_nanos() % NANOS_PER_MILLI) * 1_000);
1800                FractionalPrinter { integer, fraction, fmtint, fmtfraction }
1801            }
1802            FractionalUnit::Microsecond => {
1803                // Unwrap is OK, but this is subtle. For printing a
1804                // SignedDuration, as_millis() can never return anything
1805                // bigger than 1 second, because the duration given is reduced
1806                // in a balanced fashion before hitting this routine. But
1807                // for printing a Span, it can, since spans can be totally
1808                // unbalanced. But Spans have limits on their units such that
1809                // each will fit into an i64. So this is also okay in that case
1810                // too.
1811                let integer = u64::try_from(dur.as_micros()).unwrap();
1812                let fraction = u32::from(
1813                    (dur.subsec_nanos() % NANOS_PER_MICRO) * 1_000_000,
1814                );
1815                FractionalPrinter { integer, fraction, fmtint, fmtfraction }
1816            }
1817        }
1818    }
1819
1820    /// Returns true if both the integer and fractional component are zero.
1821    fn is_zero(&self) -> bool {
1822        self.integer == 0 && self.fraction == 0
1823    }
1824
1825    /// Returns true if this integer/fraction should be considered plural
1826    /// when choosing what designator to use.
1827    fn is_plural(&self) -> bool {
1828        self.integer != 1
1829            || (self.fraction != 0
1830                && !self.fmtfraction.has_zero_fixed_precision())
1831    }
1832
1833    /// Returns true if and only if this printer must write some kind of number
1834    /// when `print` is called.
1835    ///
1836    /// The only case where this returns `false` is when both the integer and
1837    /// fractional component are zero *and* the precision is fixed to a number
1838    /// greater than zero.
1839    fn must_write_digits(&self) -> bool {
1840        !self.is_zero() || self.fmtfraction.has_non_zero_fixed_precision()
1841    }
1842
1843    /// Prints the integer and optional fractional component.
1844    ///
1845    /// This will always print the integer, even if it's zero. Therefore, if
1846    /// the caller wants to omit printing zero, the caller should do their own
1847    /// conditional logic.
1848    fn print<W: Write>(&self, mut wtr: W) -> Result<(), Error> {
1849        wtr.write_uint(&self.fmtint, self.integer)?;
1850        if self.fmtfraction.will_write_digits(self.fraction) {
1851            wtr.write_str(".")?;
1852            wtr.write_fraction(&self.fmtfraction, self.fraction)?;
1853        }
1854        Ok(())
1855    }
1856}
1857
1858#[cfg(feature = "alloc")]
1859#[cfg(test)]
1860mod tests {
1861    use crate::ToSpan;
1862
1863    use super::*;
1864
1865    #[test]
1866    fn print_span_designator_default() {
1867        let printer = || SpanPrinter::new();
1868        let p = |span| printer().span_to_string(&span);
1869
1870        insta::assert_snapshot!(p(1.second()), @"1s");
1871        insta::assert_snapshot!(p(2.seconds()), @"2s");
1872        insta::assert_snapshot!(p(10.seconds()), @"10s");
1873        insta::assert_snapshot!(p(1.minute().seconds(40)), @"1m 40s");
1874
1875        insta::assert_snapshot!(p(1.minute()), @"1m");
1876        insta::assert_snapshot!(p(2.minutes()), @"2m");
1877        insta::assert_snapshot!(p(10.minutes()), @"10m");
1878        insta::assert_snapshot!(p(1.hour().minutes(40)), @"1h 40m");
1879
1880        insta::assert_snapshot!(p(1.hour()), @"1h");
1881        insta::assert_snapshot!(p(2.hours()), @"2h");
1882        insta::assert_snapshot!(p(10.hours()), @"10h");
1883        insta::assert_snapshot!(p(100.hours()), @"100h");
1884
1885        insta::assert_snapshot!(
1886            p(1.hour().minutes(1).seconds(1)),
1887            @"1h 1m 1s",
1888        );
1889        insta::assert_snapshot!(
1890            p(2.hours().minutes(2).seconds(2)),
1891            @"2h 2m 2s",
1892        );
1893        insta::assert_snapshot!(
1894            p(10.hours().minutes(10).seconds(10)),
1895            @"10h 10m 10s",
1896        );
1897        insta::assert_snapshot!(
1898            p(100.hours().minutes(100).seconds(100)),
1899            @"100h 100m 100s",
1900        );
1901
1902        insta::assert_snapshot!(p(-1.hour()), @"1h ago");
1903        insta::assert_snapshot!(p(-1.hour().seconds(30)), @"1h 30s ago");
1904
1905        insta::assert_snapshot!(
1906            p(1.second().milliseconds(2000)),
1907            @"1s 2000ms",
1908        );
1909    }
1910
1911    #[test]
1912    fn print_span_designator_verbose() {
1913        let printer = || SpanPrinter::new().designator(Designator::Verbose);
1914        let p = |span| printer().span_to_string(&span);
1915
1916        insta::assert_snapshot!(p(1.second()), @"1second");
1917        insta::assert_snapshot!(p(2.seconds()), @"2seconds");
1918        insta::assert_snapshot!(p(10.seconds()), @"10seconds");
1919        insta::assert_snapshot!(p(1.minute().seconds(40)), @"1minute 40seconds");
1920
1921        insta::assert_snapshot!(p(1.minute()), @"1minute");
1922        insta::assert_snapshot!(p(2.minutes()), @"2minutes");
1923        insta::assert_snapshot!(p(10.minutes()), @"10minutes");
1924        insta::assert_snapshot!(p(1.hour().minutes(40)), @"1hour 40minutes");
1925
1926        insta::assert_snapshot!(p(1.hour()), @"1hour");
1927        insta::assert_snapshot!(p(2.hours()), @"2hours");
1928        insta::assert_snapshot!(p(10.hours()), @"10hours");
1929        insta::assert_snapshot!(p(100.hours()), @"100hours");
1930
1931        insta::assert_snapshot!(
1932            p(1.hour().minutes(1).seconds(1)),
1933            @"1hour 1minute 1second",
1934        );
1935        insta::assert_snapshot!(
1936            p(2.hours().minutes(2).seconds(2)),
1937            @"2hours 2minutes 2seconds",
1938        );
1939        insta::assert_snapshot!(
1940            p(10.hours().minutes(10).seconds(10)),
1941            @"10hours 10minutes 10seconds",
1942        );
1943        insta::assert_snapshot!(
1944            p(100.hours().minutes(100).seconds(100)),
1945            @"100hours 100minutes 100seconds",
1946        );
1947
1948        insta::assert_snapshot!(p(-1.hour()), @"1hour ago");
1949        insta::assert_snapshot!(p(-1.hour().seconds(30)), @"1hour 30seconds ago");
1950    }
1951
1952    #[test]
1953    fn print_span_designator_short() {
1954        let printer = || SpanPrinter::new().designator(Designator::Short);
1955        let p = |span| printer().span_to_string(&span);
1956
1957        insta::assert_snapshot!(p(1.second()), @"1sec");
1958        insta::assert_snapshot!(p(2.seconds()), @"2secs");
1959        insta::assert_snapshot!(p(10.seconds()), @"10secs");
1960        insta::assert_snapshot!(p(1.minute().seconds(40)), @"1min 40secs");
1961
1962        insta::assert_snapshot!(p(1.minute()), @"1min");
1963        insta::assert_snapshot!(p(2.minutes()), @"2mins");
1964        insta::assert_snapshot!(p(10.minutes()), @"10mins");
1965        insta::assert_snapshot!(p(1.hour().minutes(40)), @"1hr 40mins");
1966
1967        insta::assert_snapshot!(p(1.hour()), @"1hr");
1968        insta::assert_snapshot!(p(2.hours()), @"2hrs");
1969        insta::assert_snapshot!(p(10.hours()), @"10hrs");
1970        insta::assert_snapshot!(p(100.hours()), @"100hrs");
1971
1972        insta::assert_snapshot!(
1973            p(1.hour().minutes(1).seconds(1)),
1974            @"1hr 1min 1sec",
1975        );
1976        insta::assert_snapshot!(
1977            p(2.hours().minutes(2).seconds(2)),
1978            @"2hrs 2mins 2secs",
1979        );
1980        insta::assert_snapshot!(
1981            p(10.hours().minutes(10).seconds(10)),
1982            @"10hrs 10mins 10secs",
1983        );
1984        insta::assert_snapshot!(
1985            p(100.hours().minutes(100).seconds(100)),
1986            @"100hrs 100mins 100secs",
1987        );
1988
1989        insta::assert_snapshot!(p(-1.hour()), @"1hr ago");
1990        insta::assert_snapshot!(p(-1.hour().seconds(30)), @"1hr 30secs ago");
1991    }
1992
1993    #[test]
1994    fn print_span_designator_compact() {
1995        let printer = || SpanPrinter::new().designator(Designator::Compact);
1996        let p = |span| printer().span_to_string(&span);
1997
1998        insta::assert_snapshot!(p(1.second()), @"1s");
1999        insta::assert_snapshot!(p(2.seconds()), @"2s");
2000        insta::assert_snapshot!(p(10.seconds()), @"10s");
2001        insta::assert_snapshot!(p(1.minute().seconds(40)), @"1m 40s");
2002
2003        insta::assert_snapshot!(p(1.minute()), @"1m");
2004        insta::assert_snapshot!(p(2.minutes()), @"2m");
2005        insta::assert_snapshot!(p(10.minutes()), @"10m");
2006        insta::assert_snapshot!(p(1.hour().minutes(40)), @"1h 40m");
2007
2008        insta::assert_snapshot!(p(1.hour()), @"1h");
2009        insta::assert_snapshot!(p(2.hours()), @"2h");
2010        insta::assert_snapshot!(p(10.hours()), @"10h");
2011        insta::assert_snapshot!(p(100.hours()), @"100h");
2012
2013        insta::assert_snapshot!(
2014            p(1.hour().minutes(1).seconds(1)),
2015            @"1h 1m 1s",
2016        );
2017        insta::assert_snapshot!(
2018            p(2.hours().minutes(2).seconds(2)),
2019            @"2h 2m 2s",
2020        );
2021        insta::assert_snapshot!(
2022            p(10.hours().minutes(10).seconds(10)),
2023            @"10h 10m 10s",
2024        );
2025        insta::assert_snapshot!(
2026            p(100.hours().minutes(100).seconds(100)),
2027            @"100h 100m 100s",
2028        );
2029
2030        insta::assert_snapshot!(p(-1.hour()), @"1h ago");
2031        insta::assert_snapshot!(p(-1.hour().seconds(30)), @"1h 30s ago");
2032    }
2033
2034    #[test]
2035    fn print_span_designator_direction_force() {
2036        let printer = || SpanPrinter::new().direction(Direction::ForceSign);
2037        let p = |span| printer().span_to_string(&span);
2038
2039        insta::assert_snapshot!(p(1.second()), @"+1s");
2040        insta::assert_snapshot!(p(2.seconds()), @"+2s");
2041        insta::assert_snapshot!(p(10.seconds()), @"+10s");
2042        insta::assert_snapshot!(p(1.minute().seconds(40)), @"+1m 40s");
2043
2044        insta::assert_snapshot!(p(1.minute()), @"+1m");
2045        insta::assert_snapshot!(p(2.minutes()), @"+2m");
2046        insta::assert_snapshot!(p(10.minutes()), @"+10m");
2047        insta::assert_snapshot!(p(1.hour().minutes(40)), @"+1h 40m");
2048
2049        insta::assert_snapshot!(p(1.hour()), @"+1h");
2050        insta::assert_snapshot!(p(2.hours()), @"+2h");
2051        insta::assert_snapshot!(p(10.hours()), @"+10h");
2052        insta::assert_snapshot!(p(100.hours()), @"+100h");
2053
2054        insta::assert_snapshot!(
2055            p(1.hour().minutes(1).seconds(1)),
2056            @"+1h 1m 1s",
2057        );
2058        insta::assert_snapshot!(
2059            p(2.hours().minutes(2).seconds(2)),
2060            @"+2h 2m 2s",
2061        );
2062        insta::assert_snapshot!(
2063            p(10.hours().minutes(10).seconds(10)),
2064            @"+10h 10m 10s",
2065        );
2066        insta::assert_snapshot!(
2067            p(100.hours().minutes(100).seconds(100)),
2068            @"+100h 100m 100s",
2069        );
2070
2071        insta::assert_snapshot!(p(-1.hour()), @"-1h");
2072        insta::assert_snapshot!(p(-1.hour().seconds(30)), @"-1h 30s");
2073    }
2074
2075    #[test]
2076    fn print_span_designator_padding() {
2077        let printer = || SpanPrinter::new().padding(2);
2078        let p = |span| printer().span_to_string(&span);
2079
2080        insta::assert_snapshot!(p(1.second()), @"01s");
2081        insta::assert_snapshot!(p(2.seconds()), @"02s");
2082        insta::assert_snapshot!(p(10.seconds()), @"10s");
2083        insta::assert_snapshot!(p(1.minute().seconds(40)), @"01m 40s");
2084
2085        insta::assert_snapshot!(p(1.minute()), @"01m");
2086        insta::assert_snapshot!(p(2.minutes()), @"02m");
2087        insta::assert_snapshot!(p(10.minutes()), @"10m");
2088        insta::assert_snapshot!(p(1.hour().minutes(40)), @"01h 40m");
2089
2090        insta::assert_snapshot!(p(1.hour()), @"01h");
2091        insta::assert_snapshot!(p(2.hours()), @"02h");
2092        insta::assert_snapshot!(p(10.hours()), @"10h");
2093        insta::assert_snapshot!(p(100.hours()), @"100h");
2094
2095        insta::assert_snapshot!(
2096            p(1.hour().minutes(1).seconds(1)),
2097            @"01h 01m 01s",
2098        );
2099        insta::assert_snapshot!(
2100            p(2.hours().minutes(2).seconds(2)),
2101            @"02h 02m 02s",
2102        );
2103        insta::assert_snapshot!(
2104            p(10.hours().minutes(10).seconds(10)),
2105            @"10h 10m 10s",
2106        );
2107        insta::assert_snapshot!(
2108            p(100.hours().minutes(100).seconds(100)),
2109            @"100h 100m 100s",
2110        );
2111
2112        insta::assert_snapshot!(p(-1.hour()), @"01h ago");
2113        insta::assert_snapshot!(p(-1.hour().seconds(30)), @"01h 30s ago");
2114    }
2115
2116    #[test]
2117    fn print_span_designator_spacing_none() {
2118        let printer = || SpanPrinter::new().spacing(Spacing::None);
2119        let p = |span| printer().span_to_string(&span);
2120
2121        insta::assert_snapshot!(p(1.second()), @"1s");
2122        insta::assert_snapshot!(p(2.seconds()), @"2s");
2123        insta::assert_snapshot!(p(10.seconds()), @"10s");
2124        insta::assert_snapshot!(p(1.minute().seconds(40)), @"1m40s");
2125
2126        insta::assert_snapshot!(p(1.minute()), @"1m");
2127        insta::assert_snapshot!(p(2.minutes()), @"2m");
2128        insta::assert_snapshot!(p(10.minutes()), @"10m");
2129        insta::assert_snapshot!(p(1.hour().minutes(40)), @"1h40m");
2130
2131        insta::assert_snapshot!(p(1.hour()), @"1h");
2132        insta::assert_snapshot!(p(2.hours()), @"2h");
2133        insta::assert_snapshot!(p(10.hours()), @"10h");
2134        insta::assert_snapshot!(p(100.hours()), @"100h");
2135
2136        insta::assert_snapshot!(
2137            p(1.hour().minutes(1).seconds(1)),
2138            @"1h1m1s",
2139        );
2140        insta::assert_snapshot!(
2141            p(2.hours().minutes(2).seconds(2)),
2142            @"2h2m2s",
2143        );
2144        insta::assert_snapshot!(
2145            p(10.hours().minutes(10).seconds(10)),
2146            @"10h10m10s",
2147        );
2148        insta::assert_snapshot!(
2149            p(100.hours().minutes(100).seconds(100)),
2150            @"100h100m100s",
2151        );
2152
2153        insta::assert_snapshot!(p(-1.hour()), @"-1h");
2154        insta::assert_snapshot!(p(-1.hour().seconds(30)), @"-1h30s");
2155    }
2156
2157    #[test]
2158    fn print_span_designator_spacing_more() {
2159        let printer =
2160            || SpanPrinter::new().spacing(Spacing::BetweenUnitsAndDesignators);
2161        let p = |span| printer().span_to_string(&span);
2162
2163        insta::assert_snapshot!(p(1.second()), @"1 s");
2164        insta::assert_snapshot!(p(2.seconds()), @"2 s");
2165        insta::assert_snapshot!(p(10.seconds()), @"10 s");
2166        insta::assert_snapshot!(p(1.minute().seconds(40)), @"1 m 40 s");
2167
2168        insta::assert_snapshot!(p(1.minute()), @"1 m");
2169        insta::assert_snapshot!(p(2.minutes()), @"2 m");
2170        insta::assert_snapshot!(p(10.minutes()), @"10 m");
2171        insta::assert_snapshot!(p(1.hour().minutes(40)), @"1 h 40 m");
2172
2173        insta::assert_snapshot!(p(1.hour()), @"1 h");
2174        insta::assert_snapshot!(p(2.hours()), @"2 h");
2175        insta::assert_snapshot!(p(10.hours()), @"10 h");
2176        insta::assert_snapshot!(p(100.hours()), @"100 h");
2177
2178        insta::assert_snapshot!(
2179            p(1.hour().minutes(1).seconds(1)),
2180            @"1 h 1 m 1 s",
2181        );
2182        insta::assert_snapshot!(
2183            p(2.hours().minutes(2).seconds(2)),
2184            @"2 h 2 m 2 s",
2185        );
2186        insta::assert_snapshot!(
2187            p(10.hours().minutes(10).seconds(10)),
2188            @"10 h 10 m 10 s",
2189        );
2190        insta::assert_snapshot!(
2191            p(100.hours().minutes(100).seconds(100)),
2192            @"100 h 100 m 100 s",
2193        );
2194
2195        insta::assert_snapshot!(p(-1.hour()), @"1 h ago");
2196        insta::assert_snapshot!(p(-1.hour().seconds(30)), @"1 h 30 s ago");
2197    }
2198
2199    #[test]
2200    fn print_span_designator_spacing_comma() {
2201        let printer = || {
2202            SpanPrinter::new()
2203                .comma_after_designator(true)
2204                .spacing(Spacing::BetweenUnitsAndDesignators)
2205        };
2206        let p = |span| printer().span_to_string(&span);
2207
2208        insta::assert_snapshot!(p(1.second()), @"1 s");
2209        insta::assert_snapshot!(p(2.seconds()), @"2 s");
2210        insta::assert_snapshot!(p(10.seconds()), @"10 s");
2211        insta::assert_snapshot!(p(1.minute().seconds(40)), @"1 m, 40 s");
2212
2213        insta::assert_snapshot!(p(1.minute()), @"1 m");
2214        insta::assert_snapshot!(p(2.minutes()), @"2 m");
2215        insta::assert_snapshot!(p(10.minutes()), @"10 m");
2216        insta::assert_snapshot!(p(1.hour().minutes(40)), @"1 h, 40 m");
2217
2218        insta::assert_snapshot!(p(1.hour()), @"1 h");
2219        insta::assert_snapshot!(p(2.hours()), @"2 h");
2220        insta::assert_snapshot!(p(10.hours()), @"10 h");
2221        insta::assert_snapshot!(p(100.hours()), @"100 h");
2222
2223        insta::assert_snapshot!(
2224            p(1.hour().minutes(1).seconds(1)),
2225            @"1 h, 1 m, 1 s",
2226        );
2227        insta::assert_snapshot!(
2228            p(2.hours().minutes(2).seconds(2)),
2229            @"2 h, 2 m, 2 s",
2230        );
2231        insta::assert_snapshot!(
2232            p(10.hours().minutes(10).seconds(10)),
2233            @"10 h, 10 m, 10 s",
2234        );
2235        insta::assert_snapshot!(
2236            p(100.hours().minutes(100).seconds(100)),
2237            @"100 h, 100 m, 100 s",
2238        );
2239
2240        insta::assert_snapshot!(p(-1.hour()), @"1 h ago");
2241        insta::assert_snapshot!(p(-1.hour().seconds(30)), @"1 h, 30 s ago");
2242    }
2243
2244    #[test]
2245    fn print_span_designator_fractional_hour() {
2246        let printer =
2247            || SpanPrinter::new().fractional(Some(FractionalUnit::Hour));
2248        let p = |span| printer().span_to_string(&span);
2249        let pp = |precision, span| {
2250            printer().precision(Some(precision)).span_to_string(&span)
2251        };
2252
2253        insta::assert_snapshot!(p(1.hour()), @"1h");
2254        insta::assert_snapshot!(pp(0, 1.hour()), @"1h");
2255        insta::assert_snapshot!(pp(1, 1.hour()), @"1.0h");
2256        insta::assert_snapshot!(pp(2, 1.hour()), @"1.00h");
2257
2258        insta::assert_snapshot!(p(1.hour().minutes(30)), @"1.5h");
2259        insta::assert_snapshot!(pp(0, 1.hour().minutes(30)), @"1h");
2260        insta::assert_snapshot!(pp(1, 1.hour().minutes(30)), @"1.5h");
2261        insta::assert_snapshot!(pp(2, 1.hour().minutes(30)), @"1.50h");
2262
2263        insta::assert_snapshot!(p(1.hour().minutes(3)), @"1.05h");
2264        insta::assert_snapshot!(p(1.hour().minutes(3).nanoseconds(1)), @"1.05h");
2265        insta::assert_snapshot!(p(1.second()), @"0.000277777h");
2266        // precision loss!
2267        insta::assert_snapshot!(p(1.second().nanoseconds(1)), @"0.000277777h");
2268        insta::assert_snapshot!(p(0.seconds()), @"0h");
2269        // precision loss!
2270        insta::assert_snapshot!(p(1.nanosecond()), @"0h");
2271    }
2272
2273    #[test]
2274    fn print_span_designator_fractional_minute() {
2275        let printer =
2276            || SpanPrinter::new().fractional(Some(FractionalUnit::Minute));
2277        let p = |span| printer().span_to_string(&span);
2278        let pp = |precision, span| {
2279            printer().precision(Some(precision)).span_to_string(&span)
2280        };
2281
2282        insta::assert_snapshot!(p(1.hour()), @"1h");
2283        insta::assert_snapshot!(p(1.hour().minutes(30)), @"1h 30m");
2284
2285        insta::assert_snapshot!(p(1.minute()), @"1m");
2286        insta::assert_snapshot!(pp(0, 1.minute()), @"1m");
2287        insta::assert_snapshot!(pp(1, 1.minute()), @"1.0m");
2288        insta::assert_snapshot!(pp(2, 1.minute()), @"1.00m");
2289
2290        insta::assert_snapshot!(p(1.minute().seconds(30)), @"1.5m");
2291        insta::assert_snapshot!(pp(0, 1.minute().seconds(30)), @"1m");
2292        insta::assert_snapshot!(pp(1, 1.minute().seconds(30)), @"1.5m");
2293        insta::assert_snapshot!(pp(2, 1.minute().seconds(30)), @"1.50m");
2294
2295        insta::assert_snapshot!(p(1.hour().nanoseconds(1)), @"1h");
2296        insta::assert_snapshot!(p(1.minute().seconds(3)), @"1.05m");
2297        insta::assert_snapshot!(p(1.minute().seconds(3).nanoseconds(1)), @"1.05m");
2298        insta::assert_snapshot!(p(1.second()), @"0.016666666m");
2299        // precision loss!
2300        insta::assert_snapshot!(p(1.second().nanoseconds(1)), @"0.016666666m");
2301        insta::assert_snapshot!(p(0.seconds()), @"0m");
2302        // precision loss!
2303        insta::assert_snapshot!(p(1.nanosecond()), @"0m");
2304    }
2305
2306    #[test]
2307    fn print_span_designator_fractional_second() {
2308        let printer =
2309            || SpanPrinter::new().fractional(Some(FractionalUnit::Second));
2310        let p = |span| printer().span_to_string(&span);
2311        let pp = |precision, span| {
2312            printer().precision(Some(precision)).span_to_string(&span)
2313        };
2314
2315        insta::assert_snapshot!(p(1.hour()), @"1h");
2316        insta::assert_snapshot!(p(1.hour().minutes(30)), @"1h 30m");
2317
2318        insta::assert_snapshot!(p(1.second()), @"1s");
2319        insta::assert_snapshot!(pp(0, 1.second()), @"1s");
2320        insta::assert_snapshot!(pp(1, 1.second()), @"1.0s");
2321        insta::assert_snapshot!(pp(2, 1.second()), @"1.00s");
2322
2323        insta::assert_snapshot!(p(1.second().milliseconds(500)), @"1.5s");
2324        insta::assert_snapshot!(pp(0, 1.second().milliseconds(500)), @"1s");
2325        insta::assert_snapshot!(pp(1, 1.second().milliseconds(500)), @"1.5s");
2326        insta::assert_snapshot!(pp(2, 1.second().milliseconds(500)), @"1.50s");
2327
2328        insta::assert_snapshot!(p(1.second().nanoseconds(1)), @"1.000000001s");
2329        insta::assert_snapshot!(p(1.nanosecond()), @"0.000000001s");
2330        insta::assert_snapshot!(p(0.seconds()), @"0s");
2331
2332        insta::assert_snapshot!(p(1.second().milliseconds(2000)), @"3s");
2333    }
2334
2335    #[test]
2336    fn print_span_designator_fractional_millisecond() {
2337        let printer = || {
2338            SpanPrinter::new().fractional(Some(FractionalUnit::Millisecond))
2339        };
2340        let p = |span| printer().span_to_string(&span);
2341        let pp = |precision, span| {
2342            printer().precision(Some(precision)).span_to_string(&span)
2343        };
2344
2345        insta::assert_snapshot!(p(1.hour()), @"1h");
2346        insta::assert_snapshot!(p(1.hour().minutes(30)), @"1h 30m");
2347        insta::assert_snapshot!(
2348            p(1.hour().minutes(30).seconds(10)),
2349            @"1h 30m 10s",
2350        );
2351
2352        insta::assert_snapshot!(p(1.second()), @"1s");
2353        insta::assert_snapshot!(pp(0, 1.second()), @"1s");
2354        insta::assert_snapshot!(pp(1, 1.second()), @"1s 0.0ms");
2355        insta::assert_snapshot!(pp(2, 1.second()), @"1s 0.00ms");
2356
2357        insta::assert_snapshot!(p(1.second().milliseconds(500)), @"1s 500ms");
2358        insta::assert_snapshot!(
2359            pp(0, 1.second().milliseconds(1).microseconds(500)),
2360            @"1s 1ms",
2361        );
2362        insta::assert_snapshot!(
2363            pp(1, 1.second().milliseconds(1).microseconds(500)),
2364            @"1s 1.5ms",
2365        );
2366        insta::assert_snapshot!(
2367            pp(2, 1.second().milliseconds(1).microseconds(500)),
2368            @"1s 1.50ms",
2369        );
2370
2371        insta::assert_snapshot!(p(1.millisecond().nanoseconds(1)), @"1.000001ms");
2372        insta::assert_snapshot!(p(1.nanosecond()), @"0.000001ms");
2373        insta::assert_snapshot!(p(0.seconds()), @"0ms");
2374    }
2375
2376    #[test]
2377    fn print_span_designator_fractional_microsecond() {
2378        let printer = || {
2379            SpanPrinter::new().fractional(Some(FractionalUnit::Microsecond))
2380        };
2381        let p = |span| printer().span_to_string(&span);
2382        let pp = |precision, span| {
2383            printer().precision(Some(precision)).span_to_string(&span)
2384        };
2385
2386        insta::assert_snapshot!(p(1.hour()), @"1h");
2387        insta::assert_snapshot!(p(1.hour().minutes(30)), @"1h 30m");
2388        insta::assert_snapshot!(
2389            p(1.hour().minutes(30).seconds(10)),
2390            @"1h 30m 10s",
2391        );
2392
2393        insta::assert_snapshot!(p(1.second()), @"1s");
2394        insta::assert_snapshot!(pp(0, 1.second()), @"1s");
2395        insta::assert_snapshot!(pp(1, 1.second()), @"1s 0.0µs");
2396        insta::assert_snapshot!(pp(2, 1.second()), @"1s 0.00µs");
2397
2398        insta::assert_snapshot!(p(1.second().milliseconds(500)), @"1s 500ms");
2399        insta::assert_snapshot!(
2400            pp(0, 1.second().milliseconds(1).microseconds(500)),
2401            @"1s 1ms 500µs",
2402        );
2403        insta::assert_snapshot!(
2404            pp(1, 1.second().milliseconds(1).microseconds(500)),
2405            @"1s 1ms 500.0µs",
2406        );
2407        insta::assert_snapshot!(
2408            pp(2, 1.second().milliseconds(1).microseconds(500)),
2409            @"1s 1ms 500.00µs",
2410        );
2411
2412        insta::assert_snapshot!(
2413            p(1.millisecond().nanoseconds(1)),
2414            @"1ms 0.001µs",
2415        );
2416        insta::assert_snapshot!(p(1.nanosecond()), @"0.001µs");
2417        insta::assert_snapshot!(p(0.second()), @"0µs");
2418    }
2419
2420    #[test]
2421    fn print_signed_duration_designator_default() {
2422        let printer = || SpanPrinter::new();
2423        let p = |secs| {
2424            printer().duration_to_string(&SignedDuration::from_secs(secs))
2425        };
2426
2427        insta::assert_snapshot!(p(1), @"1s");
2428        insta::assert_snapshot!(p(2), @"2s");
2429        insta::assert_snapshot!(p(10), @"10s");
2430        insta::assert_snapshot!(p(100), @"1m 40s");
2431
2432        insta::assert_snapshot!(p(1 * 60), @"1m");
2433        insta::assert_snapshot!(p(2 * 60), @"2m");
2434        insta::assert_snapshot!(p(10 * 60), @"10m");
2435        insta::assert_snapshot!(p(100 * 60), @"1h 40m");
2436
2437        insta::assert_snapshot!(p(1 * 60 * 60), @"1h");
2438        insta::assert_snapshot!(p(2 * 60 * 60), @"2h");
2439        insta::assert_snapshot!(p(10 * 60 * 60), @"10h");
2440        insta::assert_snapshot!(p(100 * 60 * 60), @"100h");
2441
2442        insta::assert_snapshot!(
2443            p(60 * 60 + 60 + 1),
2444            @"1h 1m 1s",
2445        );
2446        insta::assert_snapshot!(
2447            p(2 * 60 * 60 + 2 * 60 + 2),
2448            @"2h 2m 2s",
2449        );
2450        insta::assert_snapshot!(
2451            p(10 * 60 * 60 + 10 * 60 + 10),
2452            @"10h 10m 10s",
2453        );
2454        insta::assert_snapshot!(
2455            p(100 * 60 * 60 + 100 * 60 + 100),
2456            @"101h 41m 40s",
2457        );
2458
2459        insta::assert_snapshot!(p(-1 * 60 * 60), @"1h ago");
2460        insta::assert_snapshot!(p(-1 * 60 * 60 - 30), @"1h 30s ago");
2461    }
2462
2463    #[test]
2464    fn print_signed_duration_designator_verbose() {
2465        let printer = || SpanPrinter::new().designator(Designator::Verbose);
2466        let p = |secs| {
2467            printer().duration_to_string(&SignedDuration::from_secs(secs))
2468        };
2469
2470        insta::assert_snapshot!(p(1), @"1second");
2471        insta::assert_snapshot!(p(2), @"2seconds");
2472        insta::assert_snapshot!(p(10), @"10seconds");
2473        insta::assert_snapshot!(p(100), @"1minute 40seconds");
2474
2475        insta::assert_snapshot!(p(1 * 60), @"1minute");
2476        insta::assert_snapshot!(p(2 * 60), @"2minutes");
2477        insta::assert_snapshot!(p(10 * 60), @"10minutes");
2478        insta::assert_snapshot!(p(100 * 60), @"1hour 40minutes");
2479
2480        insta::assert_snapshot!(p(1 * 60 * 60), @"1hour");
2481        insta::assert_snapshot!(p(2 * 60 * 60), @"2hours");
2482        insta::assert_snapshot!(p(10 * 60 * 60), @"10hours");
2483        insta::assert_snapshot!(p(100 * 60 * 60), @"100hours");
2484
2485        insta::assert_snapshot!(
2486            p(60 * 60 + 60 + 1),
2487            @"1hour 1minute 1second",
2488        );
2489        insta::assert_snapshot!(
2490            p(2 * 60 * 60 + 2 * 60 + 2),
2491            @"2hours 2minutes 2seconds",
2492        );
2493        insta::assert_snapshot!(
2494            p(10 * 60 * 60 + 10 * 60 + 10),
2495            @"10hours 10minutes 10seconds",
2496        );
2497        insta::assert_snapshot!(
2498            p(100 * 60 * 60 + 100 * 60 + 100),
2499            @"101hours 41minutes 40seconds",
2500        );
2501
2502        insta::assert_snapshot!(p(-1 * 60 * 60), @"1hour ago");
2503        insta::assert_snapshot!(p(-1 * 60 * 60 - 30), @"1hour 30seconds ago");
2504    }
2505
2506    #[test]
2507    fn print_signed_duration_designator_short() {
2508        let printer = || SpanPrinter::new().designator(Designator::Short);
2509        let p = |secs| {
2510            printer().duration_to_string(&SignedDuration::from_secs(secs))
2511        };
2512
2513        insta::assert_snapshot!(p(1), @"1sec");
2514        insta::assert_snapshot!(p(2), @"2secs");
2515        insta::assert_snapshot!(p(10), @"10secs");
2516        insta::assert_snapshot!(p(100), @"1min 40secs");
2517
2518        insta::assert_snapshot!(p(1 * 60), @"1min");
2519        insta::assert_snapshot!(p(2 * 60), @"2mins");
2520        insta::assert_snapshot!(p(10 * 60), @"10mins");
2521        insta::assert_snapshot!(p(100 * 60), @"1hr 40mins");
2522
2523        insta::assert_snapshot!(p(1 * 60 * 60), @"1hr");
2524        insta::assert_snapshot!(p(2 * 60 * 60), @"2hrs");
2525        insta::assert_snapshot!(p(10 * 60 * 60), @"10hrs");
2526        insta::assert_snapshot!(p(100 * 60 * 60), @"100hrs");
2527
2528        insta::assert_snapshot!(
2529            p(60 * 60 + 60 + 1),
2530            @"1hr 1min 1sec",
2531        );
2532        insta::assert_snapshot!(
2533            p(2 * 60 * 60 + 2 * 60 + 2),
2534            @"2hrs 2mins 2secs",
2535        );
2536        insta::assert_snapshot!(
2537            p(10 * 60 * 60 + 10 * 60 + 10),
2538            @"10hrs 10mins 10secs",
2539        );
2540        insta::assert_snapshot!(
2541            p(100 * 60 * 60 + 100 * 60 + 100),
2542            @"101hrs 41mins 40secs",
2543        );
2544
2545        insta::assert_snapshot!(p(-1 * 60 * 60), @"1hr ago");
2546        insta::assert_snapshot!(p(-1 * 60 * 60 - 30), @"1hr 30secs ago");
2547    }
2548
2549    #[test]
2550    fn print_signed_duration_designator_compact() {
2551        let printer = || SpanPrinter::new().designator(Designator::Compact);
2552        let p = |secs| {
2553            printer().duration_to_string(&SignedDuration::from_secs(secs))
2554        };
2555
2556        insta::assert_snapshot!(p(1), @"1s");
2557        insta::assert_snapshot!(p(2), @"2s");
2558        insta::assert_snapshot!(p(10), @"10s");
2559        insta::assert_snapshot!(p(100), @"1m 40s");
2560
2561        insta::assert_snapshot!(p(1 * 60), @"1m");
2562        insta::assert_snapshot!(p(2 * 60), @"2m");
2563        insta::assert_snapshot!(p(10 * 60), @"10m");
2564        insta::assert_snapshot!(p(100 * 60), @"1h 40m");
2565
2566        insta::assert_snapshot!(p(1 * 60 * 60), @"1h");
2567        insta::assert_snapshot!(p(2 * 60 * 60), @"2h");
2568        insta::assert_snapshot!(p(10 * 60 * 60), @"10h");
2569        insta::assert_snapshot!(p(100 * 60 * 60), @"100h");
2570
2571        insta::assert_snapshot!(
2572            p(60 * 60 + 60 + 1),
2573            @"1h 1m 1s",
2574        );
2575        insta::assert_snapshot!(
2576            p(2 * 60 * 60 + 2 * 60 + 2),
2577            @"2h 2m 2s",
2578        );
2579        insta::assert_snapshot!(
2580            p(10 * 60 * 60 + 10 * 60 + 10),
2581            @"10h 10m 10s",
2582        );
2583        insta::assert_snapshot!(
2584            p(100 * 60 * 60 + 100 * 60 + 100),
2585            @"101h 41m 40s",
2586        );
2587
2588        insta::assert_snapshot!(p(-1 * 60 * 60), @"1h ago");
2589        insta::assert_snapshot!(p(-1 * 60 * 60 - 30), @"1h 30s ago");
2590    }
2591
2592    #[test]
2593    fn print_signed_duration_designator_direction_force() {
2594        let printer = || SpanPrinter::new().direction(Direction::ForceSign);
2595        let p = |secs| {
2596            printer().duration_to_string(&SignedDuration::from_secs(secs))
2597        };
2598
2599        insta::assert_snapshot!(p(1), @"+1s");
2600        insta::assert_snapshot!(p(2), @"+2s");
2601        insta::assert_snapshot!(p(10), @"+10s");
2602        insta::assert_snapshot!(p(100), @"+1m 40s");
2603
2604        insta::assert_snapshot!(p(1 * 60), @"+1m");
2605        insta::assert_snapshot!(p(2 * 60), @"+2m");
2606        insta::assert_snapshot!(p(10 * 60), @"+10m");
2607        insta::assert_snapshot!(p(100 * 60), @"+1h 40m");
2608
2609        insta::assert_snapshot!(p(1 * 60 * 60), @"+1h");
2610        insta::assert_snapshot!(p(2 * 60 * 60), @"+2h");
2611        insta::assert_snapshot!(p(10 * 60 * 60), @"+10h");
2612        insta::assert_snapshot!(p(100 * 60 * 60), @"+100h");
2613
2614        insta::assert_snapshot!(
2615            p(60 * 60 + 60 + 1),
2616            @"+1h 1m 1s",
2617        );
2618        insta::assert_snapshot!(
2619            p(2 * 60 * 60 + 2 * 60 + 2),
2620            @"+2h 2m 2s",
2621        );
2622        insta::assert_snapshot!(
2623            p(10 * 60 * 60 + 10 * 60 + 10),
2624            @"+10h 10m 10s",
2625        );
2626        insta::assert_snapshot!(
2627            p(100 * 60 * 60 + 100 * 60 + 100),
2628            @"+101h 41m 40s",
2629        );
2630
2631        insta::assert_snapshot!(p(-1 * 60 * 60), @"-1h");
2632        insta::assert_snapshot!(p(-1 * 60 * 60 - 30), @"-1h 30s");
2633    }
2634
2635    #[test]
2636    fn print_signed_duration_designator_padding() {
2637        let printer = || SpanPrinter::new().padding(2);
2638        let p = |secs| {
2639            printer().duration_to_string(&SignedDuration::from_secs(secs))
2640        };
2641
2642        insta::assert_snapshot!(p(1), @"01s");
2643        insta::assert_snapshot!(p(2), @"02s");
2644        insta::assert_snapshot!(p(10), @"10s");
2645        insta::assert_snapshot!(p(100), @"01m 40s");
2646
2647        insta::assert_snapshot!(p(1 * 60), @"01m");
2648        insta::assert_snapshot!(p(2 * 60), @"02m");
2649        insta::assert_snapshot!(p(10 * 60), @"10m");
2650        insta::assert_snapshot!(p(100 * 60), @"01h 40m");
2651
2652        insta::assert_snapshot!(p(1 * 60 * 60), @"01h");
2653        insta::assert_snapshot!(p(2 * 60 * 60), @"02h");
2654        insta::assert_snapshot!(p(10 * 60 * 60), @"10h");
2655        insta::assert_snapshot!(p(100 * 60 * 60), @"100h");
2656
2657        insta::assert_snapshot!(
2658            p(60 * 60 + 60 + 1),
2659            @"01h 01m 01s",
2660        );
2661        insta::assert_snapshot!(
2662            p(2 * 60 * 60 + 2 * 60 + 2),
2663            @"02h 02m 02s",
2664        );
2665        insta::assert_snapshot!(
2666            p(10 * 60 * 60 + 10 * 60 + 10),
2667            @"10h 10m 10s",
2668        );
2669        insta::assert_snapshot!(
2670            p(100 * 60 * 60 + 100 * 60 + 100),
2671            @"101h 41m 40s",
2672        );
2673
2674        insta::assert_snapshot!(p(-1 * 60 * 60), @"01h ago");
2675        insta::assert_snapshot!(p(-1 * 60 * 60 - 30), @"01h 30s ago");
2676    }
2677
2678    #[test]
2679    fn print_signed_duration_designator_spacing_none() {
2680        let printer = || SpanPrinter::new().spacing(Spacing::None);
2681        let p = |secs| {
2682            printer().duration_to_string(&SignedDuration::from_secs(secs))
2683        };
2684
2685        insta::assert_snapshot!(p(1), @"1s");
2686        insta::assert_snapshot!(p(2), @"2s");
2687        insta::assert_snapshot!(p(10), @"10s");
2688        insta::assert_snapshot!(p(100), @"1m40s");
2689
2690        insta::assert_snapshot!(p(1 * 60), @"1m");
2691        insta::assert_snapshot!(p(2 * 60), @"2m");
2692        insta::assert_snapshot!(p(10 * 60), @"10m");
2693        insta::assert_snapshot!(p(100 * 60), @"1h40m");
2694
2695        insta::assert_snapshot!(p(1 * 60 * 60), @"1h");
2696        insta::assert_snapshot!(p(2 * 60 * 60), @"2h");
2697        insta::assert_snapshot!(p(10 * 60 * 60), @"10h");
2698        insta::assert_snapshot!(p(100 * 60 * 60), @"100h");
2699
2700        insta::assert_snapshot!(
2701            p(60 * 60 + 60 + 1),
2702            @"1h1m1s",
2703        );
2704        insta::assert_snapshot!(
2705            p(2 * 60 * 60 + 2 * 60 + 2),
2706            @"2h2m2s",
2707        );
2708        insta::assert_snapshot!(
2709            p(10 * 60 * 60 + 10 * 60 + 10),
2710            @"10h10m10s",
2711        );
2712        insta::assert_snapshot!(
2713            p(100 * 60 * 60 + 100 * 60 + 100),
2714            @"101h41m40s",
2715        );
2716
2717        insta::assert_snapshot!(p(-1 * 60 * 60), @"-1h");
2718        insta::assert_snapshot!(p(-1 * 60 * 60 - 30), @"-1h30s");
2719    }
2720
2721    #[test]
2722    fn print_signed_duration_designator_spacing_more() {
2723        let printer =
2724            || SpanPrinter::new().spacing(Spacing::BetweenUnitsAndDesignators);
2725        let p = |secs| {
2726            printer().duration_to_string(&SignedDuration::from_secs(secs))
2727        };
2728
2729        insta::assert_snapshot!(p(1), @"1 s");
2730        insta::assert_snapshot!(p(2), @"2 s");
2731        insta::assert_snapshot!(p(10), @"10 s");
2732        insta::assert_snapshot!(p(100), @"1 m 40 s");
2733
2734        insta::assert_snapshot!(p(1 * 60), @"1 m");
2735        insta::assert_snapshot!(p(2 * 60), @"2 m");
2736        insta::assert_snapshot!(p(10 * 60), @"10 m");
2737        insta::assert_snapshot!(p(100 * 60), @"1 h 40 m");
2738
2739        insta::assert_snapshot!(p(1 * 60 * 60), @"1 h");
2740        insta::assert_snapshot!(p(2 * 60 * 60), @"2 h");
2741        insta::assert_snapshot!(p(10 * 60 * 60), @"10 h");
2742        insta::assert_snapshot!(p(100 * 60 * 60), @"100 h");
2743
2744        insta::assert_snapshot!(
2745            p(60 * 60 + 60 + 1),
2746            @"1 h 1 m 1 s",
2747        );
2748        insta::assert_snapshot!(
2749            p(2 * 60 * 60 + 2 * 60 + 2),
2750            @"2 h 2 m 2 s",
2751        );
2752        insta::assert_snapshot!(
2753            p(10 * 60 * 60 + 10 * 60 + 10),
2754            @"10 h 10 m 10 s",
2755        );
2756        insta::assert_snapshot!(
2757            p(100 * 60 * 60 + 100 * 60 + 100),
2758            @"101 h 41 m 40 s",
2759        );
2760
2761        insta::assert_snapshot!(p(-1 * 60 * 60), @"1 h ago");
2762        insta::assert_snapshot!(p(-1 * 60 * 60 - 30), @"1 h 30 s ago");
2763    }
2764
2765    #[test]
2766    fn print_signed_duration_designator_spacing_comma() {
2767        let printer = || {
2768            SpanPrinter::new()
2769                .comma_after_designator(true)
2770                .spacing(Spacing::BetweenUnitsAndDesignators)
2771        };
2772        let p = |secs| {
2773            printer().duration_to_string(&SignedDuration::from_secs(secs))
2774        };
2775
2776        insta::assert_snapshot!(p(1), @"1 s");
2777        insta::assert_snapshot!(p(2), @"2 s");
2778        insta::assert_snapshot!(p(10), @"10 s");
2779        insta::assert_snapshot!(p(100), @"1 m, 40 s");
2780
2781        insta::assert_snapshot!(p(1 * 60), @"1 m");
2782        insta::assert_snapshot!(p(2 * 60), @"2 m");
2783        insta::assert_snapshot!(p(10 * 60), @"10 m");
2784        insta::assert_snapshot!(p(100 * 60), @"1 h, 40 m");
2785
2786        insta::assert_snapshot!(p(1 * 60 * 60), @"1 h");
2787        insta::assert_snapshot!(p(2 * 60 * 60), @"2 h");
2788        insta::assert_snapshot!(p(10 * 60 * 60), @"10 h");
2789        insta::assert_snapshot!(p(100 * 60 * 60), @"100 h");
2790
2791        insta::assert_snapshot!(
2792            p(60 * 60 + 60 + 1),
2793            @"1 h, 1 m, 1 s",
2794        );
2795        insta::assert_snapshot!(
2796            p(2 * 60 * 60 + 2 * 60 + 2),
2797            @"2 h, 2 m, 2 s",
2798        );
2799        insta::assert_snapshot!(
2800            p(10 * 60 * 60 + 10 * 60 + 10),
2801            @"10 h, 10 m, 10 s",
2802        );
2803        insta::assert_snapshot!(
2804            p(100 * 60 * 60 + 100 * 60 + 100),
2805            @"101 h, 41 m, 40 s",
2806        );
2807
2808        insta::assert_snapshot!(p(-1 * 60 * 60), @"1 h ago");
2809        insta::assert_snapshot!(p(-1 * 60 * 60 - 30), @"1 h, 30 s ago");
2810    }
2811
2812    #[test]
2813    fn print_signed_duration_designator_fractional_hour() {
2814        let printer =
2815            || SpanPrinter::new().fractional(Some(FractionalUnit::Hour));
2816        let p = |secs, nanos| {
2817            printer().duration_to_string(&SignedDuration::new(secs, nanos))
2818        };
2819        let pp = |precision, secs, nanos| {
2820            printer()
2821                .precision(Some(precision))
2822                .duration_to_string(&SignedDuration::new(secs, nanos))
2823        };
2824
2825        insta::assert_snapshot!(p(1 * 60 * 60, 0), @"1h");
2826        insta::assert_snapshot!(pp(0, 1 * 60 * 60, 0), @"1h");
2827        insta::assert_snapshot!(pp(1, 1 * 60 * 60, 0), @"1.0h");
2828        insta::assert_snapshot!(pp(2, 1 * 60 * 60, 0), @"1.00h");
2829
2830        insta::assert_snapshot!(p(1 * 60 * 60 + 30 * 60, 0), @"1.5h");
2831        insta::assert_snapshot!(pp(0, 1 * 60 * 60 + 30 * 60, 0), @"1h");
2832        insta::assert_snapshot!(pp(1, 1 * 60 * 60 + 30 * 60, 0), @"1.5h");
2833        insta::assert_snapshot!(pp(2, 1 * 60 * 60 + 30 * 60, 0), @"1.50h");
2834
2835        insta::assert_snapshot!(p(1 * 60 * 60 + 3 * 60, 0), @"1.05h");
2836        insta::assert_snapshot!(p(1 * 60 * 60 + 3 * 60, 1), @"1.05h");
2837        insta::assert_snapshot!(p(1, 0), @"0.000277777h");
2838        // precision loss!
2839        insta::assert_snapshot!(p(1, 1), @"0.000277777h");
2840        insta::assert_snapshot!(p(0, 0), @"0h");
2841        // precision loss!
2842        insta::assert_snapshot!(p(0, 1), @"0h");
2843
2844        insta::assert_snapshot!(
2845            printer().duration_to_string(&SignedDuration::MIN),
2846            @"2562047788015215.502499999h ago",
2847        );
2848    }
2849
2850    #[test]
2851    fn print_signed_duration_designator_fractional_minute() {
2852        let printer =
2853            || SpanPrinter::new().fractional(Some(FractionalUnit::Minute));
2854        let p = |secs, nanos| {
2855            printer().duration_to_string(&SignedDuration::new(secs, nanos))
2856        };
2857        let pp = |precision, secs, nanos| {
2858            printer()
2859                .precision(Some(precision))
2860                .duration_to_string(&SignedDuration::new(secs, nanos))
2861        };
2862
2863        insta::assert_snapshot!(p(1 * 60 * 60, 0), @"1h");
2864        insta::assert_snapshot!(p(1 * 60 * 60 + 30 * 60, 0), @"1h 30m");
2865
2866        insta::assert_snapshot!(p(60, 0), @"1m");
2867        insta::assert_snapshot!(pp(0, 60, 0), @"1m");
2868        insta::assert_snapshot!(pp(1, 60, 0), @"1.0m");
2869        insta::assert_snapshot!(pp(2, 60, 0), @"1.00m");
2870
2871        insta::assert_snapshot!(p(90, 0), @"1.5m");
2872        insta::assert_snapshot!(pp(0, 90, 0), @"1m");
2873        insta::assert_snapshot!(pp(1, 90, 0), @"1.5m");
2874        insta::assert_snapshot!(pp(2, 90, 0), @"1.50m");
2875
2876        insta::assert_snapshot!(p(1 * 60 * 60, 1), @"1h");
2877        insta::assert_snapshot!(p(63, 0), @"1.05m");
2878        insta::assert_snapshot!(p(63, 1), @"1.05m");
2879        insta::assert_snapshot!(p(1, 0), @"0.016666666m");
2880        // precision loss!
2881        insta::assert_snapshot!(p(1, 1), @"0.016666666m");
2882        insta::assert_snapshot!(p(0, 0), @"0m");
2883        // precision loss!
2884        insta::assert_snapshot!(p(0, 1), @"0m");
2885
2886        insta::assert_snapshot!(
2887            printer().duration_to_string(&SignedDuration::MIN),
2888            @"2562047788015215h 30.149999999m ago",
2889        );
2890    }
2891
2892    #[test]
2893    fn print_signed_duration_designator_fractional_second() {
2894        let printer =
2895            || SpanPrinter::new().fractional(Some(FractionalUnit::Second));
2896        let p = |secs, nanos| {
2897            printer().duration_to_string(&SignedDuration::new(secs, nanos))
2898        };
2899        let pp = |precision, secs, nanos| {
2900            printer()
2901                .precision(Some(precision))
2902                .duration_to_string(&SignedDuration::new(secs, nanos))
2903        };
2904
2905        insta::assert_snapshot!(p(1 * 60 * 60, 0), @"1h");
2906        insta::assert_snapshot!(p(1 * 60 * 60 + 30 * 60, 0), @"1h 30m");
2907
2908        insta::assert_snapshot!(p(1, 0), @"1s");
2909        insta::assert_snapshot!(pp(0, 1, 0), @"1s");
2910        insta::assert_snapshot!(pp(1, 1, 0), @"1.0s");
2911        insta::assert_snapshot!(pp(2, 1, 0), @"1.00s");
2912
2913        insta::assert_snapshot!(p(1, 500_000_000), @"1.5s");
2914        insta::assert_snapshot!(pp(0, 1, 500_000_000), @"1s");
2915        insta::assert_snapshot!(pp(1, 1, 500_000_000), @"1.5s");
2916        insta::assert_snapshot!(pp(2, 1, 500_000_000), @"1.50s");
2917
2918        insta::assert_snapshot!(p(1, 1), @"1.000000001s");
2919        insta::assert_snapshot!(p(0, 1), @"0.000000001s");
2920        insta::assert_snapshot!(p(0, 0), @"0s");
2921
2922        insta::assert_snapshot!(
2923            printer().duration_to_string(&SignedDuration::MIN),
2924            @"2562047788015215h 30m 8.999999999s ago",
2925        );
2926    }
2927
2928    #[test]
2929    fn print_signed_duration_designator_fractional_millisecond() {
2930        let printer = || {
2931            SpanPrinter::new().fractional(Some(FractionalUnit::Millisecond))
2932        };
2933        let p = |secs, nanos| {
2934            printer().duration_to_string(&SignedDuration::new(secs, nanos))
2935        };
2936        let pp = |precision, secs, nanos| {
2937            printer()
2938                .precision(Some(precision))
2939                .duration_to_string(&SignedDuration::new(secs, nanos))
2940        };
2941
2942        insta::assert_snapshot!(p(1 * 60 * 60, 0), @"1h");
2943        insta::assert_snapshot!(p(1 * 60 * 60 + 30 * 60, 0), @"1h 30m");
2944        insta::assert_snapshot!(
2945            p(1 * 60 * 60 + 30 * 60 + 10, 0),
2946            @"1h 30m 10s",
2947        );
2948
2949        insta::assert_snapshot!(p(1, 0), @"1s");
2950        insta::assert_snapshot!(pp(0, 1, 0), @"1s");
2951        insta::assert_snapshot!(pp(1, 1, 0), @"1s 0.0ms");
2952        insta::assert_snapshot!(pp(2, 1, 0), @"1s 0.00ms");
2953
2954        insta::assert_snapshot!(p(1, 500_000_000), @"1s 500ms");
2955        insta::assert_snapshot!(pp(0, 1, 1_500_000), @"1s 1ms");
2956        insta::assert_snapshot!(pp(1, 1, 1_500_000), @"1s 1.5ms");
2957        insta::assert_snapshot!(pp(2, 1, 1_500_000), @"1s 1.50ms");
2958
2959        insta::assert_snapshot!(p(0, 1_000_001), @"1.000001ms");
2960        insta::assert_snapshot!(p(0, 0_000_001), @"0.000001ms");
2961        insta::assert_snapshot!(p(0, 0), @"0ms");
2962
2963        insta::assert_snapshot!(
2964            printer().duration_to_string(&SignedDuration::MIN),
2965            @"2562047788015215h 30m 8s 999.999999ms ago",
2966        );
2967    }
2968
2969    #[test]
2970    fn print_signed_duration_designator_fractional_microsecond() {
2971        let printer = || {
2972            SpanPrinter::new().fractional(Some(FractionalUnit::Microsecond))
2973        };
2974        let p = |secs, nanos| {
2975            printer().duration_to_string(&SignedDuration::new(secs, nanos))
2976        };
2977        let pp = |precision, secs, nanos| {
2978            printer()
2979                .precision(Some(precision))
2980                .duration_to_string(&SignedDuration::new(secs, nanos))
2981        };
2982
2983        insta::assert_snapshot!(p(1 * 60 * 60, 0), @"1h");
2984        insta::assert_snapshot!(p(1 * 60 * 60 + 30 * 60, 0), @"1h 30m");
2985        insta::assert_snapshot!(
2986            p(1 * 60 * 60 + 30 * 60 + 10, 0),
2987            @"1h 30m 10s",
2988        );
2989
2990        insta::assert_snapshot!(p(1, 0), @"1s");
2991        insta::assert_snapshot!(pp(0, 1, 0), @"1s");
2992        insta::assert_snapshot!(pp(1, 1, 0), @"1s 0.0µs");
2993        insta::assert_snapshot!(pp(2, 1, 0), @"1s 0.00µs");
2994
2995        insta::assert_snapshot!(p(1, 500_000_000), @"1s 500ms");
2996        insta::assert_snapshot!(pp(0, 1, 1_500_000), @"1s 1ms 500µs");
2997        insta::assert_snapshot!(pp(1, 1, 1_500_000), @"1s 1ms 500.0µs");
2998        insta::assert_snapshot!(pp(2, 1, 1_500_000), @"1s 1ms 500.00µs");
2999
3000        insta::assert_snapshot!(p(0, 1_000_001), @"1ms 0.001µs");
3001        insta::assert_snapshot!(p(0, 0_000_001), @"0.001µs");
3002        insta::assert_snapshot!(p(0, 0), @"0µs");
3003
3004        insta::assert_snapshot!(
3005            printer().duration_to_string(&SignedDuration::MIN),
3006            @"2562047788015215h 30m 8s 999ms 999.999µs ago",
3007        );
3008    }
3009
3010    #[test]
3011    fn print_unsigned_duration_designator_default() {
3012        let printer = || SpanPrinter::new();
3013        let p = |secs| {
3014            printer().unsigned_duration_to_string(
3015                &core::time::Duration::from_secs(secs),
3016            )
3017        };
3018
3019        insta::assert_snapshot!(p(1), @"1s");
3020        insta::assert_snapshot!(p(2), @"2s");
3021        insta::assert_snapshot!(p(10), @"10s");
3022        insta::assert_snapshot!(p(100), @"1m 40s");
3023
3024        insta::assert_snapshot!(p(1 * 60), @"1m");
3025        insta::assert_snapshot!(p(2 * 60), @"2m");
3026        insta::assert_snapshot!(p(10 * 60), @"10m");
3027        insta::assert_snapshot!(p(100 * 60), @"1h 40m");
3028
3029        insta::assert_snapshot!(p(1 * 60 * 60), @"1h");
3030        insta::assert_snapshot!(p(2 * 60 * 60), @"2h");
3031        insta::assert_snapshot!(p(10 * 60 * 60), @"10h");
3032        insta::assert_snapshot!(p(100 * 60 * 60), @"100h");
3033
3034        insta::assert_snapshot!(
3035            p(60 * 60 + 60 + 1),
3036            @"1h 1m 1s",
3037        );
3038        insta::assert_snapshot!(
3039            p(2 * 60 * 60 + 2 * 60 + 2),
3040            @"2h 2m 2s",
3041        );
3042        insta::assert_snapshot!(
3043            p(10 * 60 * 60 + 10 * 60 + 10),
3044            @"10h 10m 10s",
3045        );
3046        insta::assert_snapshot!(
3047            p(100 * 60 * 60 + 100 * 60 + 100),
3048            @"101h 41m 40s",
3049        );
3050    }
3051
3052    #[test]
3053    fn print_unsigned_duration_designator_verbose() {
3054        let printer = || SpanPrinter::new().designator(Designator::Verbose);
3055        let p = |secs| {
3056            printer().unsigned_duration_to_string(
3057                &core::time::Duration::from_secs(secs),
3058            )
3059        };
3060
3061        insta::assert_snapshot!(p(1), @"1second");
3062        insta::assert_snapshot!(p(2), @"2seconds");
3063        insta::assert_snapshot!(p(10), @"10seconds");
3064        insta::assert_snapshot!(p(100), @"1minute 40seconds");
3065
3066        insta::assert_snapshot!(p(1 * 60), @"1minute");
3067        insta::assert_snapshot!(p(2 * 60), @"2minutes");
3068        insta::assert_snapshot!(p(10 * 60), @"10minutes");
3069        insta::assert_snapshot!(p(100 * 60), @"1hour 40minutes");
3070
3071        insta::assert_snapshot!(p(1 * 60 * 60), @"1hour");
3072        insta::assert_snapshot!(p(2 * 60 * 60), @"2hours");
3073        insta::assert_snapshot!(p(10 * 60 * 60), @"10hours");
3074        insta::assert_snapshot!(p(100 * 60 * 60), @"100hours");
3075
3076        insta::assert_snapshot!(
3077            p(60 * 60 + 60 + 1),
3078            @"1hour 1minute 1second",
3079        );
3080        insta::assert_snapshot!(
3081            p(2 * 60 * 60 + 2 * 60 + 2),
3082            @"2hours 2minutes 2seconds",
3083        );
3084        insta::assert_snapshot!(
3085            p(10 * 60 * 60 + 10 * 60 + 10),
3086            @"10hours 10minutes 10seconds",
3087        );
3088        insta::assert_snapshot!(
3089            p(100 * 60 * 60 + 100 * 60 + 100),
3090            @"101hours 41minutes 40seconds",
3091        );
3092    }
3093
3094    #[test]
3095    fn print_unsigned_duration_designator_short() {
3096        let printer = || SpanPrinter::new().designator(Designator::Short);
3097        let p = |secs| {
3098            printer().unsigned_duration_to_string(
3099                &core::time::Duration::from_secs(secs),
3100            )
3101        };
3102
3103        insta::assert_snapshot!(p(1), @"1sec");
3104        insta::assert_snapshot!(p(2), @"2secs");
3105        insta::assert_snapshot!(p(10), @"10secs");
3106        insta::assert_snapshot!(p(100), @"1min 40secs");
3107
3108        insta::assert_snapshot!(p(1 * 60), @"1min");
3109        insta::assert_snapshot!(p(2 * 60), @"2mins");
3110        insta::assert_snapshot!(p(10 * 60), @"10mins");
3111        insta::assert_snapshot!(p(100 * 60), @"1hr 40mins");
3112
3113        insta::assert_snapshot!(p(1 * 60 * 60), @"1hr");
3114        insta::assert_snapshot!(p(2 * 60 * 60), @"2hrs");
3115        insta::assert_snapshot!(p(10 * 60 * 60), @"10hrs");
3116        insta::assert_snapshot!(p(100 * 60 * 60), @"100hrs");
3117
3118        insta::assert_snapshot!(
3119            p(60 * 60 + 60 + 1),
3120            @"1hr 1min 1sec",
3121        );
3122        insta::assert_snapshot!(
3123            p(2 * 60 * 60 + 2 * 60 + 2),
3124            @"2hrs 2mins 2secs",
3125        );
3126        insta::assert_snapshot!(
3127            p(10 * 60 * 60 + 10 * 60 + 10),
3128            @"10hrs 10mins 10secs",
3129        );
3130        insta::assert_snapshot!(
3131            p(100 * 60 * 60 + 100 * 60 + 100),
3132            @"101hrs 41mins 40secs",
3133        );
3134    }
3135
3136    #[test]
3137    fn print_unsigned_duration_designator_compact() {
3138        let printer = || SpanPrinter::new().designator(Designator::Compact);
3139        let p = |secs| {
3140            printer().unsigned_duration_to_string(
3141                &core::time::Duration::from_secs(secs),
3142            )
3143        };
3144
3145        insta::assert_snapshot!(p(1), @"1s");
3146        insta::assert_snapshot!(p(2), @"2s");
3147        insta::assert_snapshot!(p(10), @"10s");
3148        insta::assert_snapshot!(p(100), @"1m 40s");
3149
3150        insta::assert_snapshot!(p(1 * 60), @"1m");
3151        insta::assert_snapshot!(p(2 * 60), @"2m");
3152        insta::assert_snapshot!(p(10 * 60), @"10m");
3153        insta::assert_snapshot!(p(100 * 60), @"1h 40m");
3154
3155        insta::assert_snapshot!(p(1 * 60 * 60), @"1h");
3156        insta::assert_snapshot!(p(2 * 60 * 60), @"2h");
3157        insta::assert_snapshot!(p(10 * 60 * 60), @"10h");
3158        insta::assert_snapshot!(p(100 * 60 * 60), @"100h");
3159
3160        insta::assert_snapshot!(
3161            p(60 * 60 + 60 + 1),
3162            @"1h 1m 1s",
3163        );
3164        insta::assert_snapshot!(
3165            p(2 * 60 * 60 + 2 * 60 + 2),
3166            @"2h 2m 2s",
3167        );
3168        insta::assert_snapshot!(
3169            p(10 * 60 * 60 + 10 * 60 + 10),
3170            @"10h 10m 10s",
3171        );
3172        insta::assert_snapshot!(
3173            p(100 * 60 * 60 + 100 * 60 + 100),
3174            @"101h 41m 40s",
3175        );
3176    }
3177
3178    #[test]
3179    fn print_unsigned_duration_designator_direction_force() {
3180        let printer = || SpanPrinter::new().direction(Direction::ForceSign);
3181        let p = |secs| {
3182            printer().unsigned_duration_to_string(
3183                &core::time::Duration::from_secs(secs),
3184            )
3185        };
3186
3187        insta::assert_snapshot!(p(1), @"+1s");
3188        insta::assert_snapshot!(p(2), @"+2s");
3189        insta::assert_snapshot!(p(10), @"+10s");
3190        insta::assert_snapshot!(p(100), @"+1m 40s");
3191
3192        insta::assert_snapshot!(p(1 * 60), @"+1m");
3193        insta::assert_snapshot!(p(2 * 60), @"+2m");
3194        insta::assert_snapshot!(p(10 * 60), @"+10m");
3195        insta::assert_snapshot!(p(100 * 60), @"+1h 40m");
3196
3197        insta::assert_snapshot!(p(1 * 60 * 60), @"+1h");
3198        insta::assert_snapshot!(p(2 * 60 * 60), @"+2h");
3199        insta::assert_snapshot!(p(10 * 60 * 60), @"+10h");
3200        insta::assert_snapshot!(p(100 * 60 * 60), @"+100h");
3201
3202        insta::assert_snapshot!(
3203            p(60 * 60 + 60 + 1),
3204            @"+1h 1m 1s",
3205        );
3206        insta::assert_snapshot!(
3207            p(2 * 60 * 60 + 2 * 60 + 2),
3208            @"+2h 2m 2s",
3209        );
3210        insta::assert_snapshot!(
3211            p(10 * 60 * 60 + 10 * 60 + 10),
3212            @"+10h 10m 10s",
3213        );
3214        insta::assert_snapshot!(
3215            p(100 * 60 * 60 + 100 * 60 + 100),
3216            @"+101h 41m 40s",
3217        );
3218    }
3219
3220    #[test]
3221    fn print_unsigned_duration_designator_padding() {
3222        let printer = || SpanPrinter::new().padding(2);
3223        let p = |secs| {
3224            printer().unsigned_duration_to_string(
3225                &core::time::Duration::from_secs(secs),
3226            )
3227        };
3228
3229        insta::assert_snapshot!(p(1), @"01s");
3230        insta::assert_snapshot!(p(2), @"02s");
3231        insta::assert_snapshot!(p(10), @"10s");
3232        insta::assert_snapshot!(p(100), @"01m 40s");
3233
3234        insta::assert_snapshot!(p(1 * 60), @"01m");
3235        insta::assert_snapshot!(p(2 * 60), @"02m");
3236        insta::assert_snapshot!(p(10 * 60), @"10m");
3237        insta::assert_snapshot!(p(100 * 60), @"01h 40m");
3238
3239        insta::assert_snapshot!(p(1 * 60 * 60), @"01h");
3240        insta::assert_snapshot!(p(2 * 60 * 60), @"02h");
3241        insta::assert_snapshot!(p(10 * 60 * 60), @"10h");
3242        insta::assert_snapshot!(p(100 * 60 * 60), @"100h");
3243
3244        insta::assert_snapshot!(
3245            p(60 * 60 + 60 + 1),
3246            @"01h 01m 01s",
3247        );
3248        insta::assert_snapshot!(
3249            p(2 * 60 * 60 + 2 * 60 + 2),
3250            @"02h 02m 02s",
3251        );
3252        insta::assert_snapshot!(
3253            p(10 * 60 * 60 + 10 * 60 + 10),
3254            @"10h 10m 10s",
3255        );
3256        insta::assert_snapshot!(
3257            p(100 * 60 * 60 + 100 * 60 + 100),
3258            @"101h 41m 40s",
3259        );
3260    }
3261
3262    #[test]
3263    fn print_unsigned_duration_designator_spacing_none() {
3264        let printer = || SpanPrinter::new().spacing(Spacing::None);
3265        let p = |secs| {
3266            printer().unsigned_duration_to_string(
3267                &core::time::Duration::from_secs(secs),
3268            )
3269        };
3270
3271        insta::assert_snapshot!(p(1), @"1s");
3272        insta::assert_snapshot!(p(2), @"2s");
3273        insta::assert_snapshot!(p(10), @"10s");
3274        insta::assert_snapshot!(p(100), @"1m40s");
3275
3276        insta::assert_snapshot!(p(1 * 60), @"1m");
3277        insta::assert_snapshot!(p(2 * 60), @"2m");
3278        insta::assert_snapshot!(p(10 * 60), @"10m");
3279        insta::assert_snapshot!(p(100 * 60), @"1h40m");
3280
3281        insta::assert_snapshot!(p(1 * 60 * 60), @"1h");
3282        insta::assert_snapshot!(p(2 * 60 * 60), @"2h");
3283        insta::assert_snapshot!(p(10 * 60 * 60), @"10h");
3284        insta::assert_snapshot!(p(100 * 60 * 60), @"100h");
3285
3286        insta::assert_snapshot!(
3287            p(60 * 60 + 60 + 1),
3288            @"1h1m1s",
3289        );
3290        insta::assert_snapshot!(
3291            p(2 * 60 * 60 + 2 * 60 + 2),
3292            @"2h2m2s",
3293        );
3294        insta::assert_snapshot!(
3295            p(10 * 60 * 60 + 10 * 60 + 10),
3296            @"10h10m10s",
3297        );
3298        insta::assert_snapshot!(
3299            p(100 * 60 * 60 + 100 * 60 + 100),
3300            @"101h41m40s",
3301        );
3302    }
3303
3304    #[test]
3305    fn print_unsigned_duration_designator_spacing_more() {
3306        let printer =
3307            || SpanPrinter::new().spacing(Spacing::BetweenUnitsAndDesignators);
3308        let p = |secs| {
3309            printer().unsigned_duration_to_string(
3310                &core::time::Duration::from_secs(secs),
3311            )
3312        };
3313
3314        insta::assert_snapshot!(p(1), @"1 s");
3315        insta::assert_snapshot!(p(2), @"2 s");
3316        insta::assert_snapshot!(p(10), @"10 s");
3317        insta::assert_snapshot!(p(100), @"1 m 40 s");
3318
3319        insta::assert_snapshot!(p(1 * 60), @"1 m");
3320        insta::assert_snapshot!(p(2 * 60), @"2 m");
3321        insta::assert_snapshot!(p(10 * 60), @"10 m");
3322        insta::assert_snapshot!(p(100 * 60), @"1 h 40 m");
3323
3324        insta::assert_snapshot!(p(1 * 60 * 60), @"1 h");
3325        insta::assert_snapshot!(p(2 * 60 * 60), @"2 h");
3326        insta::assert_snapshot!(p(10 * 60 * 60), @"10 h");
3327        insta::assert_snapshot!(p(100 * 60 * 60), @"100 h");
3328
3329        insta::assert_snapshot!(
3330            p(60 * 60 + 60 + 1),
3331            @"1 h 1 m 1 s",
3332        );
3333        insta::assert_snapshot!(
3334            p(2 * 60 * 60 + 2 * 60 + 2),
3335            @"2 h 2 m 2 s",
3336        );
3337        insta::assert_snapshot!(
3338            p(10 * 60 * 60 + 10 * 60 + 10),
3339            @"10 h 10 m 10 s",
3340        );
3341        insta::assert_snapshot!(
3342            p(100 * 60 * 60 + 100 * 60 + 100),
3343            @"101 h 41 m 40 s",
3344        );
3345    }
3346
3347    #[test]
3348    fn print_unsigned_duration_designator_spacing_comma() {
3349        let printer = || {
3350            SpanPrinter::new()
3351                .comma_after_designator(true)
3352                .spacing(Spacing::BetweenUnitsAndDesignators)
3353        };
3354        let p = |secs| {
3355            printer().unsigned_duration_to_string(
3356                &core::time::Duration::from_secs(secs),
3357            )
3358        };
3359
3360        insta::assert_snapshot!(p(1), @"1 s");
3361        insta::assert_snapshot!(p(2), @"2 s");
3362        insta::assert_snapshot!(p(10), @"10 s");
3363        insta::assert_snapshot!(p(100), @"1 m, 40 s");
3364
3365        insta::assert_snapshot!(p(1 * 60), @"1 m");
3366        insta::assert_snapshot!(p(2 * 60), @"2 m");
3367        insta::assert_snapshot!(p(10 * 60), @"10 m");
3368        insta::assert_snapshot!(p(100 * 60), @"1 h, 40 m");
3369
3370        insta::assert_snapshot!(p(1 * 60 * 60), @"1 h");
3371        insta::assert_snapshot!(p(2 * 60 * 60), @"2 h");
3372        insta::assert_snapshot!(p(10 * 60 * 60), @"10 h");
3373        insta::assert_snapshot!(p(100 * 60 * 60), @"100 h");
3374
3375        insta::assert_snapshot!(
3376            p(60 * 60 + 60 + 1),
3377            @"1 h, 1 m, 1 s",
3378        );
3379        insta::assert_snapshot!(
3380            p(2 * 60 * 60 + 2 * 60 + 2),
3381            @"2 h, 2 m, 2 s",
3382        );
3383        insta::assert_snapshot!(
3384            p(10 * 60 * 60 + 10 * 60 + 10),
3385            @"10 h, 10 m, 10 s",
3386        );
3387        insta::assert_snapshot!(
3388            p(100 * 60 * 60 + 100 * 60 + 100),
3389            @"101 h, 41 m, 40 s",
3390        );
3391    }
3392
3393    #[test]
3394    fn print_unsigned_duration_designator_fractional_hour() {
3395        let printer =
3396            || SpanPrinter::new().fractional(Some(FractionalUnit::Hour));
3397        let p = |secs, nanos| {
3398            printer().unsigned_duration_to_string(&core::time::Duration::new(
3399                secs, nanos,
3400            ))
3401        };
3402        let pp = |precision, secs, nanos| {
3403            printer()
3404                .precision(Some(precision))
3405                .duration_to_string(&SignedDuration::new(secs, nanos))
3406        };
3407
3408        insta::assert_snapshot!(p(1 * 60 * 60, 0), @"1h");
3409        insta::assert_snapshot!(pp(0, 1 * 60 * 60, 0), @"1h");
3410        insta::assert_snapshot!(pp(1, 1 * 60 * 60, 0), @"1.0h");
3411        insta::assert_snapshot!(pp(2, 1 * 60 * 60, 0), @"1.00h");
3412
3413        insta::assert_snapshot!(p(1 * 60 * 60 + 30 * 60, 0), @"1.5h");
3414        insta::assert_snapshot!(pp(0, 1 * 60 * 60 + 30 * 60, 0), @"1h");
3415        insta::assert_snapshot!(pp(1, 1 * 60 * 60 + 30 * 60, 0), @"1.5h");
3416        insta::assert_snapshot!(pp(2, 1 * 60 * 60 + 30 * 60, 0), @"1.50h");
3417
3418        insta::assert_snapshot!(p(1 * 60 * 60 + 3 * 60, 0), @"1.05h");
3419        insta::assert_snapshot!(p(1 * 60 * 60 + 3 * 60, 1), @"1.05h");
3420        insta::assert_snapshot!(p(1, 0), @"0.000277777h");
3421        // precision loss!
3422        insta::assert_snapshot!(p(1, 1), @"0.000277777h");
3423        insta::assert_snapshot!(p(0, 0), @"0h");
3424        // precision loss!
3425        insta::assert_snapshot!(p(0, 1), @"0h");
3426    }
3427
3428    #[test]
3429    fn print_unsigned_duration_designator_fractional_minute() {
3430        let printer =
3431            || SpanPrinter::new().fractional(Some(FractionalUnit::Minute));
3432        let p = |secs, nanos| {
3433            printer().unsigned_duration_to_string(&core::time::Duration::new(
3434                secs, nanos,
3435            ))
3436        };
3437        let pp = |precision, secs, nanos| {
3438            printer()
3439                .precision(Some(precision))
3440                .duration_to_string(&SignedDuration::new(secs, nanos))
3441        };
3442
3443        insta::assert_snapshot!(p(1 * 60 * 60, 0), @"1h");
3444        insta::assert_snapshot!(p(1 * 60 * 60 + 30 * 60, 0), @"1h 30m");
3445
3446        insta::assert_snapshot!(p(60, 0), @"1m");
3447        insta::assert_snapshot!(pp(0, 60, 0), @"1m");
3448        insta::assert_snapshot!(pp(1, 60, 0), @"1.0m");
3449        insta::assert_snapshot!(pp(2, 60, 0), @"1.00m");
3450
3451        insta::assert_snapshot!(p(90, 0), @"1.5m");
3452        insta::assert_snapshot!(pp(0, 90, 0), @"1m");
3453        insta::assert_snapshot!(pp(1, 90, 0), @"1.5m");
3454        insta::assert_snapshot!(pp(2, 90, 0), @"1.50m");
3455
3456        insta::assert_snapshot!(p(1 * 60 * 60, 1), @"1h");
3457        insta::assert_snapshot!(p(63, 0), @"1.05m");
3458        insta::assert_snapshot!(p(63, 1), @"1.05m");
3459        insta::assert_snapshot!(p(1, 0), @"0.016666666m");
3460        // precision loss!
3461        insta::assert_snapshot!(p(1, 1), @"0.016666666m");
3462        insta::assert_snapshot!(p(0, 0), @"0m");
3463        // precision loss!
3464        insta::assert_snapshot!(p(0, 1), @"0m");
3465    }
3466
3467    #[test]
3468    fn print_unsigned_duration_designator_fractional_second() {
3469        let printer =
3470            || SpanPrinter::new().fractional(Some(FractionalUnit::Second));
3471        let p = |secs, nanos| {
3472            printer().unsigned_duration_to_string(&core::time::Duration::new(
3473                secs, nanos,
3474            ))
3475        };
3476        let pp = |precision, secs, nanos| {
3477            printer()
3478                .precision(Some(precision))
3479                .duration_to_string(&SignedDuration::new(secs, nanos))
3480        };
3481
3482        insta::assert_snapshot!(p(1 * 60 * 60, 0), @"1h");
3483        insta::assert_snapshot!(p(1 * 60 * 60 + 30 * 60, 0), @"1h 30m");
3484
3485        insta::assert_snapshot!(p(1, 0), @"1s");
3486        insta::assert_snapshot!(pp(0, 1, 0), @"1s");
3487        insta::assert_snapshot!(pp(1, 1, 0), @"1.0s");
3488        insta::assert_snapshot!(pp(2, 1, 0), @"1.00s");
3489
3490        insta::assert_snapshot!(p(1, 500_000_000), @"1.5s");
3491        insta::assert_snapshot!(pp(0, 1, 500_000_000), @"1s");
3492        insta::assert_snapshot!(pp(1, 1, 500_000_000), @"1.5s");
3493        insta::assert_snapshot!(pp(2, 1, 500_000_000), @"1.50s");
3494
3495        insta::assert_snapshot!(p(1, 1), @"1.000000001s");
3496        insta::assert_snapshot!(p(0, 1), @"0.000000001s");
3497        insta::assert_snapshot!(p(0, 0), @"0s");
3498    }
3499
3500    #[test]
3501    fn print_unsigned_duration_designator_fractional_millisecond() {
3502        let printer = || {
3503            SpanPrinter::new().fractional(Some(FractionalUnit::Millisecond))
3504        };
3505        let p = |secs, nanos| {
3506            printer().unsigned_duration_to_string(&core::time::Duration::new(
3507                secs, nanos,
3508            ))
3509        };
3510        let pp = |precision, secs, nanos| {
3511            printer()
3512                .precision(Some(precision))
3513                .duration_to_string(&SignedDuration::new(secs, nanos))
3514        };
3515
3516        insta::assert_snapshot!(p(1 * 60 * 60, 0), @"1h");
3517        insta::assert_snapshot!(p(1 * 60 * 60 + 30 * 60, 0), @"1h 30m");
3518        insta::assert_snapshot!(
3519            p(1 * 60 * 60 + 30 * 60 + 10, 0),
3520            @"1h 30m 10s",
3521        );
3522
3523        insta::assert_snapshot!(p(1, 0), @"1s");
3524        insta::assert_snapshot!(pp(0, 1, 0), @"1s");
3525        insta::assert_snapshot!(pp(1, 1, 0), @"1s 0.0ms");
3526        insta::assert_snapshot!(pp(2, 1, 0), @"1s 0.00ms");
3527
3528        insta::assert_snapshot!(p(1, 500_000_000), @"1s 500ms");
3529        insta::assert_snapshot!(pp(0, 1, 1_500_000), @"1s 1ms");
3530        insta::assert_snapshot!(pp(1, 1, 1_500_000), @"1s 1.5ms");
3531        insta::assert_snapshot!(pp(2, 1, 1_500_000), @"1s 1.50ms");
3532
3533        insta::assert_snapshot!(p(0, 1_000_001), @"1.000001ms");
3534        insta::assert_snapshot!(p(0, 0_000_001), @"0.000001ms");
3535        insta::assert_snapshot!(p(0, 0), @"0ms");
3536    }
3537
3538    #[test]
3539    fn print_unsigned_duration_designator_fractional_microsecond() {
3540        let printer = || {
3541            SpanPrinter::new().fractional(Some(FractionalUnit::Microsecond))
3542        };
3543        let p = |secs, nanos| {
3544            printer().unsigned_duration_to_string(&core::time::Duration::new(
3545                secs, nanos,
3546            ))
3547        };
3548        let pp = |precision, secs, nanos| {
3549            printer().precision(Some(precision)).unsigned_duration_to_string(
3550                &core::time::Duration::new(secs, nanos),
3551            )
3552        };
3553
3554        insta::assert_snapshot!(p(1 * 60 * 60, 0), @"1h");
3555        insta::assert_snapshot!(p(1 * 60 * 60 + 30 * 60, 0), @"1h 30m");
3556        insta::assert_snapshot!(
3557            p(1 * 60 * 60 + 30 * 60 + 10, 0),
3558            @"1h 30m 10s",
3559        );
3560
3561        insta::assert_snapshot!(p(1, 0), @"1s");
3562        insta::assert_snapshot!(pp(0, 1, 0), @"1s");
3563        insta::assert_snapshot!(pp(1, 1, 0), @"1s 0.0µs");
3564        insta::assert_snapshot!(pp(2, 1, 0), @"1s 0.00µs");
3565
3566        insta::assert_snapshot!(p(1, 500_000_000), @"1s 500ms");
3567        insta::assert_snapshot!(pp(0, 1, 1_500_000), @"1s 1ms 500µs");
3568        insta::assert_snapshot!(pp(1, 1, 1_500_000), @"1s 1ms 500.0µs");
3569        insta::assert_snapshot!(pp(2, 1, 1_500_000), @"1s 1ms 500.00µs");
3570
3571        insta::assert_snapshot!(p(0, 1_000_001), @"1ms 0.001µs");
3572        insta::assert_snapshot!(p(0, 0_000_001), @"0.001µs");
3573        insta::assert_snapshot!(p(0, 0), @"0µs");
3574    }
3575
3576    #[test]
3577    fn print_span_hms() {
3578        let printer = || SpanPrinter::new().hours_minutes_seconds(true);
3579        let p = |span| printer().span_to_string(&span);
3580
3581        insta::assert_snapshot!(p(1.second()), @"00:00:01");
3582        insta::assert_snapshot!(p(2.seconds()), @"00:00:02");
3583        insta::assert_snapshot!(p(10.seconds()), @"00:00:10");
3584        insta::assert_snapshot!(p(100.seconds()), @"00:00:100");
3585
3586        insta::assert_snapshot!(p(1.minute()), @"00:01:00");
3587        insta::assert_snapshot!(p(2.minutes()), @"00:02:00");
3588        insta::assert_snapshot!(p(10.minutes()), @"00:10:00");
3589        insta::assert_snapshot!(p(100.minutes()), @"00:100:00");
3590
3591        insta::assert_snapshot!(p(1.hour()), @"01:00:00");
3592        insta::assert_snapshot!(p(2.hours()), @"02:00:00");
3593        insta::assert_snapshot!(p(10.hours()), @"10:00:00");
3594        insta::assert_snapshot!(p(100.hours()), @"100:00:00");
3595
3596        insta::assert_snapshot!(
3597            p(1.hour().minutes(1).seconds(1)),
3598            @"01:01:01",
3599        );
3600        insta::assert_snapshot!(
3601            p(2.hours().minutes(2).seconds(2)),
3602            @"02:02:02",
3603        );
3604        insta::assert_snapshot!(
3605            p(10.hours().minutes(10).seconds(10)),
3606            @"10:10:10",
3607        );
3608        insta::assert_snapshot!(
3609            p(100.hours().minutes(100).seconds(100)),
3610            @"100:100:100",
3611        );
3612
3613        insta::assert_snapshot!(
3614            p(1.day().hours(1).minutes(1).seconds(1)),
3615            @"1d 01:01:01",
3616        );
3617        insta::assert_snapshot!(
3618            p(1.day()),
3619            @"1d 00:00:00",
3620        );
3621        insta::assert_snapshot!(
3622            p(1.day().seconds(2)),
3623            @"1d 00:00:02",
3624        );
3625    }
3626
3627    #[test]
3628    fn print_span_hms_fmt() {
3629        let printer = || {
3630            SpanPrinter::new()
3631                .hours_minutes_seconds(true)
3632                .comma_after_designator(true)
3633                .spacing(Spacing::BetweenUnitsAndDesignators)
3634        };
3635        let p = |span| printer().span_to_string(&span);
3636
3637        insta::assert_snapshot!(
3638            p(1.day().hours(1).minutes(1).seconds(1)),
3639            @"1 d, 01:01:01",
3640        );
3641        insta::assert_snapshot!(
3642            p(1.year().months(1).weeks(1).days(1).hours(1).minutes(1).seconds(1)),
3643            @"1 y, 1 mo, 1 w, 1 d, 01:01:01",
3644        );
3645        insta::assert_snapshot!(
3646            p(1.day().hours(1).minutes(1).seconds(1).nanoseconds(1)),
3647            @"1 d, 01:01:01.000000001",
3648        );
3649    }
3650
3651    #[test]
3652    fn print_span_hms_sign() {
3653        let printer = |direction| {
3654            SpanPrinter::new().hours_minutes_seconds(true).direction(direction)
3655        };
3656        let p = |direction, span| printer(direction).span_to_string(&span);
3657
3658        insta::assert_snapshot!(
3659            p(Direction::Auto, 1.hour()),
3660            @"01:00:00",
3661        );
3662        insta::assert_snapshot!(
3663            p(Direction::Sign, 1.hour()),
3664            @"01:00:00",
3665        );
3666        insta::assert_snapshot!(
3667            p(Direction::ForceSign, 1.hour()),
3668            @"+01:00:00",
3669        );
3670        insta::assert_snapshot!(
3671            p(Direction::Suffix, 1.hour()),
3672            @"01:00:00",
3673        );
3674        insta::assert_snapshot!(
3675            p(Direction::Auto, -1.hour()),
3676            @"-01:00:00",
3677        );
3678        insta::assert_snapshot!(
3679            p(Direction::Sign, -1.hour()),
3680            @"-01:00:00",
3681        );
3682        insta::assert_snapshot!(
3683            p(Direction::ForceSign, -1.hour()),
3684            @"-01:00:00",
3685        );
3686        insta::assert_snapshot!(
3687            p(Direction::Suffix, -1.hour()),
3688            @"01:00:00 ago",
3689        );
3690
3691        insta::assert_snapshot!(
3692            p(Direction::Auto, 1.day().hours(1)),
3693            @"1d 01:00:00",
3694        );
3695        insta::assert_snapshot!(
3696            p(Direction::Sign, 1.day().hours(1)),
3697            @"1d 01:00:00",
3698        );
3699        insta::assert_snapshot!(
3700            p(Direction::ForceSign, 1.day().hours(1)),
3701            @"+1d 01:00:00",
3702        );
3703        insta::assert_snapshot!(
3704            p(Direction::Suffix, 1.day().hours(1)),
3705            @"1d 01:00:00",
3706        );
3707        // This is the main change from above. With non-zero
3708        // calendar units, the default for expressing a negative
3709        // sign switches to a suffix in the HH:MM:SS format.
3710        insta::assert_snapshot!(
3711            p(Direction::Auto, -1.day().hours(1)),
3712            @"1d 01:00:00 ago",
3713        );
3714        insta::assert_snapshot!(
3715            p(Direction::Sign, -1.day().hours(1)),
3716            @"-1d 01:00:00",
3717        );
3718        insta::assert_snapshot!(
3719            p(Direction::ForceSign, -1.day().hours(1)),
3720            @"-1d 01:00:00",
3721        );
3722        insta::assert_snapshot!(
3723            p(Direction::Suffix, -1.day().hours(1)),
3724            @"1d 01:00:00 ago",
3725        );
3726    }
3727
3728    #[test]
3729    fn print_span_hms_fraction_auto() {
3730        let printer = || SpanPrinter::new().hours_minutes_seconds(true);
3731        let p = |span| printer().span_to_string(&span);
3732
3733        insta::assert_snapshot!(p(1.nanosecond()), @"00:00:00.000000001");
3734        insta::assert_snapshot!(p(-1.nanosecond()), @"-00:00:00.000000001");
3735        insta::assert_snapshot!(
3736            printer().direction(Direction::ForceSign).span_to_string(&1.nanosecond()),
3737            @"+00:00:00.000000001",
3738        );
3739
3740        insta::assert_snapshot!(
3741            p(1.second().nanoseconds(123)),
3742            @"00:00:01.000000123",
3743        );
3744        insta::assert_snapshot!(
3745            p(1.second().milliseconds(123)),
3746            @"00:00:01.123",
3747        );
3748        insta::assert_snapshot!(
3749            p(1.second().milliseconds(1_123)),
3750            @"00:00:02.123",
3751        );
3752        insta::assert_snapshot!(
3753            p(1.second().milliseconds(61_123)),
3754            @"00:00:62.123",
3755        );
3756    }
3757
3758    #[test]
3759    fn print_span_hms_fraction_fixed_precision() {
3760        let printer = || SpanPrinter::new().hours_minutes_seconds(true);
3761        let p = |precision, span| {
3762            printer().precision(Some(precision)).span_to_string(&span)
3763        };
3764
3765        insta::assert_snapshot!(p(3, 1.second()), @"00:00:01.000");
3766        insta::assert_snapshot!(
3767            p(3, 1.second().milliseconds(1)),
3768            @"00:00:01.001",
3769        );
3770        insta::assert_snapshot!(
3771            p(3, 1.second().milliseconds(123)),
3772            @"00:00:01.123",
3773        );
3774        insta::assert_snapshot!(
3775            p(3, 1.second().milliseconds(100)),
3776            @"00:00:01.100",
3777        );
3778
3779        insta::assert_snapshot!(p(0, 1.second()), @"00:00:01");
3780        insta::assert_snapshot!(p(0, 1.second().milliseconds(1)), @"00:00:01");
3781        insta::assert_snapshot!(
3782            p(1, 1.second().milliseconds(999)),
3783            @"00:00:01.9",
3784        );
3785    }
3786
3787    #[test]
3788    fn print_signed_duration_hms() {
3789        let printer = || SpanPrinter::new().hours_minutes_seconds(true);
3790        let p = |secs| {
3791            printer().duration_to_string(&SignedDuration::from_secs(secs))
3792        };
3793
3794        // Note the differences with `Span`, since with a `SignedDuration`,
3795        // all units are balanced.
3796
3797        insta::assert_snapshot!(p(1), @"00:00:01");
3798        insta::assert_snapshot!(p(2), @"00:00:02");
3799        insta::assert_snapshot!(p(10), @"00:00:10");
3800        insta::assert_snapshot!(p(100), @"00:01:40");
3801
3802        insta::assert_snapshot!(p(1 * 60), @"00:01:00");
3803        insta::assert_snapshot!(p(2 * 60), @"00:02:00");
3804        insta::assert_snapshot!(p(10 * 60), @"00:10:00");
3805        insta::assert_snapshot!(p(100 * 60), @"01:40:00");
3806
3807        insta::assert_snapshot!(p(1 * 60 * 60), @"01:00:00");
3808        insta::assert_snapshot!(p(2 * 60 * 60), @"02:00:00");
3809        insta::assert_snapshot!(p(10 * 60 * 60), @"10:00:00");
3810        insta::assert_snapshot!(p(100 * 60 * 60), @"100:00:00");
3811
3812        insta::assert_snapshot!(
3813            p(60 * 60 + 60 + 1),
3814            @"01:01:01",
3815        );
3816        insta::assert_snapshot!(
3817            p(2 * 60 * 60 + 2 * 60 + 2),
3818            @"02:02:02",
3819        );
3820        insta::assert_snapshot!(
3821            p(10 * 60 * 60 + 10 * 60 + 10),
3822            @"10:10:10",
3823        );
3824        insta::assert_snapshot!(
3825            p(100 * 60 * 60 + 100 * 60 + 100),
3826            @"101:41:40",
3827        );
3828    }
3829
3830    #[test]
3831    fn print_signed_duration_hms_sign() {
3832        let printer = |direction| {
3833            SpanPrinter::new().hours_minutes_seconds(true).direction(direction)
3834        };
3835        let p = |direction, secs| {
3836            printer(direction)
3837                .duration_to_string(&SignedDuration::from_secs(secs))
3838        };
3839
3840        insta::assert_snapshot!(p(Direction::Auto, 1), @"00:00:01");
3841        insta::assert_snapshot!(p(Direction::Sign, 1), @"00:00:01");
3842        insta::assert_snapshot!(p(Direction::ForceSign, 1), @"+00:00:01");
3843        insta::assert_snapshot!(p(Direction::Suffix, 1), @"00:00:01");
3844
3845        insta::assert_snapshot!(p(Direction::Auto, -1), @"-00:00:01");
3846        insta::assert_snapshot!(p(Direction::Sign, -1), @"-00:00:01");
3847        insta::assert_snapshot!(p(Direction::ForceSign, -1), @"-00:00:01");
3848        insta::assert_snapshot!(p(Direction::Suffix, -1), @"00:00:01 ago");
3849    }
3850
3851    #[test]
3852    fn print_signed_duration_hms_fraction_auto() {
3853        let printer = || SpanPrinter::new().hours_minutes_seconds(true);
3854        let p = |secs, nanos| {
3855            printer().duration_to_string(&SignedDuration::new(secs, nanos))
3856        };
3857
3858        insta::assert_snapshot!(p(0, 1), @"00:00:00.000000001");
3859        insta::assert_snapshot!(p(0, -1), @"-00:00:00.000000001");
3860        insta::assert_snapshot!(
3861            printer().direction(Direction::ForceSign).duration_to_string(
3862                &SignedDuration::new(0, 1),
3863            ),
3864            @"+00:00:00.000000001",
3865        );
3866
3867        insta::assert_snapshot!(
3868            p(1, 123),
3869            @"00:00:01.000000123",
3870        );
3871        insta::assert_snapshot!(
3872            p(1, 123_000_000),
3873            @"00:00:01.123",
3874        );
3875        insta::assert_snapshot!(
3876            p(1, 1_123_000_000),
3877            @"00:00:02.123",
3878        );
3879        insta::assert_snapshot!(
3880            p(61, 1_123_000_000),
3881            @"00:01:02.123",
3882        );
3883    }
3884
3885    #[test]
3886    fn print_signed_duration_hms_fraction_fixed_precision() {
3887        let printer = || SpanPrinter::new().hours_minutes_seconds(true);
3888        let p = |precision, secs, nanos| {
3889            printer()
3890                .precision(Some(precision))
3891                .duration_to_string(&SignedDuration::new(secs, nanos))
3892        };
3893
3894        insta::assert_snapshot!(p(3, 1, 0), @"00:00:01.000");
3895        insta::assert_snapshot!(
3896            p(3, 1, 1_000_000),
3897            @"00:00:01.001",
3898        );
3899        insta::assert_snapshot!(
3900            p(3, 1, 123_000_000),
3901            @"00:00:01.123",
3902        );
3903        insta::assert_snapshot!(
3904            p(3, 1, 100_000_000),
3905            @"00:00:01.100",
3906        );
3907
3908        insta::assert_snapshot!(p(0, 1, 0), @"00:00:01");
3909        insta::assert_snapshot!(p(0, 1, 1_000_000), @"00:00:01");
3910        insta::assert_snapshot!(
3911            p(1, 1, 999_000_000),
3912            @"00:00:01.9",
3913        );
3914    }
3915
3916    #[test]
3917    fn print_unsigned_duration_hms() {
3918        let printer = || SpanPrinter::new().hours_minutes_seconds(true);
3919        let p = |secs| {
3920            printer().unsigned_duration_to_string(
3921                &core::time::Duration::from_secs(secs),
3922            )
3923        };
3924
3925        // Note the differences with `Span`, since with a `Duration`,
3926        // all units are balanced.
3927
3928        insta::assert_snapshot!(p(1), @"00:00:01");
3929        insta::assert_snapshot!(p(2), @"00:00:02");
3930        insta::assert_snapshot!(p(10), @"00:00:10");
3931        insta::assert_snapshot!(p(100), @"00:01:40");
3932
3933        insta::assert_snapshot!(p(1 * 60), @"00:01:00");
3934        insta::assert_snapshot!(p(2 * 60), @"00:02:00");
3935        insta::assert_snapshot!(p(10 * 60), @"00:10:00");
3936        insta::assert_snapshot!(p(100 * 60), @"01:40:00");
3937
3938        insta::assert_snapshot!(p(1 * 60 * 60), @"01:00:00");
3939        insta::assert_snapshot!(p(2 * 60 * 60), @"02:00:00");
3940        insta::assert_snapshot!(p(10 * 60 * 60), @"10:00:00");
3941        insta::assert_snapshot!(p(100 * 60 * 60), @"100:00:00");
3942
3943        insta::assert_snapshot!(
3944            p(60 * 60 + 60 + 1),
3945            @"01:01:01",
3946        );
3947        insta::assert_snapshot!(
3948            p(2 * 60 * 60 + 2 * 60 + 2),
3949            @"02:02:02",
3950        );
3951        insta::assert_snapshot!(
3952            p(10 * 60 * 60 + 10 * 60 + 10),
3953            @"10:10:10",
3954        );
3955        insta::assert_snapshot!(
3956            p(100 * 60 * 60 + 100 * 60 + 100),
3957            @"101:41:40",
3958        );
3959    }
3960
3961    #[test]
3962    fn print_unsigned_duration_hms_sign() {
3963        let printer = |direction| {
3964            SpanPrinter::new().hours_minutes_seconds(true).direction(direction)
3965        };
3966        let p = |direction, secs| {
3967            printer(direction).unsigned_duration_to_string(
3968                &core::time::Duration::from_secs(secs),
3969            )
3970        };
3971
3972        insta::assert_snapshot!(p(Direction::Auto, 1), @"00:00:01");
3973        insta::assert_snapshot!(p(Direction::Sign, 1), @"00:00:01");
3974        insta::assert_snapshot!(p(Direction::ForceSign, 1), @"+00:00:01");
3975        insta::assert_snapshot!(p(Direction::Suffix, 1), @"00:00:01");
3976    }
3977
3978    #[test]
3979    fn print_unsigned_duration_hms_fraction_auto() {
3980        let printer = || SpanPrinter::new().hours_minutes_seconds(true);
3981        let p = |secs, nanos| {
3982            printer().unsigned_duration_to_string(&core::time::Duration::new(
3983                secs, nanos,
3984            ))
3985        };
3986
3987        insta::assert_snapshot!(p(0, 1), @"00:00:00.000000001");
3988        insta::assert_snapshot!(
3989            printer().direction(Direction::ForceSign).duration_to_string(
3990                &SignedDuration::new(0, 1),
3991            ),
3992            @"+00:00:00.000000001",
3993        );
3994
3995        insta::assert_snapshot!(
3996            p(1, 123),
3997            @"00:00:01.000000123",
3998        );
3999        insta::assert_snapshot!(
4000            p(1, 123_000_000),
4001            @"00:00:01.123",
4002        );
4003        insta::assert_snapshot!(
4004            p(1, 1_123_000_000),
4005            @"00:00:02.123",
4006        );
4007        insta::assert_snapshot!(
4008            p(61, 1_123_000_000),
4009            @"00:01:02.123",
4010        );
4011    }
4012
4013    #[test]
4014    fn print_unsigned_duration_hms_fraction_fixed_precision() {
4015        let printer = || SpanPrinter::new().hours_minutes_seconds(true);
4016        let p = |precision, secs, nanos| {
4017            printer().precision(Some(precision)).unsigned_duration_to_string(
4018                &core::time::Duration::new(secs, nanos),
4019            )
4020        };
4021
4022        insta::assert_snapshot!(p(3, 1, 0), @"00:00:01.000");
4023        insta::assert_snapshot!(
4024            p(3, 1, 1_000_000),
4025            @"00:00:01.001",
4026        );
4027        insta::assert_snapshot!(
4028            p(3, 1, 123_000_000),
4029            @"00:00:01.123",
4030        );
4031        insta::assert_snapshot!(
4032            p(3, 1, 100_000_000),
4033            @"00:00:01.100",
4034        );
4035
4036        insta::assert_snapshot!(p(0, 1, 0), @"00:00:01");
4037        insta::assert_snapshot!(p(0, 1, 1_000_000), @"00:00:01");
4038        insta::assert_snapshot!(
4039            p(1, 1, 999_000_000),
4040            @"00:00:01.9",
4041        );
4042    }
4043}