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: i64 = MINS_PER_HOUR * SECS_PER_MIN;
10const SECS_PER_MIN: i64 = 60;
11const MINS_PER_HOUR: i64 = 60;
12const NANOS_PER_HOUR: i128 =
13    (SECS_PER_MIN * MINS_PER_HOUR * NANOS_PER_SEC) as i128;
14const NANOS_PER_MIN: i128 = (SECS_PER_MIN * NANOS_PER_SEC) as i128;
15const NANOS_PER_SEC: i64 = 1_000_000_000;
16const NANOS_PER_MILLI: i32 = 1_000_000;
17const NANOS_PER_MICRO: i32 = 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    /// Print a `Span` to the given writer using the "friendly" format.
1039    ///
1040    /// # Errors
1041    ///
1042    /// This only returns an error when writing to the given [`Write`]
1043    /// implementation would fail. Some such implementations, like for `String`
1044    /// and `Vec<u8>`, never fail (unless memory allocation fails). In such
1045    /// cases, it would be appropriate to call `unwrap()` on the result.
1046    ///
1047    /// # Example
1048    ///
1049    /// ```
1050    /// use jiff::{fmt::friendly::SpanPrinter, ToSpan};
1051    ///
1052    /// static PRINTER: SpanPrinter = SpanPrinter::new();
1053    ///
1054    /// let span = 3.years().months(5);
1055    ///
1056    /// let mut buf = String::new();
1057    /// // Printing to a `String` can never fail.
1058    /// PRINTER.print_span(&span, &mut buf).unwrap();
1059    /// assert_eq!(buf, "3y 5mo");
1060    /// ```
1061    pub fn print_span<W: Write>(
1062        &self,
1063        span: &Span,
1064        wtr: W,
1065    ) -> Result<(), Error> {
1066        if self.hms {
1067            return self.print_span_hms(span, wtr);
1068        }
1069        self.print_span_designators(span, wtr)
1070    }
1071
1072    /// Print a `SignedDuration` to the given writer using the "friendly"
1073    /// format.
1074    ///
1075    /// This balances the units of the duration up to at most hours
1076    /// automatically.
1077    ///
1078    /// # Errors
1079    ///
1080    /// This only returns an error when writing to the given [`Write`]
1081    /// implementation would fail. Some such implementations, like for `String`
1082    /// and `Vec<u8>`, never fail (unless memory allocation fails). In such
1083    /// cases, it would be appropriate to call `unwrap()` on the result.
1084    ///
1085    /// # Example
1086    ///
1087    /// ```
1088    /// use jiff::{fmt::friendly::SpanPrinter, SignedDuration};
1089    ///
1090    /// static PRINTER: SpanPrinter = SpanPrinter::new();
1091    ///
1092    /// let dur = SignedDuration::new(86_525, 123_000_789);
1093    ///
1094    /// let mut buf = String::new();
1095    /// // Printing to a `String` can never fail.
1096    /// PRINTER.print_duration(&dur, &mut buf).unwrap();
1097    /// assert_eq!(buf, "24h 2m 5s 123ms 789ns");
1098    ///
1099    /// // Negative durations are supported.
1100    /// buf.clear();
1101    /// PRINTER.print_duration(&-dur, &mut buf).unwrap();
1102    /// assert_eq!(buf, "24h 2m 5s 123ms 789ns ago");
1103    /// ```
1104    pub fn print_duration<W: Write>(
1105        &self,
1106        duration: &SignedDuration,
1107        wtr: W,
1108    ) -> Result<(), Error> {
1109        if self.hms {
1110            return self.print_duration_hms(duration, wtr);
1111        }
1112        self.print_duration_designators(duration, wtr)
1113    }
1114
1115    fn print_span_designators<W: Write>(
1116        &self,
1117        span: &Span,
1118        mut wtr: W,
1119    ) -> Result<(), Error> {
1120        let mut wtr =
1121            DesignatorWriter::new(self, &mut wtr, false, span.signum());
1122        wtr.maybe_write_prefix_sign()?;
1123        match self.fractional {
1124            None => {
1125                self.print_span_designators_non_fraction(span, &mut wtr)?;
1126            }
1127            Some(unit) => {
1128                self.print_span_designators_fractional(span, unit, &mut wtr)?;
1129            }
1130        }
1131        wtr.maybe_write_zero()?;
1132        wtr.maybe_write_suffix_sign()?;
1133        Ok(())
1134    }
1135
1136    fn print_span_designators_non_fraction<'p, 'w, W: Write>(
1137        &self,
1138        span: &Span,
1139        wtr: &mut DesignatorWriter<'p, 'w, W>,
1140    ) -> Result<(), Error> {
1141        let span = span.abs();
1142        if span.get_years() != 0 {
1143            wtr.write(Unit::Year, span.get_years())?;
1144        }
1145        if span.get_months() != 0 {
1146            wtr.write(Unit::Month, span.get_months())?;
1147        }
1148        if span.get_weeks() != 0 {
1149            wtr.write(Unit::Week, span.get_weeks())?;
1150        }
1151        if span.get_days() != 0 {
1152            wtr.write(Unit::Day, span.get_days())?;
1153        }
1154        if span.get_hours() != 0 {
1155            wtr.write(Unit::Hour, span.get_hours())?;
1156        }
1157        if span.get_minutes() != 0 {
1158            wtr.write(Unit::Minute, span.get_minutes())?;
1159        }
1160        if span.get_seconds() != 0 {
1161            wtr.write(Unit::Second, span.get_seconds())?;
1162        }
1163        if span.get_milliseconds() != 0 {
1164            wtr.write(Unit::Millisecond, span.get_milliseconds())?;
1165        }
1166        if span.get_microseconds() != 0 {
1167            wtr.write(Unit::Microsecond, span.get_microseconds())?;
1168        }
1169        if span.get_nanoseconds() != 0 {
1170            wtr.write(Unit::Nanosecond, span.get_nanoseconds())?;
1171        }
1172        Ok(())
1173    }
1174
1175    #[inline(never)]
1176    fn print_span_designators_fractional<'p, 'w, W: Write>(
1177        &self,
1178        span: &Span,
1179        unit: FractionalUnit,
1180        wtr: &mut DesignatorWriter<'p, 'w, W>,
1181    ) -> Result<(), Error> {
1182        // OK because the biggest FractionalUnit is Hour, and there is always
1183        // a Unit bigger than hour.
1184        let split_at = Unit::from(unit).next().unwrap();
1185        let non_fractional = span.without_lower(split_at);
1186        let fractional = span.only_lower(split_at);
1187        self.print_span_designators_non_fraction(&non_fractional, wtr)?;
1188        wtr.write_fractional_duration(
1189            unit,
1190            &fractional.to_duration_invariant(),
1191        )?;
1192        Ok(())
1193    }
1194
1195    fn print_span_hms<W: Write>(
1196        &self,
1197        span: &Span,
1198        mut wtr: W,
1199    ) -> Result<(), Error> {
1200        let span_cal = span.only_calendar();
1201        let mut span_time = span.only_time();
1202        let has_cal = !span_cal.is_zero();
1203
1204        let mut wtr =
1205            DesignatorWriter::new(self, &mut wtr, has_cal, span.signum());
1206        wtr.maybe_write_prefix_sign()?;
1207        if has_cal {
1208            self.print_span_designators_non_fraction(&span_cal, &mut wtr)?;
1209            wtr.finish_preceding()?;
1210            // When spacing is disabled, then `finish_preceding` won't write
1211            // any spaces. But this would result in, e.g., `1yr15:00:00`, which
1212            // is just totally wrong. So detect that case here and insert a
1213            // space forcefully.
1214            if matches!(self.spacing, Spacing::None) {
1215                wtr.wtr.write_str(" ")?;
1216            }
1217        }
1218        span_time = span_time.abs();
1219
1220        let fmtint =
1221            DecimalFormatter::new().padding(self.padding.unwrap_or(2));
1222        let fmtfraction = FractionalFormatter::new().precision(self.precision);
1223        wtr.wtr.write_int(&fmtint, span_time.get_hours_ranged().get())?;
1224        wtr.wtr.write_str(":")?;
1225        wtr.wtr.write_int(&fmtint, span_time.get_minutes_ranged().get())?;
1226        wtr.wtr.write_str(":")?;
1227        let fp = FractionalPrinter::from_span(
1228            &span_time.only_lower(Unit::Minute),
1229            FractionalUnit::Second,
1230            fmtint,
1231            fmtfraction,
1232        );
1233        fp.print(&mut wtr.wtr)?;
1234        wtr.maybe_write_suffix_sign()?;
1235        Ok(())
1236    }
1237
1238    fn print_duration_designators<W: Write>(
1239        &self,
1240        dur: &SignedDuration,
1241        mut wtr: W,
1242    ) -> Result<(), Error> {
1243        let mut wtr =
1244            DesignatorWriter::new(self, &mut wtr, false, dur.signum());
1245        wtr.maybe_write_prefix_sign()?;
1246        match self.fractional {
1247            None => {
1248                let mut secs = dur.as_secs();
1249                wtr.write(Unit::Hour, (secs / SECS_PER_HOUR).abs())?;
1250                secs %= MINS_PER_HOUR * SECS_PER_MIN;
1251                wtr.write(Unit::Minute, (secs / SECS_PER_MIN).abs())?;
1252                wtr.write(Unit::Second, (secs % SECS_PER_MIN).abs())?;
1253                let mut nanos = dur.subsec_nanos();
1254                wtr.write(Unit::Millisecond, (nanos / NANOS_PER_MILLI).abs())?;
1255                nanos %= NANOS_PER_MILLI;
1256                wtr.write(Unit::Microsecond, (nanos / NANOS_PER_MICRO).abs())?;
1257                wtr.write(Unit::Nanosecond, (nanos % NANOS_PER_MICRO).abs())?;
1258            }
1259            Some(FractionalUnit::Hour) => {
1260                wtr.write_fractional_duration(FractionalUnit::Hour, dur)?;
1261            }
1262            Some(FractionalUnit::Minute) => {
1263                let mut secs = dur.as_secs();
1264                wtr.write(Unit::Hour, (secs / SECS_PER_HOUR).abs())?;
1265                secs %= MINS_PER_HOUR * SECS_PER_MIN;
1266
1267                let leftovers = SignedDuration::new(secs, dur.subsec_nanos());
1268                wtr.write_fractional_duration(
1269                    FractionalUnit::Minute,
1270                    &leftovers,
1271                )?;
1272            }
1273            Some(FractionalUnit::Second) => {
1274                let mut secs = dur.as_secs();
1275                wtr.write(Unit::Hour, (secs / SECS_PER_HOUR).abs())?;
1276                secs %= MINS_PER_HOUR * SECS_PER_MIN;
1277                wtr.write(Unit::Minute, (secs / SECS_PER_MIN).abs())?;
1278                secs %= SECS_PER_MIN;
1279
1280                // Absolute value is OK because -59<=secs<=59 and nanoseconds
1281                // can never be i32::MIN.
1282                let leftovers =
1283                    SignedDuration::new(secs, dur.subsec_nanos()).abs();
1284                wtr.write_fractional_duration(
1285                    FractionalUnit::Second,
1286                    &leftovers,
1287                )?;
1288            }
1289            Some(FractionalUnit::Millisecond) => {
1290                let mut secs = dur.as_secs();
1291                wtr.write(Unit::Hour, (secs / SECS_PER_HOUR).abs())?;
1292                secs %= MINS_PER_HOUR * SECS_PER_MIN;
1293                wtr.write(Unit::Minute, (secs / SECS_PER_MIN).abs())?;
1294                wtr.write(Unit::Second, (secs % SECS_PER_MIN).abs())?;
1295
1296                let leftovers =
1297                    SignedDuration::new(0, dur.subsec_nanos().abs());
1298                wtr.write_fractional_duration(
1299                    FractionalUnit::Millisecond,
1300                    &leftovers,
1301                )?;
1302            }
1303            Some(FractionalUnit::Microsecond) => {
1304                let mut secs = dur.as_secs();
1305                wtr.write(Unit::Hour, (secs / SECS_PER_HOUR).abs())?;
1306                secs %= MINS_PER_HOUR * SECS_PER_MIN;
1307                wtr.write(Unit::Minute, (secs / SECS_PER_MIN).abs())?;
1308                wtr.write(Unit::Second, (secs % SECS_PER_MIN).abs())?;
1309                let mut nanos = dur.subsec_nanos();
1310                wtr.write(Unit::Millisecond, (nanos / NANOS_PER_MILLI).abs())?;
1311                nanos %= NANOS_PER_MILLI;
1312
1313                let leftovers = SignedDuration::new(0, nanos.abs());
1314                wtr.write_fractional_duration(
1315                    FractionalUnit::Microsecond,
1316                    &leftovers,
1317                )?;
1318            }
1319        }
1320        wtr.maybe_write_zero()?;
1321        wtr.maybe_write_suffix_sign()?;
1322        Ok(())
1323    }
1324
1325    fn print_duration_hms<W: Write>(
1326        &self,
1327        dur: &SignedDuration,
1328        mut wtr: W,
1329    ) -> Result<(), Error> {
1330        // N.B. It should be technically correct to convert a
1331        // `SignedDuration` to `Span` (since this process balances)
1332        // and then format the `Span` as-is. But this doesn't work
1333        // because the range of a `SignedDuration` is much bigger.
1334
1335        let fmtint =
1336            DecimalFormatter::new().padding(self.padding.unwrap_or(2));
1337        let fmtfraction = FractionalFormatter::new().precision(self.precision);
1338
1339        if dur.is_negative() {
1340            if !matches!(self.direction, Direction::Suffix) {
1341                wtr.write_str("-")?;
1342            }
1343        } else if let Direction::ForceSign = self.direction {
1344            wtr.write_str("+")?;
1345        }
1346        let mut secs = dur.as_secs();
1347        // OK because guaranteed to be bigger than i64::MIN.
1348        let hours = (secs / (MINS_PER_HOUR * SECS_PER_MIN)).abs();
1349        secs %= MINS_PER_HOUR * SECS_PER_MIN;
1350        // OK because guaranteed to be bigger than i64::MIN.
1351        let minutes = (secs / SECS_PER_MIN).abs();
1352        // OK because guaranteed to be bigger than i64::MIN.
1353        secs = (secs % SECS_PER_MIN).abs();
1354
1355        wtr.write_int(&fmtint, hours)?;
1356        wtr.write_str(":")?;
1357        wtr.write_int(&fmtint, minutes)?;
1358        wtr.write_str(":")?;
1359        let fp = FractionalPrinter::from_duration(
1360            // OK because -999_999_999 <= nanos <= 999_999_999 and secs < 60.
1361            &SignedDuration::new(secs, dur.subsec_nanos().abs()),
1362            FractionalUnit::Second,
1363            fmtint,
1364            fmtfraction,
1365        );
1366        fp.print(&mut wtr)?;
1367        if dur.is_negative() {
1368            if matches!(self.direction, Direction::Suffix) {
1369                wtr.write_str(" ago")?;
1370            }
1371        }
1372        Ok(())
1373    }
1374}
1375
1376impl Default for SpanPrinter {
1377    fn default() -> SpanPrinter {
1378        SpanPrinter::new()
1379    }
1380}
1381
1382/// A type that represents the designator choice.
1383///
1384/// Basically, whether we want verbose, short or compact designators. This in
1385/// turn permits lookups based on `Unit`, which makes writing generic code for
1386/// writing designators a bit nicer and still fast.
1387#[derive(Debug)]
1388struct Designators {
1389    singular: &'static [&'static str],
1390    plural: &'static [&'static str],
1391}
1392
1393impl Designators {
1394    const VERBOSE_SINGULAR: &'static [&'static str] = &[
1395        "nanosecond",
1396        "microsecond",
1397        "millisecond",
1398        "second",
1399        "minute",
1400        "hour",
1401        "day",
1402        "week",
1403        "month",
1404        "year",
1405    ];
1406    const VERBOSE_PLURAL: &'static [&'static str] = &[
1407        "nanoseconds",
1408        "microseconds",
1409        "milliseconds",
1410        "seconds",
1411        "minutes",
1412        "hours",
1413        "days",
1414        "weeks",
1415        "months",
1416        "years",
1417    ];
1418
1419    const SHORT_SINGULAR: &'static [&'static str] =
1420        &["nsec", "µsec", "msec", "sec", "min", "hr", "day", "wk", "mo", "yr"];
1421    const SHORT_PLURAL: &'static [&'static str] = &[
1422        "nsecs", "µsecs", "msecs", "secs", "mins", "hrs", "days", "wks",
1423        "mos", "yrs",
1424    ];
1425
1426    const COMPACT: &'static [&'static str] =
1427        &["ns", "µs", "ms", "s", "m", "h", "d", "w", "mo", "y"];
1428
1429    const HUMAN_TIME_SINGULAR: &'static [&'static str] =
1430        &["ns", "us", "ms", "s", "m", "h", "d", "w", "month", "y"];
1431    const HUMAN_TIME_PLURAL: &'static [&'static str] =
1432        &["ns", "us", "ms", "s", "m", "h", "d", "w", "months", "y"];
1433
1434    fn new(config: Designator) -> Designators {
1435        match config {
1436            Designator::Verbose => Designators {
1437                singular: Designators::VERBOSE_SINGULAR,
1438                plural: Designators::VERBOSE_PLURAL,
1439            },
1440            Designator::Short => Designators {
1441                singular: Designators::SHORT_SINGULAR,
1442                plural: Designators::SHORT_PLURAL,
1443            },
1444            Designator::Compact => Designators {
1445                singular: Designators::COMPACT,
1446                plural: Designators::COMPACT,
1447            },
1448            Designator::HumanTime => Designators {
1449                singular: Designators::HUMAN_TIME_SINGULAR,
1450                plural: Designators::HUMAN_TIME_PLURAL,
1451            },
1452        }
1453    }
1454
1455    fn designator(&self, unit: impl Into<Unit>, plural: bool) -> &'static str {
1456        let unit = unit.into();
1457        let index = unit as usize;
1458        if plural {
1459            self.plural[index]
1460        } else {
1461            self.singular[index]
1462        }
1463    }
1464}
1465
1466/// An abstraction for writing the "designator" variant of the friendly format.
1467///
1468/// This takes care of computing some initial state and keeping track of some
1469/// mutable state that influences printing. For example, whether to write a
1470/// delimiter or not (one should only come after a unit that has been written).
1471#[derive(Debug)]
1472struct DesignatorWriter<'p, 'w, W> {
1473    printer: &'p SpanPrinter,
1474    wtr: &'w mut W,
1475    desig: Designators,
1476    sign: Option<DirectionSign>,
1477    fmtint: DecimalFormatter,
1478    fmtfraction: FractionalFormatter,
1479    written_non_zero_unit: bool,
1480}
1481
1482impl<'p, 'w, W: Write> DesignatorWriter<'p, 'w, W> {
1483    fn new(
1484        printer: &'p SpanPrinter,
1485        wtr: &'w mut W,
1486        has_calendar: bool,
1487        signum: i8,
1488    ) -> DesignatorWriter<'p, 'w, W> {
1489        let desig = Designators::new(printer.designator);
1490        let sign = printer.direction.sign(printer, has_calendar, signum);
1491        let fmtint =
1492            DecimalFormatter::new().padding(printer.padding.unwrap_or(0));
1493        let fmtfraction =
1494            FractionalFormatter::new().precision(printer.precision);
1495        DesignatorWriter {
1496            printer,
1497            wtr,
1498            desig,
1499            sign,
1500            fmtint,
1501            fmtfraction,
1502            written_non_zero_unit: false,
1503        }
1504    }
1505
1506    fn maybe_write_prefix_sign(&mut self) -> Result<(), Error> {
1507        if let Some(DirectionSign::Prefix(sign)) = self.sign {
1508            self.wtr.write_str(sign)?;
1509        }
1510        Ok(())
1511    }
1512
1513    fn maybe_write_suffix_sign(&mut self) -> Result<(), Error> {
1514        if let Some(DirectionSign::Suffix(sign)) = self.sign {
1515            self.wtr.write_str(sign)?;
1516        }
1517        Ok(())
1518    }
1519
1520    fn maybe_write_zero(&mut self) -> Result<(), Error> {
1521        if self.written_non_zero_unit {
1522            return Ok(());
1523        }
1524        // If a fractional unit is set, then we should use that unit
1525        // specifically to express "zero."
1526        let unit = self
1527            .printer
1528            .fractional
1529            .map(Unit::from)
1530            .unwrap_or(self.printer.zero_unit);
1531        self.wtr.write_int(&self.fmtint, 0)?;
1532        self.wtr
1533            .write_str(self.printer.spacing.between_units_and_designators())?;
1534        self.wtr.write_str(self.desig.designator(unit, true))?;
1535        Ok(())
1536    }
1537
1538    fn write(
1539        &mut self,
1540        unit: Unit,
1541        value: impl Into<i64>,
1542    ) -> Result<(), Error> {
1543        let value = value.into();
1544        if value == 0 {
1545            return Ok(());
1546        }
1547        self.finish_preceding()?;
1548        self.written_non_zero_unit = true;
1549        self.wtr.write_int(&self.fmtint, value)?;
1550        self.wtr
1551            .write_str(self.printer.spacing.between_units_and_designators())?;
1552        self.wtr.write_str(self.desig.designator(unit, value != 1))?;
1553        Ok(())
1554    }
1555
1556    fn write_fractional_duration(
1557        &mut self,
1558        unit: FractionalUnit,
1559        duration: &SignedDuration,
1560    ) -> Result<(), Error> {
1561        let fp = FractionalPrinter::from_duration(
1562            duration,
1563            unit,
1564            self.fmtint,
1565            self.fmtfraction,
1566        );
1567        if !fp.must_write_digits() {
1568            return Ok(());
1569        }
1570        self.finish_preceding()?;
1571        self.written_non_zero_unit = true;
1572        fp.print(&mut *self.wtr)?;
1573        self.wtr
1574            .write_str(self.printer.spacing.between_units_and_designators())?;
1575        self.wtr.write_str(self.desig.designator(unit, fp.is_plural()))?;
1576        Ok(())
1577    }
1578
1579    fn finish_preceding(&mut self) -> Result<(), Error> {
1580        if self.written_non_zero_unit {
1581            if self.printer.comma_after_designator {
1582                self.wtr.write_str(",")?;
1583            }
1584            self.wtr.write_str(self.printer.spacing.between_units())?;
1585        }
1586        Ok(())
1587    }
1588}
1589
1590/// A printer for a fraction with an integer and fraction component.
1591///
1592/// This also includes the formatter for the integer component and the
1593/// formatter for the fractional component.
1594struct FractionalPrinter {
1595    integer: i64,
1596    fraction: i64,
1597    fmtint: DecimalFormatter,
1598    fmtfraction: FractionalFormatter,
1599}
1600
1601impl FractionalPrinter {
1602    /// Build a fractional printer for the `Span` given. This includes the `.`.
1603    ///
1604    /// Callers must ensure that all units greater than `FractionalUnit` are
1605    /// zero in the span given.
1606    ///
1607    /// Note that the printer returned only prints a fractional component
1608    /// if necessary. For example, if the fractional component is zero and
1609    /// precision is `None`, or if `precision` is `Some(0)`, then no fractional
1610    /// component will be emitted.
1611    fn from_span(
1612        span: &Span,
1613        unit: FractionalUnit,
1614        fmtint: DecimalFormatter,
1615        fmtfraction: FractionalFormatter,
1616    ) -> FractionalPrinter {
1617        debug_assert!(span.largest_unit() <= Unit::from(unit));
1618        let dur = span.to_duration_invariant();
1619        FractionalPrinter::from_duration(&dur, unit, fmtint, fmtfraction)
1620    }
1621
1622    /// Like `from_span`, but for `SignedDuration`.
1623    fn from_duration(
1624        dur: &SignedDuration,
1625        unit: FractionalUnit,
1626        fmtint: DecimalFormatter,
1627        fmtfraction: FractionalFormatter,
1628    ) -> FractionalPrinter {
1629        // Should we assume `dur` is non-negative in this context?
1630        // I don't think we can in general, because `dur` could
1631        // be `SignedDuration::MIN` in the case where `unit` is
1632        // `FractionalUnit::Hour`. In this case, the caller can't call `abs`
1633        // because it would panic.
1634        match unit {
1635            FractionalUnit::Hour => {
1636                let integer = (dur.as_secs() / SECS_PER_HOUR).abs();
1637                let fraction = dur.as_nanos() % NANOS_PER_HOUR;
1638                // OK because NANOS_PER_HOUR fits in an i64.
1639                debug_assert!(fraction <= i128::from(i64::MAX));
1640                let mut fraction = i64::try_from(fraction).unwrap();
1641                // Drop precision since we're only allowed 9 decimal places.
1642                fraction /= SECS_PER_HOUR;
1643                // OK because fraction can't be i64::MIN.
1644                fraction = fraction.abs();
1645                FractionalPrinter { integer, fraction, fmtint, fmtfraction }
1646            }
1647            FractionalUnit::Minute => {
1648                let integer = (dur.as_secs() / SECS_PER_MIN).abs();
1649                let fraction = dur.as_nanos() % NANOS_PER_MIN;
1650                // OK because NANOS_PER_HOUR fits in an i64.
1651                debug_assert!(fraction <= i128::from(i64::MAX));
1652                let mut fraction = i64::try_from(fraction).unwrap();
1653                // Drop precision since we're only allowed 9 decimal places.
1654                fraction /= SECS_PER_MIN;
1655                // OK because fraction can't be i64::MIN.
1656                fraction = fraction.abs();
1657                FractionalPrinter { integer, fraction, fmtint, fmtfraction }
1658            }
1659            FractionalUnit::Second => {
1660                let integer = dur.as_secs();
1661                let fraction = i64::from(dur.subsec_nanos());
1662                FractionalPrinter { integer, fraction, fmtint, fmtfraction }
1663            }
1664            FractionalUnit::Millisecond => {
1665                // Unwrap is OK, but this is subtle. For printing a
1666                // SignedDuration, as_millis() can never return anything
1667                // bigger than 1 second. So that case is clearly okay. But
1668                // for printing a Span, it can, since spans can be totally
1669                // unbalanced. But Spans have limits on their units such that
1670                // each will fit into an i64. So this is also okay in that case
1671                // too.
1672                let integer = i64::try_from(dur.as_millis()).unwrap();
1673                let fraction =
1674                    i64::from((dur.subsec_nanos() % NANOS_PER_MILLI) * 1_000);
1675                FractionalPrinter { integer, fraction, fmtint, fmtfraction }
1676            }
1677            FractionalUnit::Microsecond => {
1678                // Unwrap is OK, but this is subtle. For printing a
1679                // SignedDuration, as_micros() can never return anything
1680                // bigger than 1 millisecond. So that case is clearly okay. But
1681                // for printing a Span, it can, since spans can be totally
1682                // unbalanced. But Spans have limits on their units such that
1683                // each will fit into an i64. So this is also okay in that case
1684                // too.
1685                let integer = i64::try_from(dur.as_micros()).unwrap();
1686                let fraction = i64::from(
1687                    (dur.subsec_nanos() % NANOS_PER_MICRO) * 1_000_000,
1688                );
1689                FractionalPrinter { integer, fraction, fmtint, fmtfraction }
1690            }
1691        }
1692    }
1693
1694    /// Returns true if both the integer and fractional component are zero.
1695    fn is_zero(&self) -> bool {
1696        self.integer == 0 && self.fraction == 0
1697    }
1698
1699    /// Returns true if this integer/fraction should be considered plural
1700    /// when choosing what designator to use.
1701    fn is_plural(&self) -> bool {
1702        self.integer != 1
1703            || (self.fraction != 0
1704                && !self.fmtfraction.has_zero_fixed_precision())
1705    }
1706
1707    /// Returns true if and only if this printer must write some kind of number
1708    /// when `print` is called.
1709    ///
1710    /// The only case where this returns `false` is when both the integer and
1711    /// fractional component are zero *and* the precision is fixed to a number
1712    /// greater than zero.
1713    fn must_write_digits(&self) -> bool {
1714        !self.is_zero() || self.fmtfraction.has_non_zero_fixed_precision()
1715    }
1716
1717    /// Prints the integer and optional fractional component.
1718    ///
1719    /// This will always print the integer, even if it's zero. Therefore, if
1720    /// the caller wants to omit printing zero, the caller should do their own
1721    /// conditional logic.
1722    fn print<W: Write>(&self, mut wtr: W) -> Result<(), Error> {
1723        wtr.write_int(&self.fmtint, self.integer)?;
1724        if self.fmtfraction.will_write_digits(self.fraction) {
1725            wtr.write_str(".")?;
1726            wtr.write_fraction(&self.fmtfraction, self.fraction)?;
1727        }
1728        Ok(())
1729    }
1730}
1731
1732#[cfg(test)]
1733mod tests {
1734    use crate::ToSpan;
1735
1736    use super::*;
1737
1738    #[test]
1739    fn print_span_designator_default() {
1740        let printer = || SpanPrinter::new();
1741        let p = |span| printer().span_to_string(&span);
1742
1743        insta::assert_snapshot!(p(1.second()), @"1s");
1744        insta::assert_snapshot!(p(2.seconds()), @"2s");
1745        insta::assert_snapshot!(p(10.seconds()), @"10s");
1746        insta::assert_snapshot!(p(1.minute().seconds(40)), @"1m 40s");
1747
1748        insta::assert_snapshot!(p(1.minute()), @"1m");
1749        insta::assert_snapshot!(p(2.minutes()), @"2m");
1750        insta::assert_snapshot!(p(10.minutes()), @"10m");
1751        insta::assert_snapshot!(p(1.hour().minutes(40)), @"1h 40m");
1752
1753        insta::assert_snapshot!(p(1.hour()), @"1h");
1754        insta::assert_snapshot!(p(2.hours()), @"2h");
1755        insta::assert_snapshot!(p(10.hours()), @"10h");
1756        insta::assert_snapshot!(p(100.hours()), @"100h");
1757
1758        insta::assert_snapshot!(
1759            p(1.hour().minutes(1).seconds(1)),
1760            @"1h 1m 1s",
1761        );
1762        insta::assert_snapshot!(
1763            p(2.hours().minutes(2).seconds(2)),
1764            @"2h 2m 2s",
1765        );
1766        insta::assert_snapshot!(
1767            p(10.hours().minutes(10).seconds(10)),
1768            @"10h 10m 10s",
1769        );
1770        insta::assert_snapshot!(
1771            p(100.hours().minutes(100).seconds(100)),
1772            @"100h 100m 100s",
1773        );
1774
1775        insta::assert_snapshot!(p(-1.hour()), @"1h ago");
1776        insta::assert_snapshot!(p(-1.hour().seconds(30)), @"1h 30s ago");
1777
1778        insta::assert_snapshot!(
1779            p(1.second().milliseconds(2000)),
1780            @"1s 2000ms",
1781        );
1782    }
1783
1784    #[test]
1785    fn print_span_designator_verbose() {
1786        let printer = || SpanPrinter::new().designator(Designator::Verbose);
1787        let p = |span| printer().span_to_string(&span);
1788
1789        insta::assert_snapshot!(p(1.second()), @"1second");
1790        insta::assert_snapshot!(p(2.seconds()), @"2seconds");
1791        insta::assert_snapshot!(p(10.seconds()), @"10seconds");
1792        insta::assert_snapshot!(p(1.minute().seconds(40)), @"1minute 40seconds");
1793
1794        insta::assert_snapshot!(p(1.minute()), @"1minute");
1795        insta::assert_snapshot!(p(2.minutes()), @"2minutes");
1796        insta::assert_snapshot!(p(10.minutes()), @"10minutes");
1797        insta::assert_snapshot!(p(1.hour().minutes(40)), @"1hour 40minutes");
1798
1799        insta::assert_snapshot!(p(1.hour()), @"1hour");
1800        insta::assert_snapshot!(p(2.hours()), @"2hours");
1801        insta::assert_snapshot!(p(10.hours()), @"10hours");
1802        insta::assert_snapshot!(p(100.hours()), @"100hours");
1803
1804        insta::assert_snapshot!(
1805            p(1.hour().minutes(1).seconds(1)),
1806            @"1hour 1minute 1second",
1807        );
1808        insta::assert_snapshot!(
1809            p(2.hours().minutes(2).seconds(2)),
1810            @"2hours 2minutes 2seconds",
1811        );
1812        insta::assert_snapshot!(
1813            p(10.hours().minutes(10).seconds(10)),
1814            @"10hours 10minutes 10seconds",
1815        );
1816        insta::assert_snapshot!(
1817            p(100.hours().minutes(100).seconds(100)),
1818            @"100hours 100minutes 100seconds",
1819        );
1820
1821        insta::assert_snapshot!(p(-1.hour()), @"1hour ago");
1822        insta::assert_snapshot!(p(-1.hour().seconds(30)), @"1hour 30seconds ago");
1823    }
1824
1825    #[test]
1826    fn print_span_designator_short() {
1827        let printer = || SpanPrinter::new().designator(Designator::Short);
1828        let p = |span| printer().span_to_string(&span);
1829
1830        insta::assert_snapshot!(p(1.second()), @"1sec");
1831        insta::assert_snapshot!(p(2.seconds()), @"2secs");
1832        insta::assert_snapshot!(p(10.seconds()), @"10secs");
1833        insta::assert_snapshot!(p(1.minute().seconds(40)), @"1min 40secs");
1834
1835        insta::assert_snapshot!(p(1.minute()), @"1min");
1836        insta::assert_snapshot!(p(2.minutes()), @"2mins");
1837        insta::assert_snapshot!(p(10.minutes()), @"10mins");
1838        insta::assert_snapshot!(p(1.hour().minutes(40)), @"1hr 40mins");
1839
1840        insta::assert_snapshot!(p(1.hour()), @"1hr");
1841        insta::assert_snapshot!(p(2.hours()), @"2hrs");
1842        insta::assert_snapshot!(p(10.hours()), @"10hrs");
1843        insta::assert_snapshot!(p(100.hours()), @"100hrs");
1844
1845        insta::assert_snapshot!(
1846            p(1.hour().minutes(1).seconds(1)),
1847            @"1hr 1min 1sec",
1848        );
1849        insta::assert_snapshot!(
1850            p(2.hours().minutes(2).seconds(2)),
1851            @"2hrs 2mins 2secs",
1852        );
1853        insta::assert_snapshot!(
1854            p(10.hours().minutes(10).seconds(10)),
1855            @"10hrs 10mins 10secs",
1856        );
1857        insta::assert_snapshot!(
1858            p(100.hours().minutes(100).seconds(100)),
1859            @"100hrs 100mins 100secs",
1860        );
1861
1862        insta::assert_snapshot!(p(-1.hour()), @"1hr ago");
1863        insta::assert_snapshot!(p(-1.hour().seconds(30)), @"1hr 30secs ago");
1864    }
1865
1866    #[test]
1867    fn print_span_designator_compact() {
1868        let printer = || SpanPrinter::new().designator(Designator::Compact);
1869        let p = |span| printer().span_to_string(&span);
1870
1871        insta::assert_snapshot!(p(1.second()), @"1s");
1872        insta::assert_snapshot!(p(2.seconds()), @"2s");
1873        insta::assert_snapshot!(p(10.seconds()), @"10s");
1874        insta::assert_snapshot!(p(1.minute().seconds(40)), @"1m 40s");
1875
1876        insta::assert_snapshot!(p(1.minute()), @"1m");
1877        insta::assert_snapshot!(p(2.minutes()), @"2m");
1878        insta::assert_snapshot!(p(10.minutes()), @"10m");
1879        insta::assert_snapshot!(p(1.hour().minutes(40)), @"1h 40m");
1880
1881        insta::assert_snapshot!(p(1.hour()), @"1h");
1882        insta::assert_snapshot!(p(2.hours()), @"2h");
1883        insta::assert_snapshot!(p(10.hours()), @"10h");
1884        insta::assert_snapshot!(p(100.hours()), @"100h");
1885
1886        insta::assert_snapshot!(
1887            p(1.hour().minutes(1).seconds(1)),
1888            @"1h 1m 1s",
1889        );
1890        insta::assert_snapshot!(
1891            p(2.hours().minutes(2).seconds(2)),
1892            @"2h 2m 2s",
1893        );
1894        insta::assert_snapshot!(
1895            p(10.hours().minutes(10).seconds(10)),
1896            @"10h 10m 10s",
1897        );
1898        insta::assert_snapshot!(
1899            p(100.hours().minutes(100).seconds(100)),
1900            @"100h 100m 100s",
1901        );
1902
1903        insta::assert_snapshot!(p(-1.hour()), @"1h ago");
1904        insta::assert_snapshot!(p(-1.hour().seconds(30)), @"1h 30s ago");
1905    }
1906
1907    #[test]
1908    fn print_span_designator_direction_force() {
1909        let printer = || SpanPrinter::new().direction(Direction::ForceSign);
1910        let p = |span| printer().span_to_string(&span);
1911
1912        insta::assert_snapshot!(p(1.second()), @"+1s");
1913        insta::assert_snapshot!(p(2.seconds()), @"+2s");
1914        insta::assert_snapshot!(p(10.seconds()), @"+10s");
1915        insta::assert_snapshot!(p(1.minute().seconds(40)), @"+1m 40s");
1916
1917        insta::assert_snapshot!(p(1.minute()), @"+1m");
1918        insta::assert_snapshot!(p(2.minutes()), @"+2m");
1919        insta::assert_snapshot!(p(10.minutes()), @"+10m");
1920        insta::assert_snapshot!(p(1.hour().minutes(40)), @"+1h 40m");
1921
1922        insta::assert_snapshot!(p(1.hour()), @"+1h");
1923        insta::assert_snapshot!(p(2.hours()), @"+2h");
1924        insta::assert_snapshot!(p(10.hours()), @"+10h");
1925        insta::assert_snapshot!(p(100.hours()), @"+100h");
1926
1927        insta::assert_snapshot!(
1928            p(1.hour().minutes(1).seconds(1)),
1929            @"+1h 1m 1s",
1930        );
1931        insta::assert_snapshot!(
1932            p(2.hours().minutes(2).seconds(2)),
1933            @"+2h 2m 2s",
1934        );
1935        insta::assert_snapshot!(
1936            p(10.hours().minutes(10).seconds(10)),
1937            @"+10h 10m 10s",
1938        );
1939        insta::assert_snapshot!(
1940            p(100.hours().minutes(100).seconds(100)),
1941            @"+100h 100m 100s",
1942        );
1943
1944        insta::assert_snapshot!(p(-1.hour()), @"-1h");
1945        insta::assert_snapshot!(p(-1.hour().seconds(30)), @"-1h 30s");
1946    }
1947
1948    #[test]
1949    fn print_span_designator_padding() {
1950        let printer = || SpanPrinter::new().padding(2);
1951        let p = |span| printer().span_to_string(&span);
1952
1953        insta::assert_snapshot!(p(1.second()), @"01s");
1954        insta::assert_snapshot!(p(2.seconds()), @"02s");
1955        insta::assert_snapshot!(p(10.seconds()), @"10s");
1956        insta::assert_snapshot!(p(1.minute().seconds(40)), @"01m 40s");
1957
1958        insta::assert_snapshot!(p(1.minute()), @"01m");
1959        insta::assert_snapshot!(p(2.minutes()), @"02m");
1960        insta::assert_snapshot!(p(10.minutes()), @"10m");
1961        insta::assert_snapshot!(p(1.hour().minutes(40)), @"01h 40m");
1962
1963        insta::assert_snapshot!(p(1.hour()), @"01h");
1964        insta::assert_snapshot!(p(2.hours()), @"02h");
1965        insta::assert_snapshot!(p(10.hours()), @"10h");
1966        insta::assert_snapshot!(p(100.hours()), @"100h");
1967
1968        insta::assert_snapshot!(
1969            p(1.hour().minutes(1).seconds(1)),
1970            @"01h 01m 01s",
1971        );
1972        insta::assert_snapshot!(
1973            p(2.hours().minutes(2).seconds(2)),
1974            @"02h 02m 02s",
1975        );
1976        insta::assert_snapshot!(
1977            p(10.hours().minutes(10).seconds(10)),
1978            @"10h 10m 10s",
1979        );
1980        insta::assert_snapshot!(
1981            p(100.hours().minutes(100).seconds(100)),
1982            @"100h 100m 100s",
1983        );
1984
1985        insta::assert_snapshot!(p(-1.hour()), @"01h ago");
1986        insta::assert_snapshot!(p(-1.hour().seconds(30)), @"01h 30s ago");
1987    }
1988
1989    #[test]
1990    fn print_span_designator_spacing_none() {
1991        let printer = || SpanPrinter::new().spacing(Spacing::None);
1992        let p = |span| printer().span_to_string(&span);
1993
1994        insta::assert_snapshot!(p(1.second()), @"1s");
1995        insta::assert_snapshot!(p(2.seconds()), @"2s");
1996        insta::assert_snapshot!(p(10.seconds()), @"10s");
1997        insta::assert_snapshot!(p(1.minute().seconds(40)), @"1m40s");
1998
1999        insta::assert_snapshot!(p(1.minute()), @"1m");
2000        insta::assert_snapshot!(p(2.minutes()), @"2m");
2001        insta::assert_snapshot!(p(10.minutes()), @"10m");
2002        insta::assert_snapshot!(p(1.hour().minutes(40)), @"1h40m");
2003
2004        insta::assert_snapshot!(p(1.hour()), @"1h");
2005        insta::assert_snapshot!(p(2.hours()), @"2h");
2006        insta::assert_snapshot!(p(10.hours()), @"10h");
2007        insta::assert_snapshot!(p(100.hours()), @"100h");
2008
2009        insta::assert_snapshot!(
2010            p(1.hour().minutes(1).seconds(1)),
2011            @"1h1m1s",
2012        );
2013        insta::assert_snapshot!(
2014            p(2.hours().minutes(2).seconds(2)),
2015            @"2h2m2s",
2016        );
2017        insta::assert_snapshot!(
2018            p(10.hours().minutes(10).seconds(10)),
2019            @"10h10m10s",
2020        );
2021        insta::assert_snapshot!(
2022            p(100.hours().minutes(100).seconds(100)),
2023            @"100h100m100s",
2024        );
2025
2026        insta::assert_snapshot!(p(-1.hour()), @"-1h");
2027        insta::assert_snapshot!(p(-1.hour().seconds(30)), @"-1h30s");
2028    }
2029
2030    #[test]
2031    fn print_span_designator_spacing_more() {
2032        let printer =
2033            || SpanPrinter::new().spacing(Spacing::BetweenUnitsAndDesignators);
2034        let p = |span| printer().span_to_string(&span);
2035
2036        insta::assert_snapshot!(p(1.second()), @"1 s");
2037        insta::assert_snapshot!(p(2.seconds()), @"2 s");
2038        insta::assert_snapshot!(p(10.seconds()), @"10 s");
2039        insta::assert_snapshot!(p(1.minute().seconds(40)), @"1 m 40 s");
2040
2041        insta::assert_snapshot!(p(1.minute()), @"1 m");
2042        insta::assert_snapshot!(p(2.minutes()), @"2 m");
2043        insta::assert_snapshot!(p(10.minutes()), @"10 m");
2044        insta::assert_snapshot!(p(1.hour().minutes(40)), @"1 h 40 m");
2045
2046        insta::assert_snapshot!(p(1.hour()), @"1 h");
2047        insta::assert_snapshot!(p(2.hours()), @"2 h");
2048        insta::assert_snapshot!(p(10.hours()), @"10 h");
2049        insta::assert_snapshot!(p(100.hours()), @"100 h");
2050
2051        insta::assert_snapshot!(
2052            p(1.hour().minutes(1).seconds(1)),
2053            @"1 h 1 m 1 s",
2054        );
2055        insta::assert_snapshot!(
2056            p(2.hours().minutes(2).seconds(2)),
2057            @"2 h 2 m 2 s",
2058        );
2059        insta::assert_snapshot!(
2060            p(10.hours().minutes(10).seconds(10)),
2061            @"10 h 10 m 10 s",
2062        );
2063        insta::assert_snapshot!(
2064            p(100.hours().minutes(100).seconds(100)),
2065            @"100 h 100 m 100 s",
2066        );
2067
2068        insta::assert_snapshot!(p(-1.hour()), @"1 h ago");
2069        insta::assert_snapshot!(p(-1.hour().seconds(30)), @"1 h 30 s ago");
2070    }
2071
2072    #[test]
2073    fn print_span_designator_spacing_comma() {
2074        let printer = || {
2075            SpanPrinter::new()
2076                .comma_after_designator(true)
2077                .spacing(Spacing::BetweenUnitsAndDesignators)
2078        };
2079        let p = |span| printer().span_to_string(&span);
2080
2081        insta::assert_snapshot!(p(1.second()), @"1 s");
2082        insta::assert_snapshot!(p(2.seconds()), @"2 s");
2083        insta::assert_snapshot!(p(10.seconds()), @"10 s");
2084        insta::assert_snapshot!(p(1.minute().seconds(40)), @"1 m, 40 s");
2085
2086        insta::assert_snapshot!(p(1.minute()), @"1 m");
2087        insta::assert_snapshot!(p(2.minutes()), @"2 m");
2088        insta::assert_snapshot!(p(10.minutes()), @"10 m");
2089        insta::assert_snapshot!(p(1.hour().minutes(40)), @"1 h, 40 m");
2090
2091        insta::assert_snapshot!(p(1.hour()), @"1 h");
2092        insta::assert_snapshot!(p(2.hours()), @"2 h");
2093        insta::assert_snapshot!(p(10.hours()), @"10 h");
2094        insta::assert_snapshot!(p(100.hours()), @"100 h");
2095
2096        insta::assert_snapshot!(
2097            p(1.hour().minutes(1).seconds(1)),
2098            @"1 h, 1 m, 1 s",
2099        );
2100        insta::assert_snapshot!(
2101            p(2.hours().minutes(2).seconds(2)),
2102            @"2 h, 2 m, 2 s",
2103        );
2104        insta::assert_snapshot!(
2105            p(10.hours().minutes(10).seconds(10)),
2106            @"10 h, 10 m, 10 s",
2107        );
2108        insta::assert_snapshot!(
2109            p(100.hours().minutes(100).seconds(100)),
2110            @"100 h, 100 m, 100 s",
2111        );
2112
2113        insta::assert_snapshot!(p(-1.hour()), @"1 h ago");
2114        insta::assert_snapshot!(p(-1.hour().seconds(30)), @"1 h, 30 s ago");
2115    }
2116
2117    #[test]
2118    fn print_span_designator_fractional_hour() {
2119        let printer =
2120            || SpanPrinter::new().fractional(Some(FractionalUnit::Hour));
2121        let p = |span| printer().span_to_string(&span);
2122        let pp = |precision, span| {
2123            printer().precision(Some(precision)).span_to_string(&span)
2124        };
2125
2126        insta::assert_snapshot!(p(1.hour()), @"1h");
2127        insta::assert_snapshot!(pp(0, 1.hour()), @"1h");
2128        insta::assert_snapshot!(pp(1, 1.hour()), @"1.0h");
2129        insta::assert_snapshot!(pp(2, 1.hour()), @"1.00h");
2130
2131        insta::assert_snapshot!(p(1.hour().minutes(30)), @"1.5h");
2132        insta::assert_snapshot!(pp(0, 1.hour().minutes(30)), @"1h");
2133        insta::assert_snapshot!(pp(1, 1.hour().minutes(30)), @"1.5h");
2134        insta::assert_snapshot!(pp(2, 1.hour().minutes(30)), @"1.50h");
2135
2136        insta::assert_snapshot!(p(1.hour().minutes(3)), @"1.05h");
2137        insta::assert_snapshot!(p(1.hour().minutes(3).nanoseconds(1)), @"1.05h");
2138        insta::assert_snapshot!(p(1.second()), @"0.000277777h");
2139        // precision loss!
2140        insta::assert_snapshot!(p(1.second().nanoseconds(1)), @"0.000277777h");
2141        insta::assert_snapshot!(p(0.seconds()), @"0h");
2142        // precision loss!
2143        insta::assert_snapshot!(p(1.nanosecond()), @"0h");
2144    }
2145
2146    #[test]
2147    fn print_span_designator_fractional_minute() {
2148        let printer =
2149            || SpanPrinter::new().fractional(Some(FractionalUnit::Minute));
2150        let p = |span| printer().span_to_string(&span);
2151        let pp = |precision, span| {
2152            printer().precision(Some(precision)).span_to_string(&span)
2153        };
2154
2155        insta::assert_snapshot!(p(1.hour()), @"1h");
2156        insta::assert_snapshot!(p(1.hour().minutes(30)), @"1h 30m");
2157
2158        insta::assert_snapshot!(p(1.minute()), @"1m");
2159        insta::assert_snapshot!(pp(0, 1.minute()), @"1m");
2160        insta::assert_snapshot!(pp(1, 1.minute()), @"1.0m");
2161        insta::assert_snapshot!(pp(2, 1.minute()), @"1.00m");
2162
2163        insta::assert_snapshot!(p(1.minute().seconds(30)), @"1.5m");
2164        insta::assert_snapshot!(pp(0, 1.minute().seconds(30)), @"1m");
2165        insta::assert_snapshot!(pp(1, 1.minute().seconds(30)), @"1.5m");
2166        insta::assert_snapshot!(pp(2, 1.minute().seconds(30)), @"1.50m");
2167
2168        insta::assert_snapshot!(p(1.hour().nanoseconds(1)), @"1h");
2169        insta::assert_snapshot!(p(1.minute().seconds(3)), @"1.05m");
2170        insta::assert_snapshot!(p(1.minute().seconds(3).nanoseconds(1)), @"1.05m");
2171        insta::assert_snapshot!(p(1.second()), @"0.016666666m");
2172        // precision loss!
2173        insta::assert_snapshot!(p(1.second().nanoseconds(1)), @"0.016666666m");
2174        insta::assert_snapshot!(p(0.seconds()), @"0m");
2175        // precision loss!
2176        insta::assert_snapshot!(p(1.nanosecond()), @"0m");
2177    }
2178
2179    #[test]
2180    fn print_span_designator_fractional_second() {
2181        let printer =
2182            || SpanPrinter::new().fractional(Some(FractionalUnit::Second));
2183        let p = |span| printer().span_to_string(&span);
2184        let pp = |precision, span| {
2185            printer().precision(Some(precision)).span_to_string(&span)
2186        };
2187
2188        insta::assert_snapshot!(p(1.hour()), @"1h");
2189        insta::assert_snapshot!(p(1.hour().minutes(30)), @"1h 30m");
2190
2191        insta::assert_snapshot!(p(1.second()), @"1s");
2192        insta::assert_snapshot!(pp(0, 1.second()), @"1s");
2193        insta::assert_snapshot!(pp(1, 1.second()), @"1.0s");
2194        insta::assert_snapshot!(pp(2, 1.second()), @"1.00s");
2195
2196        insta::assert_snapshot!(p(1.second().milliseconds(500)), @"1.5s");
2197        insta::assert_snapshot!(pp(0, 1.second().milliseconds(500)), @"1s");
2198        insta::assert_snapshot!(pp(1, 1.second().milliseconds(500)), @"1.5s");
2199        insta::assert_snapshot!(pp(2, 1.second().milliseconds(500)), @"1.50s");
2200
2201        insta::assert_snapshot!(p(1.second().nanoseconds(1)), @"1.000000001s");
2202        insta::assert_snapshot!(p(1.nanosecond()), @"0.000000001s");
2203        insta::assert_snapshot!(p(0.seconds()), @"0s");
2204
2205        insta::assert_snapshot!(p(1.second().milliseconds(2000)), @"3s");
2206    }
2207
2208    #[test]
2209    fn print_span_designator_fractional_millisecond() {
2210        let printer = || {
2211            SpanPrinter::new().fractional(Some(FractionalUnit::Millisecond))
2212        };
2213        let p = |span| printer().span_to_string(&span);
2214        let pp = |precision, span| {
2215            printer().precision(Some(precision)).span_to_string(&span)
2216        };
2217
2218        insta::assert_snapshot!(p(1.hour()), @"1h");
2219        insta::assert_snapshot!(p(1.hour().minutes(30)), @"1h 30m");
2220        insta::assert_snapshot!(
2221            p(1.hour().minutes(30).seconds(10)),
2222            @"1h 30m 10s",
2223        );
2224
2225        insta::assert_snapshot!(p(1.second()), @"1s");
2226        insta::assert_snapshot!(pp(0, 1.second()), @"1s");
2227        insta::assert_snapshot!(pp(1, 1.second()), @"1s 0.0ms");
2228        insta::assert_snapshot!(pp(2, 1.second()), @"1s 0.00ms");
2229
2230        insta::assert_snapshot!(p(1.second().milliseconds(500)), @"1s 500ms");
2231        insta::assert_snapshot!(
2232            pp(0, 1.second().milliseconds(1).microseconds(500)),
2233            @"1s 1ms",
2234        );
2235        insta::assert_snapshot!(
2236            pp(1, 1.second().milliseconds(1).microseconds(500)),
2237            @"1s 1.5ms",
2238        );
2239        insta::assert_snapshot!(
2240            pp(2, 1.second().milliseconds(1).microseconds(500)),
2241            @"1s 1.50ms",
2242        );
2243
2244        insta::assert_snapshot!(p(1.millisecond().nanoseconds(1)), @"1.000001ms");
2245        insta::assert_snapshot!(p(1.nanosecond()), @"0.000001ms");
2246        insta::assert_snapshot!(p(0.seconds()), @"0ms");
2247    }
2248
2249    #[test]
2250    fn print_span_designator_fractional_microsecond() {
2251        let printer = || {
2252            SpanPrinter::new().fractional(Some(FractionalUnit::Microsecond))
2253        };
2254        let p = |span| printer().span_to_string(&span);
2255        let pp = |precision, span| {
2256            printer().precision(Some(precision)).span_to_string(&span)
2257        };
2258
2259        insta::assert_snapshot!(p(1.hour()), @"1h");
2260        insta::assert_snapshot!(p(1.hour().minutes(30)), @"1h 30m");
2261        insta::assert_snapshot!(
2262            p(1.hour().minutes(30).seconds(10)),
2263            @"1h 30m 10s",
2264        );
2265
2266        insta::assert_snapshot!(p(1.second()), @"1s");
2267        insta::assert_snapshot!(pp(0, 1.second()), @"1s");
2268        insta::assert_snapshot!(pp(1, 1.second()), @"1s 0.0µs");
2269        insta::assert_snapshot!(pp(2, 1.second()), @"1s 0.00µs");
2270
2271        insta::assert_snapshot!(p(1.second().milliseconds(500)), @"1s 500ms");
2272        insta::assert_snapshot!(
2273            pp(0, 1.second().milliseconds(1).microseconds(500)),
2274            @"1s 1ms 500µs",
2275        );
2276        insta::assert_snapshot!(
2277            pp(1, 1.second().milliseconds(1).microseconds(500)),
2278            @"1s 1ms 500.0µs",
2279        );
2280        insta::assert_snapshot!(
2281            pp(2, 1.second().milliseconds(1).microseconds(500)),
2282            @"1s 1ms 500.00µs",
2283        );
2284
2285        insta::assert_snapshot!(
2286            p(1.millisecond().nanoseconds(1)),
2287            @"1ms 0.001µs",
2288        );
2289        insta::assert_snapshot!(p(1.nanosecond()), @"0.001µs");
2290        insta::assert_snapshot!(p(0.second()), @"0µs");
2291    }
2292
2293    #[test]
2294    fn print_duration_designator_default() {
2295        let printer = || SpanPrinter::new();
2296        let p = |secs| {
2297            printer().duration_to_string(&SignedDuration::from_secs(secs))
2298        };
2299
2300        insta::assert_snapshot!(p(1), @"1s");
2301        insta::assert_snapshot!(p(2), @"2s");
2302        insta::assert_snapshot!(p(10), @"10s");
2303        insta::assert_snapshot!(p(100), @"1m 40s");
2304
2305        insta::assert_snapshot!(p(1 * 60), @"1m");
2306        insta::assert_snapshot!(p(2 * 60), @"2m");
2307        insta::assert_snapshot!(p(10 * 60), @"10m");
2308        insta::assert_snapshot!(p(100 * 60), @"1h 40m");
2309
2310        insta::assert_snapshot!(p(1 * 60 * 60), @"1h");
2311        insta::assert_snapshot!(p(2 * 60 * 60), @"2h");
2312        insta::assert_snapshot!(p(10 * 60 * 60), @"10h");
2313        insta::assert_snapshot!(p(100 * 60 * 60), @"100h");
2314
2315        insta::assert_snapshot!(
2316            p(60 * 60 + 60 + 1),
2317            @"1h 1m 1s",
2318        );
2319        insta::assert_snapshot!(
2320            p(2 * 60 * 60 + 2 * 60 + 2),
2321            @"2h 2m 2s",
2322        );
2323        insta::assert_snapshot!(
2324            p(10 * 60 * 60 + 10 * 60 + 10),
2325            @"10h 10m 10s",
2326        );
2327        insta::assert_snapshot!(
2328            p(100 * 60 * 60 + 100 * 60 + 100),
2329            @"101h 41m 40s",
2330        );
2331
2332        insta::assert_snapshot!(p(-1 * 60 * 60), @"1h ago");
2333        insta::assert_snapshot!(p(-1 * 60 * 60 - 30), @"1h 30s ago");
2334    }
2335
2336    #[test]
2337    fn print_duration_designator_verbose() {
2338        let printer = || SpanPrinter::new().designator(Designator::Verbose);
2339        let p = |secs| {
2340            printer().duration_to_string(&SignedDuration::from_secs(secs))
2341        };
2342
2343        insta::assert_snapshot!(p(1), @"1second");
2344        insta::assert_snapshot!(p(2), @"2seconds");
2345        insta::assert_snapshot!(p(10), @"10seconds");
2346        insta::assert_snapshot!(p(100), @"1minute 40seconds");
2347
2348        insta::assert_snapshot!(p(1 * 60), @"1minute");
2349        insta::assert_snapshot!(p(2 * 60), @"2minutes");
2350        insta::assert_snapshot!(p(10 * 60), @"10minutes");
2351        insta::assert_snapshot!(p(100 * 60), @"1hour 40minutes");
2352
2353        insta::assert_snapshot!(p(1 * 60 * 60), @"1hour");
2354        insta::assert_snapshot!(p(2 * 60 * 60), @"2hours");
2355        insta::assert_snapshot!(p(10 * 60 * 60), @"10hours");
2356        insta::assert_snapshot!(p(100 * 60 * 60), @"100hours");
2357
2358        insta::assert_snapshot!(
2359            p(60 * 60 + 60 + 1),
2360            @"1hour 1minute 1second",
2361        );
2362        insta::assert_snapshot!(
2363            p(2 * 60 * 60 + 2 * 60 + 2),
2364            @"2hours 2minutes 2seconds",
2365        );
2366        insta::assert_snapshot!(
2367            p(10 * 60 * 60 + 10 * 60 + 10),
2368            @"10hours 10minutes 10seconds",
2369        );
2370        insta::assert_snapshot!(
2371            p(100 * 60 * 60 + 100 * 60 + 100),
2372            @"101hours 41minutes 40seconds",
2373        );
2374
2375        insta::assert_snapshot!(p(-1 * 60 * 60), @"1hour ago");
2376        insta::assert_snapshot!(p(-1 * 60 * 60 - 30), @"1hour 30seconds ago");
2377    }
2378
2379    #[test]
2380    fn print_duration_designator_short() {
2381        let printer = || SpanPrinter::new().designator(Designator::Short);
2382        let p = |secs| {
2383            printer().duration_to_string(&SignedDuration::from_secs(secs))
2384        };
2385
2386        insta::assert_snapshot!(p(1), @"1sec");
2387        insta::assert_snapshot!(p(2), @"2secs");
2388        insta::assert_snapshot!(p(10), @"10secs");
2389        insta::assert_snapshot!(p(100), @"1min 40secs");
2390
2391        insta::assert_snapshot!(p(1 * 60), @"1min");
2392        insta::assert_snapshot!(p(2 * 60), @"2mins");
2393        insta::assert_snapshot!(p(10 * 60), @"10mins");
2394        insta::assert_snapshot!(p(100 * 60), @"1hr 40mins");
2395
2396        insta::assert_snapshot!(p(1 * 60 * 60), @"1hr");
2397        insta::assert_snapshot!(p(2 * 60 * 60), @"2hrs");
2398        insta::assert_snapshot!(p(10 * 60 * 60), @"10hrs");
2399        insta::assert_snapshot!(p(100 * 60 * 60), @"100hrs");
2400
2401        insta::assert_snapshot!(
2402            p(60 * 60 + 60 + 1),
2403            @"1hr 1min 1sec",
2404        );
2405        insta::assert_snapshot!(
2406            p(2 * 60 * 60 + 2 * 60 + 2),
2407            @"2hrs 2mins 2secs",
2408        );
2409        insta::assert_snapshot!(
2410            p(10 * 60 * 60 + 10 * 60 + 10),
2411            @"10hrs 10mins 10secs",
2412        );
2413        insta::assert_snapshot!(
2414            p(100 * 60 * 60 + 100 * 60 + 100),
2415            @"101hrs 41mins 40secs",
2416        );
2417
2418        insta::assert_snapshot!(p(-1 * 60 * 60), @"1hr ago");
2419        insta::assert_snapshot!(p(-1 * 60 * 60 - 30), @"1hr 30secs ago");
2420    }
2421
2422    #[test]
2423    fn print_duration_designator_compact() {
2424        let printer = || SpanPrinter::new().designator(Designator::Compact);
2425        let p = |secs| {
2426            printer().duration_to_string(&SignedDuration::from_secs(secs))
2427        };
2428
2429        insta::assert_snapshot!(p(1), @"1s");
2430        insta::assert_snapshot!(p(2), @"2s");
2431        insta::assert_snapshot!(p(10), @"10s");
2432        insta::assert_snapshot!(p(100), @"1m 40s");
2433
2434        insta::assert_snapshot!(p(1 * 60), @"1m");
2435        insta::assert_snapshot!(p(2 * 60), @"2m");
2436        insta::assert_snapshot!(p(10 * 60), @"10m");
2437        insta::assert_snapshot!(p(100 * 60), @"1h 40m");
2438
2439        insta::assert_snapshot!(p(1 * 60 * 60), @"1h");
2440        insta::assert_snapshot!(p(2 * 60 * 60), @"2h");
2441        insta::assert_snapshot!(p(10 * 60 * 60), @"10h");
2442        insta::assert_snapshot!(p(100 * 60 * 60), @"100h");
2443
2444        insta::assert_snapshot!(
2445            p(60 * 60 + 60 + 1),
2446            @"1h 1m 1s",
2447        );
2448        insta::assert_snapshot!(
2449            p(2 * 60 * 60 + 2 * 60 + 2),
2450            @"2h 2m 2s",
2451        );
2452        insta::assert_snapshot!(
2453            p(10 * 60 * 60 + 10 * 60 + 10),
2454            @"10h 10m 10s",
2455        );
2456        insta::assert_snapshot!(
2457            p(100 * 60 * 60 + 100 * 60 + 100),
2458            @"101h 41m 40s",
2459        );
2460
2461        insta::assert_snapshot!(p(-1 * 60 * 60), @"1h ago");
2462        insta::assert_snapshot!(p(-1 * 60 * 60 - 30), @"1h 30s ago");
2463    }
2464
2465    #[test]
2466    fn print_duration_designator_direction_force() {
2467        let printer = || SpanPrinter::new().direction(Direction::ForceSign);
2468        let p = |secs| {
2469            printer().duration_to_string(&SignedDuration::from_secs(secs))
2470        };
2471
2472        insta::assert_snapshot!(p(1), @"+1s");
2473        insta::assert_snapshot!(p(2), @"+2s");
2474        insta::assert_snapshot!(p(10), @"+10s");
2475        insta::assert_snapshot!(p(100), @"+1m 40s");
2476
2477        insta::assert_snapshot!(p(1 * 60), @"+1m");
2478        insta::assert_snapshot!(p(2 * 60), @"+2m");
2479        insta::assert_snapshot!(p(10 * 60), @"+10m");
2480        insta::assert_snapshot!(p(100 * 60), @"+1h 40m");
2481
2482        insta::assert_snapshot!(p(1 * 60 * 60), @"+1h");
2483        insta::assert_snapshot!(p(2 * 60 * 60), @"+2h");
2484        insta::assert_snapshot!(p(10 * 60 * 60), @"+10h");
2485        insta::assert_snapshot!(p(100 * 60 * 60), @"+100h");
2486
2487        insta::assert_snapshot!(
2488            p(60 * 60 + 60 + 1),
2489            @"+1h 1m 1s",
2490        );
2491        insta::assert_snapshot!(
2492            p(2 * 60 * 60 + 2 * 60 + 2),
2493            @"+2h 2m 2s",
2494        );
2495        insta::assert_snapshot!(
2496            p(10 * 60 * 60 + 10 * 60 + 10),
2497            @"+10h 10m 10s",
2498        );
2499        insta::assert_snapshot!(
2500            p(100 * 60 * 60 + 100 * 60 + 100),
2501            @"+101h 41m 40s",
2502        );
2503
2504        insta::assert_snapshot!(p(-1 * 60 * 60), @"-1h");
2505        insta::assert_snapshot!(p(-1 * 60 * 60 - 30), @"-1h 30s");
2506    }
2507
2508    #[test]
2509    fn print_duration_designator_padding() {
2510        let printer = || SpanPrinter::new().padding(2);
2511        let p = |secs| {
2512            printer().duration_to_string(&SignedDuration::from_secs(secs))
2513        };
2514
2515        insta::assert_snapshot!(p(1), @"01s");
2516        insta::assert_snapshot!(p(2), @"02s");
2517        insta::assert_snapshot!(p(10), @"10s");
2518        insta::assert_snapshot!(p(100), @"01m 40s");
2519
2520        insta::assert_snapshot!(p(1 * 60), @"01m");
2521        insta::assert_snapshot!(p(2 * 60), @"02m");
2522        insta::assert_snapshot!(p(10 * 60), @"10m");
2523        insta::assert_snapshot!(p(100 * 60), @"01h 40m");
2524
2525        insta::assert_snapshot!(p(1 * 60 * 60), @"01h");
2526        insta::assert_snapshot!(p(2 * 60 * 60), @"02h");
2527        insta::assert_snapshot!(p(10 * 60 * 60), @"10h");
2528        insta::assert_snapshot!(p(100 * 60 * 60), @"100h");
2529
2530        insta::assert_snapshot!(
2531            p(60 * 60 + 60 + 1),
2532            @"01h 01m 01s",
2533        );
2534        insta::assert_snapshot!(
2535            p(2 * 60 * 60 + 2 * 60 + 2),
2536            @"02h 02m 02s",
2537        );
2538        insta::assert_snapshot!(
2539            p(10 * 60 * 60 + 10 * 60 + 10),
2540            @"10h 10m 10s",
2541        );
2542        insta::assert_snapshot!(
2543            p(100 * 60 * 60 + 100 * 60 + 100),
2544            @"101h 41m 40s",
2545        );
2546
2547        insta::assert_snapshot!(p(-1 * 60 * 60), @"01h ago");
2548        insta::assert_snapshot!(p(-1 * 60 * 60 - 30), @"01h 30s ago");
2549    }
2550
2551    #[test]
2552    fn print_duration_designator_spacing_none() {
2553        let printer = || SpanPrinter::new().spacing(Spacing::None);
2554        let p = |secs| {
2555            printer().duration_to_string(&SignedDuration::from_secs(secs))
2556        };
2557
2558        insta::assert_snapshot!(p(1), @"1s");
2559        insta::assert_snapshot!(p(2), @"2s");
2560        insta::assert_snapshot!(p(10), @"10s");
2561        insta::assert_snapshot!(p(100), @"1m40s");
2562
2563        insta::assert_snapshot!(p(1 * 60), @"1m");
2564        insta::assert_snapshot!(p(2 * 60), @"2m");
2565        insta::assert_snapshot!(p(10 * 60), @"10m");
2566        insta::assert_snapshot!(p(100 * 60), @"1h40m");
2567
2568        insta::assert_snapshot!(p(1 * 60 * 60), @"1h");
2569        insta::assert_snapshot!(p(2 * 60 * 60), @"2h");
2570        insta::assert_snapshot!(p(10 * 60 * 60), @"10h");
2571        insta::assert_snapshot!(p(100 * 60 * 60), @"100h");
2572
2573        insta::assert_snapshot!(
2574            p(60 * 60 + 60 + 1),
2575            @"1h1m1s",
2576        );
2577        insta::assert_snapshot!(
2578            p(2 * 60 * 60 + 2 * 60 + 2),
2579            @"2h2m2s",
2580        );
2581        insta::assert_snapshot!(
2582            p(10 * 60 * 60 + 10 * 60 + 10),
2583            @"10h10m10s",
2584        );
2585        insta::assert_snapshot!(
2586            p(100 * 60 * 60 + 100 * 60 + 100),
2587            @"101h41m40s",
2588        );
2589
2590        insta::assert_snapshot!(p(-1 * 60 * 60), @"-1h");
2591        insta::assert_snapshot!(p(-1 * 60 * 60 - 30), @"-1h30s");
2592    }
2593
2594    #[test]
2595    fn print_duration_designator_spacing_more() {
2596        let printer =
2597            || SpanPrinter::new().spacing(Spacing::BetweenUnitsAndDesignators);
2598        let p = |secs| {
2599            printer().duration_to_string(&SignedDuration::from_secs(secs))
2600        };
2601
2602        insta::assert_snapshot!(p(1), @"1 s");
2603        insta::assert_snapshot!(p(2), @"2 s");
2604        insta::assert_snapshot!(p(10), @"10 s");
2605        insta::assert_snapshot!(p(100), @"1 m 40 s");
2606
2607        insta::assert_snapshot!(p(1 * 60), @"1 m");
2608        insta::assert_snapshot!(p(2 * 60), @"2 m");
2609        insta::assert_snapshot!(p(10 * 60), @"10 m");
2610        insta::assert_snapshot!(p(100 * 60), @"1 h 40 m");
2611
2612        insta::assert_snapshot!(p(1 * 60 * 60), @"1 h");
2613        insta::assert_snapshot!(p(2 * 60 * 60), @"2 h");
2614        insta::assert_snapshot!(p(10 * 60 * 60), @"10 h");
2615        insta::assert_snapshot!(p(100 * 60 * 60), @"100 h");
2616
2617        insta::assert_snapshot!(
2618            p(60 * 60 + 60 + 1),
2619            @"1 h 1 m 1 s",
2620        );
2621        insta::assert_snapshot!(
2622            p(2 * 60 * 60 + 2 * 60 + 2),
2623            @"2 h 2 m 2 s",
2624        );
2625        insta::assert_snapshot!(
2626            p(10 * 60 * 60 + 10 * 60 + 10),
2627            @"10 h 10 m 10 s",
2628        );
2629        insta::assert_snapshot!(
2630            p(100 * 60 * 60 + 100 * 60 + 100),
2631            @"101 h 41 m 40 s",
2632        );
2633
2634        insta::assert_snapshot!(p(-1 * 60 * 60), @"1 h ago");
2635        insta::assert_snapshot!(p(-1 * 60 * 60 - 30), @"1 h 30 s ago");
2636    }
2637
2638    #[test]
2639    fn print_duration_designator_spacing_comma() {
2640        let printer = || {
2641            SpanPrinter::new()
2642                .comma_after_designator(true)
2643                .spacing(Spacing::BetweenUnitsAndDesignators)
2644        };
2645        let p = |secs| {
2646            printer().duration_to_string(&SignedDuration::from_secs(secs))
2647        };
2648
2649        insta::assert_snapshot!(p(1), @"1 s");
2650        insta::assert_snapshot!(p(2), @"2 s");
2651        insta::assert_snapshot!(p(10), @"10 s");
2652        insta::assert_snapshot!(p(100), @"1 m, 40 s");
2653
2654        insta::assert_snapshot!(p(1 * 60), @"1 m");
2655        insta::assert_snapshot!(p(2 * 60), @"2 m");
2656        insta::assert_snapshot!(p(10 * 60), @"10 m");
2657        insta::assert_snapshot!(p(100 * 60), @"1 h, 40 m");
2658
2659        insta::assert_snapshot!(p(1 * 60 * 60), @"1 h");
2660        insta::assert_snapshot!(p(2 * 60 * 60), @"2 h");
2661        insta::assert_snapshot!(p(10 * 60 * 60), @"10 h");
2662        insta::assert_snapshot!(p(100 * 60 * 60), @"100 h");
2663
2664        insta::assert_snapshot!(
2665            p(60 * 60 + 60 + 1),
2666            @"1 h, 1 m, 1 s",
2667        );
2668        insta::assert_snapshot!(
2669            p(2 * 60 * 60 + 2 * 60 + 2),
2670            @"2 h, 2 m, 2 s",
2671        );
2672        insta::assert_snapshot!(
2673            p(10 * 60 * 60 + 10 * 60 + 10),
2674            @"10 h, 10 m, 10 s",
2675        );
2676        insta::assert_snapshot!(
2677            p(100 * 60 * 60 + 100 * 60 + 100),
2678            @"101 h, 41 m, 40 s",
2679        );
2680
2681        insta::assert_snapshot!(p(-1 * 60 * 60), @"1 h ago");
2682        insta::assert_snapshot!(p(-1 * 60 * 60 - 30), @"1 h, 30 s ago");
2683    }
2684
2685    #[test]
2686    fn print_duration_designator_fractional_hour() {
2687        let printer =
2688            || SpanPrinter::new().fractional(Some(FractionalUnit::Hour));
2689        let p = |secs, nanos| {
2690            printer().duration_to_string(&SignedDuration::new(secs, nanos))
2691        };
2692        let pp = |precision, secs, nanos| {
2693            printer()
2694                .precision(Some(precision))
2695                .duration_to_string(&SignedDuration::new(secs, nanos))
2696        };
2697
2698        insta::assert_snapshot!(p(1 * 60 * 60, 0), @"1h");
2699        insta::assert_snapshot!(pp(0, 1 * 60 * 60, 0), @"1h");
2700        insta::assert_snapshot!(pp(1, 1 * 60 * 60, 0), @"1.0h");
2701        insta::assert_snapshot!(pp(2, 1 * 60 * 60, 0), @"1.00h");
2702
2703        insta::assert_snapshot!(p(1 * 60 * 60 + 30 * 60, 0), @"1.5h");
2704        insta::assert_snapshot!(pp(0, 1 * 60 * 60 + 30 * 60, 0), @"1h");
2705        insta::assert_snapshot!(pp(1, 1 * 60 * 60 + 30 * 60, 0), @"1.5h");
2706        insta::assert_snapshot!(pp(2, 1 * 60 * 60 + 30 * 60, 0), @"1.50h");
2707
2708        insta::assert_snapshot!(p(1 * 60 * 60 + 3 * 60, 0), @"1.05h");
2709        insta::assert_snapshot!(p(1 * 60 * 60 + 3 * 60, 1), @"1.05h");
2710        insta::assert_snapshot!(p(1, 0), @"0.000277777h");
2711        // precision loss!
2712        insta::assert_snapshot!(p(1, 1), @"0.000277777h");
2713        insta::assert_snapshot!(p(0, 0), @"0h");
2714        // precision loss!
2715        insta::assert_snapshot!(p(0, 1), @"0h");
2716
2717        insta::assert_snapshot!(
2718            printer().duration_to_string(&SignedDuration::MIN),
2719            @"2562047788015215.502499999h ago",
2720        );
2721    }
2722
2723    #[test]
2724    fn print_duration_designator_fractional_minute() {
2725        let printer =
2726            || SpanPrinter::new().fractional(Some(FractionalUnit::Minute));
2727        let p = |secs, nanos| {
2728            printer().duration_to_string(&SignedDuration::new(secs, nanos))
2729        };
2730        let pp = |precision, secs, nanos| {
2731            printer()
2732                .precision(Some(precision))
2733                .duration_to_string(&SignedDuration::new(secs, nanos))
2734        };
2735
2736        insta::assert_snapshot!(p(1 * 60 * 60, 0), @"1h");
2737        insta::assert_snapshot!(p(1 * 60 * 60 + 30 * 60, 0), @"1h 30m");
2738
2739        insta::assert_snapshot!(p(60, 0), @"1m");
2740        insta::assert_snapshot!(pp(0, 60, 0), @"1m");
2741        insta::assert_snapshot!(pp(1, 60, 0), @"1.0m");
2742        insta::assert_snapshot!(pp(2, 60, 0), @"1.00m");
2743
2744        insta::assert_snapshot!(p(90, 0), @"1.5m");
2745        insta::assert_snapshot!(pp(0, 90, 0), @"1m");
2746        insta::assert_snapshot!(pp(1, 90, 0), @"1.5m");
2747        insta::assert_snapshot!(pp(2, 90, 0), @"1.50m");
2748
2749        insta::assert_snapshot!(p(1 * 60 * 60, 1), @"1h");
2750        insta::assert_snapshot!(p(63, 0), @"1.05m");
2751        insta::assert_snapshot!(p(63, 1), @"1.05m");
2752        insta::assert_snapshot!(p(1, 0), @"0.016666666m");
2753        // precision loss!
2754        insta::assert_snapshot!(p(1, 1), @"0.016666666m");
2755        insta::assert_snapshot!(p(0, 0), @"0m");
2756        // precision loss!
2757        insta::assert_snapshot!(p(0, 1), @"0m");
2758
2759        insta::assert_snapshot!(
2760            printer().duration_to_string(&SignedDuration::MIN),
2761            @"2562047788015215h 30.149999999m ago",
2762        );
2763    }
2764
2765    #[test]
2766    fn print_duration_designator_fractional_second() {
2767        let printer =
2768            || SpanPrinter::new().fractional(Some(FractionalUnit::Second));
2769        let p = |secs, nanos| {
2770            printer().duration_to_string(&SignedDuration::new(secs, nanos))
2771        };
2772        let pp = |precision, secs, nanos| {
2773            printer()
2774                .precision(Some(precision))
2775                .duration_to_string(&SignedDuration::new(secs, nanos))
2776        };
2777
2778        insta::assert_snapshot!(p(1 * 60 * 60, 0), @"1h");
2779        insta::assert_snapshot!(p(1 * 60 * 60 + 30 * 60, 0), @"1h 30m");
2780
2781        insta::assert_snapshot!(p(1, 0), @"1s");
2782        insta::assert_snapshot!(pp(0, 1, 0), @"1s");
2783        insta::assert_snapshot!(pp(1, 1, 0), @"1.0s");
2784        insta::assert_snapshot!(pp(2, 1, 0), @"1.00s");
2785
2786        insta::assert_snapshot!(p(1, 500_000_000), @"1.5s");
2787        insta::assert_snapshot!(pp(0, 1, 500_000_000), @"1s");
2788        insta::assert_snapshot!(pp(1, 1, 500_000_000), @"1.5s");
2789        insta::assert_snapshot!(pp(2, 1, 500_000_000), @"1.50s");
2790
2791        insta::assert_snapshot!(p(1, 1), @"1.000000001s");
2792        insta::assert_snapshot!(p(0, 1), @"0.000000001s");
2793        insta::assert_snapshot!(p(0, 0), @"0s");
2794
2795        insta::assert_snapshot!(
2796            printer().duration_to_string(&SignedDuration::MIN),
2797            @"2562047788015215h 30m 8.999999999s ago",
2798        );
2799    }
2800
2801    #[test]
2802    fn print_duration_designator_fractional_millisecond() {
2803        let printer = || {
2804            SpanPrinter::new().fractional(Some(FractionalUnit::Millisecond))
2805        };
2806        let p = |secs, nanos| {
2807            printer().duration_to_string(&SignedDuration::new(secs, nanos))
2808        };
2809        let pp = |precision, secs, nanos| {
2810            printer()
2811                .precision(Some(precision))
2812                .duration_to_string(&SignedDuration::new(secs, nanos))
2813        };
2814
2815        insta::assert_snapshot!(p(1 * 60 * 60, 0), @"1h");
2816        insta::assert_snapshot!(p(1 * 60 * 60 + 30 * 60, 0), @"1h 30m");
2817        insta::assert_snapshot!(
2818            p(1 * 60 * 60 + 30 * 60 + 10, 0),
2819            @"1h 30m 10s",
2820        );
2821
2822        insta::assert_snapshot!(p(1, 0), @"1s");
2823        insta::assert_snapshot!(pp(0, 1, 0), @"1s");
2824        insta::assert_snapshot!(pp(1, 1, 0), @"1s 0.0ms");
2825        insta::assert_snapshot!(pp(2, 1, 0), @"1s 0.00ms");
2826
2827        insta::assert_snapshot!(p(1, 500_000_000), @"1s 500ms");
2828        insta::assert_snapshot!(pp(0, 1, 1_500_000), @"1s 1ms");
2829        insta::assert_snapshot!(pp(1, 1, 1_500_000), @"1s 1.5ms");
2830        insta::assert_snapshot!(pp(2, 1, 1_500_000), @"1s 1.50ms");
2831
2832        insta::assert_snapshot!(p(0, 1_000_001), @"1.000001ms");
2833        insta::assert_snapshot!(p(0, 0_000_001), @"0.000001ms");
2834        insta::assert_snapshot!(p(0, 0), @"0ms");
2835
2836        insta::assert_snapshot!(
2837            printer().duration_to_string(&SignedDuration::MIN),
2838            @"2562047788015215h 30m 8s 999.999999ms ago",
2839        );
2840    }
2841
2842    #[test]
2843    fn print_duration_designator_fractional_microsecond() {
2844        let printer = || {
2845            SpanPrinter::new().fractional(Some(FractionalUnit::Microsecond))
2846        };
2847        let p = |secs, nanos| {
2848            printer().duration_to_string(&SignedDuration::new(secs, nanos))
2849        };
2850        let pp = |precision, secs, nanos| {
2851            printer()
2852                .precision(Some(precision))
2853                .duration_to_string(&SignedDuration::new(secs, nanos))
2854        };
2855
2856        insta::assert_snapshot!(p(1 * 60 * 60, 0), @"1h");
2857        insta::assert_snapshot!(p(1 * 60 * 60 + 30 * 60, 0), @"1h 30m");
2858        insta::assert_snapshot!(
2859            p(1 * 60 * 60 + 30 * 60 + 10, 0),
2860            @"1h 30m 10s",
2861        );
2862
2863        insta::assert_snapshot!(p(1, 0), @"1s");
2864        insta::assert_snapshot!(pp(0, 1, 0), @"1s");
2865        insta::assert_snapshot!(pp(1, 1, 0), @"1s 0.0µs");
2866        insta::assert_snapshot!(pp(2, 1, 0), @"1s 0.00µs");
2867
2868        insta::assert_snapshot!(p(1, 500_000_000), @"1s 500ms");
2869        insta::assert_snapshot!(pp(0, 1, 1_500_000), @"1s 1ms 500µs");
2870        insta::assert_snapshot!(pp(1, 1, 1_500_000), @"1s 1ms 500.0µs");
2871        insta::assert_snapshot!(pp(2, 1, 1_500_000), @"1s 1ms 500.00µs");
2872
2873        insta::assert_snapshot!(p(0, 1_000_001), @"1ms 0.001µs");
2874        insta::assert_snapshot!(p(0, 0_000_001), @"0.001µs");
2875        insta::assert_snapshot!(p(0, 0), @"0µs");
2876
2877        insta::assert_snapshot!(
2878            printer().duration_to_string(&SignedDuration::MIN),
2879            @"2562047788015215h 30m 8s 999ms 999.999µs ago",
2880        );
2881    }
2882
2883    #[test]
2884    fn print_span_hms() {
2885        let printer = || SpanPrinter::new().hours_minutes_seconds(true);
2886        let p = |span| printer().span_to_string(&span);
2887
2888        insta::assert_snapshot!(p(1.second()), @"00:00:01");
2889        insta::assert_snapshot!(p(2.seconds()), @"00:00:02");
2890        insta::assert_snapshot!(p(10.seconds()), @"00:00:10");
2891        insta::assert_snapshot!(p(100.seconds()), @"00:00:100");
2892
2893        insta::assert_snapshot!(p(1.minute()), @"00:01:00");
2894        insta::assert_snapshot!(p(2.minutes()), @"00:02:00");
2895        insta::assert_snapshot!(p(10.minutes()), @"00:10:00");
2896        insta::assert_snapshot!(p(100.minutes()), @"00:100:00");
2897
2898        insta::assert_snapshot!(p(1.hour()), @"01:00:00");
2899        insta::assert_snapshot!(p(2.hours()), @"02:00:00");
2900        insta::assert_snapshot!(p(10.hours()), @"10:00:00");
2901        insta::assert_snapshot!(p(100.hours()), @"100:00:00");
2902
2903        insta::assert_snapshot!(
2904            p(1.hour().minutes(1).seconds(1)),
2905            @"01:01:01",
2906        );
2907        insta::assert_snapshot!(
2908            p(2.hours().minutes(2).seconds(2)),
2909            @"02:02:02",
2910        );
2911        insta::assert_snapshot!(
2912            p(10.hours().minutes(10).seconds(10)),
2913            @"10:10:10",
2914        );
2915        insta::assert_snapshot!(
2916            p(100.hours().minutes(100).seconds(100)),
2917            @"100:100:100",
2918        );
2919
2920        insta::assert_snapshot!(
2921            p(1.day().hours(1).minutes(1).seconds(1)),
2922            @"1d 01:01:01",
2923        );
2924        insta::assert_snapshot!(
2925            p(1.day()),
2926            @"1d 00:00:00",
2927        );
2928        insta::assert_snapshot!(
2929            p(1.day().seconds(2)),
2930            @"1d 00:00:02",
2931        );
2932    }
2933
2934    #[test]
2935    fn print_span_hms_fmt() {
2936        let printer = || {
2937            SpanPrinter::new()
2938                .hours_minutes_seconds(true)
2939                .comma_after_designator(true)
2940                .spacing(Spacing::BetweenUnitsAndDesignators)
2941        };
2942        let p = |span| printer().span_to_string(&span);
2943
2944        insta::assert_snapshot!(
2945            p(1.day().hours(1).minutes(1).seconds(1)),
2946            @"1 d, 01:01:01",
2947        );
2948        insta::assert_snapshot!(
2949            p(1.year().months(1).weeks(1).days(1).hours(1).minutes(1).seconds(1)),
2950            @"1 y, 1 mo, 1 w, 1 d, 01:01:01",
2951        );
2952        insta::assert_snapshot!(
2953            p(1.day().hours(1).minutes(1).seconds(1).nanoseconds(1)),
2954            @"1 d, 01:01:01.000000001",
2955        );
2956    }
2957
2958    #[test]
2959    fn print_span_hms_sign() {
2960        let printer = |direction| {
2961            SpanPrinter::new().hours_minutes_seconds(true).direction(direction)
2962        };
2963        let p = |direction, span| printer(direction).span_to_string(&span);
2964
2965        insta::assert_snapshot!(
2966            p(Direction::Auto, 1.hour()),
2967            @"01:00:00",
2968        );
2969        insta::assert_snapshot!(
2970            p(Direction::Sign, 1.hour()),
2971            @"01:00:00",
2972        );
2973        insta::assert_snapshot!(
2974            p(Direction::ForceSign, 1.hour()),
2975            @"+01:00:00",
2976        );
2977        insta::assert_snapshot!(
2978            p(Direction::Suffix, 1.hour()),
2979            @"01:00:00",
2980        );
2981        insta::assert_snapshot!(
2982            p(Direction::Auto, -1.hour()),
2983            @"-01:00:00",
2984        );
2985        insta::assert_snapshot!(
2986            p(Direction::Sign, -1.hour()),
2987            @"-01:00:00",
2988        );
2989        insta::assert_snapshot!(
2990            p(Direction::ForceSign, -1.hour()),
2991            @"-01:00:00",
2992        );
2993        insta::assert_snapshot!(
2994            p(Direction::Suffix, -1.hour()),
2995            @"01:00:00 ago",
2996        );
2997
2998        insta::assert_snapshot!(
2999            p(Direction::Auto, 1.day().hours(1)),
3000            @"1d 01:00:00",
3001        );
3002        insta::assert_snapshot!(
3003            p(Direction::Sign, 1.day().hours(1)),
3004            @"1d 01:00:00",
3005        );
3006        insta::assert_snapshot!(
3007            p(Direction::ForceSign, 1.day().hours(1)),
3008            @"+1d 01:00:00",
3009        );
3010        insta::assert_snapshot!(
3011            p(Direction::Suffix, 1.day().hours(1)),
3012            @"1d 01:00:00",
3013        );
3014        // This is the main change from above. With non-zero
3015        // calendar units, the default for expressing a negative
3016        // sign switches to a suffix in the HH:MM:SS format.
3017        insta::assert_snapshot!(
3018            p(Direction::Auto, -1.day().hours(1)),
3019            @"1d 01:00:00 ago",
3020        );
3021        insta::assert_snapshot!(
3022            p(Direction::Sign, -1.day().hours(1)),
3023            @"-1d 01:00:00",
3024        );
3025        insta::assert_snapshot!(
3026            p(Direction::ForceSign, -1.day().hours(1)),
3027            @"-1d 01:00:00",
3028        );
3029        insta::assert_snapshot!(
3030            p(Direction::Suffix, -1.day().hours(1)),
3031            @"1d 01:00:00 ago",
3032        );
3033    }
3034
3035    #[test]
3036    fn print_span_hms_fraction_auto() {
3037        let printer = || SpanPrinter::new().hours_minutes_seconds(true);
3038        let p = |span| printer().span_to_string(&span);
3039
3040        insta::assert_snapshot!(p(1.nanosecond()), @"00:00:00.000000001");
3041        insta::assert_snapshot!(p(-1.nanosecond()), @"-00:00:00.000000001");
3042        insta::assert_snapshot!(
3043            printer().direction(Direction::ForceSign).span_to_string(&1.nanosecond()),
3044            @"+00:00:00.000000001",
3045        );
3046
3047        insta::assert_snapshot!(
3048            p(1.second().nanoseconds(123)),
3049            @"00:00:01.000000123",
3050        );
3051        insta::assert_snapshot!(
3052            p(1.second().milliseconds(123)),
3053            @"00:00:01.123",
3054        );
3055        insta::assert_snapshot!(
3056            p(1.second().milliseconds(1_123)),
3057            @"00:00:02.123",
3058        );
3059        insta::assert_snapshot!(
3060            p(1.second().milliseconds(61_123)),
3061            @"00:00:62.123",
3062        );
3063    }
3064
3065    #[test]
3066    fn print_span_hms_fraction_fixed_precision() {
3067        let printer = || SpanPrinter::new().hours_minutes_seconds(true);
3068        let p = |precision, span| {
3069            printer().precision(Some(precision)).span_to_string(&span)
3070        };
3071
3072        insta::assert_snapshot!(p(3, 1.second()), @"00:00:01.000");
3073        insta::assert_snapshot!(
3074            p(3, 1.second().milliseconds(1)),
3075            @"00:00:01.001",
3076        );
3077        insta::assert_snapshot!(
3078            p(3, 1.second().milliseconds(123)),
3079            @"00:00:01.123",
3080        );
3081        insta::assert_snapshot!(
3082            p(3, 1.second().milliseconds(100)),
3083            @"00:00:01.100",
3084        );
3085
3086        insta::assert_snapshot!(p(0, 1.second()), @"00:00:01");
3087        insta::assert_snapshot!(p(0, 1.second().milliseconds(1)), @"00:00:01");
3088        insta::assert_snapshot!(
3089            p(1, 1.second().milliseconds(999)),
3090            @"00:00:01.9",
3091        );
3092    }
3093
3094    #[test]
3095    fn print_duration_hms() {
3096        let printer = || SpanPrinter::new().hours_minutes_seconds(true);
3097        let p = |secs| {
3098            printer().duration_to_string(&SignedDuration::from_secs(secs))
3099        };
3100
3101        // Note the differences with `Span`, since with a `SignedDuration`,
3102        // all units are balanced.
3103
3104        insta::assert_snapshot!(p(1), @"00:00:01");
3105        insta::assert_snapshot!(p(2), @"00:00:02");
3106        insta::assert_snapshot!(p(10), @"00:00:10");
3107        insta::assert_snapshot!(p(100), @"00:01:40");
3108
3109        insta::assert_snapshot!(p(1 * 60), @"00:01:00");
3110        insta::assert_snapshot!(p(2 * 60), @"00:02:00");
3111        insta::assert_snapshot!(p(10 * 60), @"00:10:00");
3112        insta::assert_snapshot!(p(100 * 60), @"01:40:00");
3113
3114        insta::assert_snapshot!(p(1 * 60 * 60), @"01:00:00");
3115        insta::assert_snapshot!(p(2 * 60 * 60), @"02:00:00");
3116        insta::assert_snapshot!(p(10 * 60 * 60), @"10:00:00");
3117        insta::assert_snapshot!(p(100 * 60 * 60), @"100:00:00");
3118
3119        insta::assert_snapshot!(
3120            p(60 * 60 + 60 + 1),
3121            @"01:01:01",
3122        );
3123        insta::assert_snapshot!(
3124            p(2 * 60 * 60 + 2 * 60 + 2),
3125            @"02:02:02",
3126        );
3127        insta::assert_snapshot!(
3128            p(10 * 60 * 60 + 10 * 60 + 10),
3129            @"10:10:10",
3130        );
3131        insta::assert_snapshot!(
3132            p(100 * 60 * 60 + 100 * 60 + 100),
3133            @"101:41:40",
3134        );
3135    }
3136
3137    #[test]
3138    fn print_duration_hms_sign() {
3139        let printer = |direction| {
3140            SpanPrinter::new().hours_minutes_seconds(true).direction(direction)
3141        };
3142        let p = |direction, secs| {
3143            printer(direction)
3144                .duration_to_string(&SignedDuration::from_secs(secs))
3145        };
3146
3147        insta::assert_snapshot!(p(Direction::Auto, 1), @"00:00:01");
3148        insta::assert_snapshot!(p(Direction::Sign, 1), @"00:00:01");
3149        insta::assert_snapshot!(p(Direction::ForceSign, 1), @"+00:00:01");
3150        insta::assert_snapshot!(p(Direction::Suffix, 1), @"00:00:01");
3151
3152        insta::assert_snapshot!(p(Direction::Auto, -1), @"-00:00:01");
3153        insta::assert_snapshot!(p(Direction::Sign, -1), @"-00:00:01");
3154        insta::assert_snapshot!(p(Direction::ForceSign, -1), @"-00:00:01");
3155        insta::assert_snapshot!(p(Direction::Suffix, -1), @"00:00:01 ago");
3156    }
3157
3158    #[test]
3159    fn print_duration_hms_fraction_auto() {
3160        let printer = || SpanPrinter::new().hours_minutes_seconds(true);
3161        let p = |secs, nanos| {
3162            printer().duration_to_string(&SignedDuration::new(secs, nanos))
3163        };
3164
3165        insta::assert_snapshot!(p(0, 1), @"00:00:00.000000001");
3166        insta::assert_snapshot!(p(0, -1), @"-00:00:00.000000001");
3167        insta::assert_snapshot!(
3168            printer().direction(Direction::ForceSign).duration_to_string(
3169                &SignedDuration::new(0, 1),
3170            ),
3171            @"+00:00:00.000000001",
3172        );
3173
3174        insta::assert_snapshot!(
3175            p(1, 123),
3176            @"00:00:01.000000123",
3177        );
3178        insta::assert_snapshot!(
3179            p(1, 123_000_000),
3180            @"00:00:01.123",
3181        );
3182        insta::assert_snapshot!(
3183            p(1, 1_123_000_000),
3184            @"00:00:02.123",
3185        );
3186        insta::assert_snapshot!(
3187            p(61, 1_123_000_000),
3188            @"00:01:02.123",
3189        );
3190    }
3191
3192    #[test]
3193    fn print_duration_hms_fraction_fixed_precision() {
3194        let printer = || SpanPrinter::new().hours_minutes_seconds(true);
3195        let p = |precision, secs, nanos| {
3196            printer()
3197                .precision(Some(precision))
3198                .duration_to_string(&SignedDuration::new(secs, nanos))
3199        };
3200
3201        insta::assert_snapshot!(p(3, 1, 0), @"00:00:01.000");
3202        insta::assert_snapshot!(
3203            p(3, 1, 1_000_000),
3204            @"00:00:01.001",
3205        );
3206        insta::assert_snapshot!(
3207            p(3, 1, 123_000_000),
3208            @"00:00:01.123",
3209        );
3210        insta::assert_snapshot!(
3211            p(3, 1, 100_000_000),
3212            @"00:00:01.100",
3213        );
3214
3215        insta::assert_snapshot!(p(0, 1, 0), @"00:00:01");
3216        insta::assert_snapshot!(p(0, 1, 1_000_000), @"00:00:01");
3217        insta::assert_snapshot!(
3218            p(1, 1, 999_000_000),
3219            @"00:00:01.9",
3220        );
3221    }
3222}