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(feature = "alloc")]
1733#[cfg(test)]
1734mod tests {
1735    use crate::ToSpan;
1736
1737    use super::*;
1738
1739    #[test]
1740    fn print_span_designator_default() {
1741        let printer = || SpanPrinter::new();
1742        let p = |span| printer().span_to_string(&span);
1743
1744        insta::assert_snapshot!(p(1.second()), @"1s");
1745        insta::assert_snapshot!(p(2.seconds()), @"2s");
1746        insta::assert_snapshot!(p(10.seconds()), @"10s");
1747        insta::assert_snapshot!(p(1.minute().seconds(40)), @"1m 40s");
1748
1749        insta::assert_snapshot!(p(1.minute()), @"1m");
1750        insta::assert_snapshot!(p(2.minutes()), @"2m");
1751        insta::assert_snapshot!(p(10.minutes()), @"10m");
1752        insta::assert_snapshot!(p(1.hour().minutes(40)), @"1h 40m");
1753
1754        insta::assert_snapshot!(p(1.hour()), @"1h");
1755        insta::assert_snapshot!(p(2.hours()), @"2h");
1756        insta::assert_snapshot!(p(10.hours()), @"10h");
1757        insta::assert_snapshot!(p(100.hours()), @"100h");
1758
1759        insta::assert_snapshot!(
1760            p(1.hour().minutes(1).seconds(1)),
1761            @"1h 1m 1s",
1762        );
1763        insta::assert_snapshot!(
1764            p(2.hours().minutes(2).seconds(2)),
1765            @"2h 2m 2s",
1766        );
1767        insta::assert_snapshot!(
1768            p(10.hours().minutes(10).seconds(10)),
1769            @"10h 10m 10s",
1770        );
1771        insta::assert_snapshot!(
1772            p(100.hours().minutes(100).seconds(100)),
1773            @"100h 100m 100s",
1774        );
1775
1776        insta::assert_snapshot!(p(-1.hour()), @"1h ago");
1777        insta::assert_snapshot!(p(-1.hour().seconds(30)), @"1h 30s ago");
1778
1779        insta::assert_snapshot!(
1780            p(1.second().milliseconds(2000)),
1781            @"1s 2000ms",
1782        );
1783    }
1784
1785    #[test]
1786    fn print_span_designator_verbose() {
1787        let printer = || SpanPrinter::new().designator(Designator::Verbose);
1788        let p = |span| printer().span_to_string(&span);
1789
1790        insta::assert_snapshot!(p(1.second()), @"1second");
1791        insta::assert_snapshot!(p(2.seconds()), @"2seconds");
1792        insta::assert_snapshot!(p(10.seconds()), @"10seconds");
1793        insta::assert_snapshot!(p(1.minute().seconds(40)), @"1minute 40seconds");
1794
1795        insta::assert_snapshot!(p(1.minute()), @"1minute");
1796        insta::assert_snapshot!(p(2.minutes()), @"2minutes");
1797        insta::assert_snapshot!(p(10.minutes()), @"10minutes");
1798        insta::assert_snapshot!(p(1.hour().minutes(40)), @"1hour 40minutes");
1799
1800        insta::assert_snapshot!(p(1.hour()), @"1hour");
1801        insta::assert_snapshot!(p(2.hours()), @"2hours");
1802        insta::assert_snapshot!(p(10.hours()), @"10hours");
1803        insta::assert_snapshot!(p(100.hours()), @"100hours");
1804
1805        insta::assert_snapshot!(
1806            p(1.hour().minutes(1).seconds(1)),
1807            @"1hour 1minute 1second",
1808        );
1809        insta::assert_snapshot!(
1810            p(2.hours().minutes(2).seconds(2)),
1811            @"2hours 2minutes 2seconds",
1812        );
1813        insta::assert_snapshot!(
1814            p(10.hours().minutes(10).seconds(10)),
1815            @"10hours 10minutes 10seconds",
1816        );
1817        insta::assert_snapshot!(
1818            p(100.hours().minutes(100).seconds(100)),
1819            @"100hours 100minutes 100seconds",
1820        );
1821
1822        insta::assert_snapshot!(p(-1.hour()), @"1hour ago");
1823        insta::assert_snapshot!(p(-1.hour().seconds(30)), @"1hour 30seconds ago");
1824    }
1825
1826    #[test]
1827    fn print_span_designator_short() {
1828        let printer = || SpanPrinter::new().designator(Designator::Short);
1829        let p = |span| printer().span_to_string(&span);
1830
1831        insta::assert_snapshot!(p(1.second()), @"1sec");
1832        insta::assert_snapshot!(p(2.seconds()), @"2secs");
1833        insta::assert_snapshot!(p(10.seconds()), @"10secs");
1834        insta::assert_snapshot!(p(1.minute().seconds(40)), @"1min 40secs");
1835
1836        insta::assert_snapshot!(p(1.minute()), @"1min");
1837        insta::assert_snapshot!(p(2.minutes()), @"2mins");
1838        insta::assert_snapshot!(p(10.minutes()), @"10mins");
1839        insta::assert_snapshot!(p(1.hour().minutes(40)), @"1hr 40mins");
1840
1841        insta::assert_snapshot!(p(1.hour()), @"1hr");
1842        insta::assert_snapshot!(p(2.hours()), @"2hrs");
1843        insta::assert_snapshot!(p(10.hours()), @"10hrs");
1844        insta::assert_snapshot!(p(100.hours()), @"100hrs");
1845
1846        insta::assert_snapshot!(
1847            p(1.hour().minutes(1).seconds(1)),
1848            @"1hr 1min 1sec",
1849        );
1850        insta::assert_snapshot!(
1851            p(2.hours().minutes(2).seconds(2)),
1852            @"2hrs 2mins 2secs",
1853        );
1854        insta::assert_snapshot!(
1855            p(10.hours().minutes(10).seconds(10)),
1856            @"10hrs 10mins 10secs",
1857        );
1858        insta::assert_snapshot!(
1859            p(100.hours().minutes(100).seconds(100)),
1860            @"100hrs 100mins 100secs",
1861        );
1862
1863        insta::assert_snapshot!(p(-1.hour()), @"1hr ago");
1864        insta::assert_snapshot!(p(-1.hour().seconds(30)), @"1hr 30secs ago");
1865    }
1866
1867    #[test]
1868    fn print_span_designator_compact() {
1869        let printer = || SpanPrinter::new().designator(Designator::Compact);
1870        let p = |span| printer().span_to_string(&span);
1871
1872        insta::assert_snapshot!(p(1.second()), @"1s");
1873        insta::assert_snapshot!(p(2.seconds()), @"2s");
1874        insta::assert_snapshot!(p(10.seconds()), @"10s");
1875        insta::assert_snapshot!(p(1.minute().seconds(40)), @"1m 40s");
1876
1877        insta::assert_snapshot!(p(1.minute()), @"1m");
1878        insta::assert_snapshot!(p(2.minutes()), @"2m");
1879        insta::assert_snapshot!(p(10.minutes()), @"10m");
1880        insta::assert_snapshot!(p(1.hour().minutes(40)), @"1h 40m");
1881
1882        insta::assert_snapshot!(p(1.hour()), @"1h");
1883        insta::assert_snapshot!(p(2.hours()), @"2h");
1884        insta::assert_snapshot!(p(10.hours()), @"10h");
1885        insta::assert_snapshot!(p(100.hours()), @"100h");
1886
1887        insta::assert_snapshot!(
1888            p(1.hour().minutes(1).seconds(1)),
1889            @"1h 1m 1s",
1890        );
1891        insta::assert_snapshot!(
1892            p(2.hours().minutes(2).seconds(2)),
1893            @"2h 2m 2s",
1894        );
1895        insta::assert_snapshot!(
1896            p(10.hours().minutes(10).seconds(10)),
1897            @"10h 10m 10s",
1898        );
1899        insta::assert_snapshot!(
1900            p(100.hours().minutes(100).seconds(100)),
1901            @"100h 100m 100s",
1902        );
1903
1904        insta::assert_snapshot!(p(-1.hour()), @"1h ago");
1905        insta::assert_snapshot!(p(-1.hour().seconds(30)), @"1h 30s ago");
1906    }
1907
1908    #[test]
1909    fn print_span_designator_direction_force() {
1910        let printer = || SpanPrinter::new().direction(Direction::ForceSign);
1911        let p = |span| printer().span_to_string(&span);
1912
1913        insta::assert_snapshot!(p(1.second()), @"+1s");
1914        insta::assert_snapshot!(p(2.seconds()), @"+2s");
1915        insta::assert_snapshot!(p(10.seconds()), @"+10s");
1916        insta::assert_snapshot!(p(1.minute().seconds(40)), @"+1m 40s");
1917
1918        insta::assert_snapshot!(p(1.minute()), @"+1m");
1919        insta::assert_snapshot!(p(2.minutes()), @"+2m");
1920        insta::assert_snapshot!(p(10.minutes()), @"+10m");
1921        insta::assert_snapshot!(p(1.hour().minutes(40)), @"+1h 40m");
1922
1923        insta::assert_snapshot!(p(1.hour()), @"+1h");
1924        insta::assert_snapshot!(p(2.hours()), @"+2h");
1925        insta::assert_snapshot!(p(10.hours()), @"+10h");
1926        insta::assert_snapshot!(p(100.hours()), @"+100h");
1927
1928        insta::assert_snapshot!(
1929            p(1.hour().minutes(1).seconds(1)),
1930            @"+1h 1m 1s",
1931        );
1932        insta::assert_snapshot!(
1933            p(2.hours().minutes(2).seconds(2)),
1934            @"+2h 2m 2s",
1935        );
1936        insta::assert_snapshot!(
1937            p(10.hours().minutes(10).seconds(10)),
1938            @"+10h 10m 10s",
1939        );
1940        insta::assert_snapshot!(
1941            p(100.hours().minutes(100).seconds(100)),
1942            @"+100h 100m 100s",
1943        );
1944
1945        insta::assert_snapshot!(p(-1.hour()), @"-1h");
1946        insta::assert_snapshot!(p(-1.hour().seconds(30)), @"-1h 30s");
1947    }
1948
1949    #[test]
1950    fn print_span_designator_padding() {
1951        let printer = || SpanPrinter::new().padding(2);
1952        let p = |span| printer().span_to_string(&span);
1953
1954        insta::assert_snapshot!(p(1.second()), @"01s");
1955        insta::assert_snapshot!(p(2.seconds()), @"02s");
1956        insta::assert_snapshot!(p(10.seconds()), @"10s");
1957        insta::assert_snapshot!(p(1.minute().seconds(40)), @"01m 40s");
1958
1959        insta::assert_snapshot!(p(1.minute()), @"01m");
1960        insta::assert_snapshot!(p(2.minutes()), @"02m");
1961        insta::assert_snapshot!(p(10.minutes()), @"10m");
1962        insta::assert_snapshot!(p(1.hour().minutes(40)), @"01h 40m");
1963
1964        insta::assert_snapshot!(p(1.hour()), @"01h");
1965        insta::assert_snapshot!(p(2.hours()), @"02h");
1966        insta::assert_snapshot!(p(10.hours()), @"10h");
1967        insta::assert_snapshot!(p(100.hours()), @"100h");
1968
1969        insta::assert_snapshot!(
1970            p(1.hour().minutes(1).seconds(1)),
1971            @"01h 01m 01s",
1972        );
1973        insta::assert_snapshot!(
1974            p(2.hours().minutes(2).seconds(2)),
1975            @"02h 02m 02s",
1976        );
1977        insta::assert_snapshot!(
1978            p(10.hours().minutes(10).seconds(10)),
1979            @"10h 10m 10s",
1980        );
1981        insta::assert_snapshot!(
1982            p(100.hours().minutes(100).seconds(100)),
1983            @"100h 100m 100s",
1984        );
1985
1986        insta::assert_snapshot!(p(-1.hour()), @"01h ago");
1987        insta::assert_snapshot!(p(-1.hour().seconds(30)), @"01h 30s ago");
1988    }
1989
1990    #[test]
1991    fn print_span_designator_spacing_none() {
1992        let printer = || SpanPrinter::new().spacing(Spacing::None);
1993        let p = |span| printer().span_to_string(&span);
1994
1995        insta::assert_snapshot!(p(1.second()), @"1s");
1996        insta::assert_snapshot!(p(2.seconds()), @"2s");
1997        insta::assert_snapshot!(p(10.seconds()), @"10s");
1998        insta::assert_snapshot!(p(1.minute().seconds(40)), @"1m40s");
1999
2000        insta::assert_snapshot!(p(1.minute()), @"1m");
2001        insta::assert_snapshot!(p(2.minutes()), @"2m");
2002        insta::assert_snapshot!(p(10.minutes()), @"10m");
2003        insta::assert_snapshot!(p(1.hour().minutes(40)), @"1h40m");
2004
2005        insta::assert_snapshot!(p(1.hour()), @"1h");
2006        insta::assert_snapshot!(p(2.hours()), @"2h");
2007        insta::assert_snapshot!(p(10.hours()), @"10h");
2008        insta::assert_snapshot!(p(100.hours()), @"100h");
2009
2010        insta::assert_snapshot!(
2011            p(1.hour().minutes(1).seconds(1)),
2012            @"1h1m1s",
2013        );
2014        insta::assert_snapshot!(
2015            p(2.hours().minutes(2).seconds(2)),
2016            @"2h2m2s",
2017        );
2018        insta::assert_snapshot!(
2019            p(10.hours().minutes(10).seconds(10)),
2020            @"10h10m10s",
2021        );
2022        insta::assert_snapshot!(
2023            p(100.hours().minutes(100).seconds(100)),
2024            @"100h100m100s",
2025        );
2026
2027        insta::assert_snapshot!(p(-1.hour()), @"-1h");
2028        insta::assert_snapshot!(p(-1.hour().seconds(30)), @"-1h30s");
2029    }
2030
2031    #[test]
2032    fn print_span_designator_spacing_more() {
2033        let printer =
2034            || SpanPrinter::new().spacing(Spacing::BetweenUnitsAndDesignators);
2035        let p = |span| printer().span_to_string(&span);
2036
2037        insta::assert_snapshot!(p(1.second()), @"1 s");
2038        insta::assert_snapshot!(p(2.seconds()), @"2 s");
2039        insta::assert_snapshot!(p(10.seconds()), @"10 s");
2040        insta::assert_snapshot!(p(1.minute().seconds(40)), @"1 m 40 s");
2041
2042        insta::assert_snapshot!(p(1.minute()), @"1 m");
2043        insta::assert_snapshot!(p(2.minutes()), @"2 m");
2044        insta::assert_snapshot!(p(10.minutes()), @"10 m");
2045        insta::assert_snapshot!(p(1.hour().minutes(40)), @"1 h 40 m");
2046
2047        insta::assert_snapshot!(p(1.hour()), @"1 h");
2048        insta::assert_snapshot!(p(2.hours()), @"2 h");
2049        insta::assert_snapshot!(p(10.hours()), @"10 h");
2050        insta::assert_snapshot!(p(100.hours()), @"100 h");
2051
2052        insta::assert_snapshot!(
2053            p(1.hour().minutes(1).seconds(1)),
2054            @"1 h 1 m 1 s",
2055        );
2056        insta::assert_snapshot!(
2057            p(2.hours().minutes(2).seconds(2)),
2058            @"2 h 2 m 2 s",
2059        );
2060        insta::assert_snapshot!(
2061            p(10.hours().minutes(10).seconds(10)),
2062            @"10 h 10 m 10 s",
2063        );
2064        insta::assert_snapshot!(
2065            p(100.hours().minutes(100).seconds(100)),
2066            @"100 h 100 m 100 s",
2067        );
2068
2069        insta::assert_snapshot!(p(-1.hour()), @"1 h ago");
2070        insta::assert_snapshot!(p(-1.hour().seconds(30)), @"1 h 30 s ago");
2071    }
2072
2073    #[test]
2074    fn print_span_designator_spacing_comma() {
2075        let printer = || {
2076            SpanPrinter::new()
2077                .comma_after_designator(true)
2078                .spacing(Spacing::BetweenUnitsAndDesignators)
2079        };
2080        let p = |span| printer().span_to_string(&span);
2081
2082        insta::assert_snapshot!(p(1.second()), @"1 s");
2083        insta::assert_snapshot!(p(2.seconds()), @"2 s");
2084        insta::assert_snapshot!(p(10.seconds()), @"10 s");
2085        insta::assert_snapshot!(p(1.minute().seconds(40)), @"1 m, 40 s");
2086
2087        insta::assert_snapshot!(p(1.minute()), @"1 m");
2088        insta::assert_snapshot!(p(2.minutes()), @"2 m");
2089        insta::assert_snapshot!(p(10.minutes()), @"10 m");
2090        insta::assert_snapshot!(p(1.hour().minutes(40)), @"1 h, 40 m");
2091
2092        insta::assert_snapshot!(p(1.hour()), @"1 h");
2093        insta::assert_snapshot!(p(2.hours()), @"2 h");
2094        insta::assert_snapshot!(p(10.hours()), @"10 h");
2095        insta::assert_snapshot!(p(100.hours()), @"100 h");
2096
2097        insta::assert_snapshot!(
2098            p(1.hour().minutes(1).seconds(1)),
2099            @"1 h, 1 m, 1 s",
2100        );
2101        insta::assert_snapshot!(
2102            p(2.hours().minutes(2).seconds(2)),
2103            @"2 h, 2 m, 2 s",
2104        );
2105        insta::assert_snapshot!(
2106            p(10.hours().minutes(10).seconds(10)),
2107            @"10 h, 10 m, 10 s",
2108        );
2109        insta::assert_snapshot!(
2110            p(100.hours().minutes(100).seconds(100)),
2111            @"100 h, 100 m, 100 s",
2112        );
2113
2114        insta::assert_snapshot!(p(-1.hour()), @"1 h ago");
2115        insta::assert_snapshot!(p(-1.hour().seconds(30)), @"1 h, 30 s ago");
2116    }
2117
2118    #[test]
2119    fn print_span_designator_fractional_hour() {
2120        let printer =
2121            || SpanPrinter::new().fractional(Some(FractionalUnit::Hour));
2122        let p = |span| printer().span_to_string(&span);
2123        let pp = |precision, span| {
2124            printer().precision(Some(precision)).span_to_string(&span)
2125        };
2126
2127        insta::assert_snapshot!(p(1.hour()), @"1h");
2128        insta::assert_snapshot!(pp(0, 1.hour()), @"1h");
2129        insta::assert_snapshot!(pp(1, 1.hour()), @"1.0h");
2130        insta::assert_snapshot!(pp(2, 1.hour()), @"1.00h");
2131
2132        insta::assert_snapshot!(p(1.hour().minutes(30)), @"1.5h");
2133        insta::assert_snapshot!(pp(0, 1.hour().minutes(30)), @"1h");
2134        insta::assert_snapshot!(pp(1, 1.hour().minutes(30)), @"1.5h");
2135        insta::assert_snapshot!(pp(2, 1.hour().minutes(30)), @"1.50h");
2136
2137        insta::assert_snapshot!(p(1.hour().minutes(3)), @"1.05h");
2138        insta::assert_snapshot!(p(1.hour().minutes(3).nanoseconds(1)), @"1.05h");
2139        insta::assert_snapshot!(p(1.second()), @"0.000277777h");
2140        // precision loss!
2141        insta::assert_snapshot!(p(1.second().nanoseconds(1)), @"0.000277777h");
2142        insta::assert_snapshot!(p(0.seconds()), @"0h");
2143        // precision loss!
2144        insta::assert_snapshot!(p(1.nanosecond()), @"0h");
2145    }
2146
2147    #[test]
2148    fn print_span_designator_fractional_minute() {
2149        let printer =
2150            || SpanPrinter::new().fractional(Some(FractionalUnit::Minute));
2151        let p = |span| printer().span_to_string(&span);
2152        let pp = |precision, span| {
2153            printer().precision(Some(precision)).span_to_string(&span)
2154        };
2155
2156        insta::assert_snapshot!(p(1.hour()), @"1h");
2157        insta::assert_snapshot!(p(1.hour().minutes(30)), @"1h 30m");
2158
2159        insta::assert_snapshot!(p(1.minute()), @"1m");
2160        insta::assert_snapshot!(pp(0, 1.minute()), @"1m");
2161        insta::assert_snapshot!(pp(1, 1.minute()), @"1.0m");
2162        insta::assert_snapshot!(pp(2, 1.minute()), @"1.00m");
2163
2164        insta::assert_snapshot!(p(1.minute().seconds(30)), @"1.5m");
2165        insta::assert_snapshot!(pp(0, 1.minute().seconds(30)), @"1m");
2166        insta::assert_snapshot!(pp(1, 1.minute().seconds(30)), @"1.5m");
2167        insta::assert_snapshot!(pp(2, 1.minute().seconds(30)), @"1.50m");
2168
2169        insta::assert_snapshot!(p(1.hour().nanoseconds(1)), @"1h");
2170        insta::assert_snapshot!(p(1.minute().seconds(3)), @"1.05m");
2171        insta::assert_snapshot!(p(1.minute().seconds(3).nanoseconds(1)), @"1.05m");
2172        insta::assert_snapshot!(p(1.second()), @"0.016666666m");
2173        // precision loss!
2174        insta::assert_snapshot!(p(1.second().nanoseconds(1)), @"0.016666666m");
2175        insta::assert_snapshot!(p(0.seconds()), @"0m");
2176        // precision loss!
2177        insta::assert_snapshot!(p(1.nanosecond()), @"0m");
2178    }
2179
2180    #[test]
2181    fn print_span_designator_fractional_second() {
2182        let printer =
2183            || SpanPrinter::new().fractional(Some(FractionalUnit::Second));
2184        let p = |span| printer().span_to_string(&span);
2185        let pp = |precision, span| {
2186            printer().precision(Some(precision)).span_to_string(&span)
2187        };
2188
2189        insta::assert_snapshot!(p(1.hour()), @"1h");
2190        insta::assert_snapshot!(p(1.hour().minutes(30)), @"1h 30m");
2191
2192        insta::assert_snapshot!(p(1.second()), @"1s");
2193        insta::assert_snapshot!(pp(0, 1.second()), @"1s");
2194        insta::assert_snapshot!(pp(1, 1.second()), @"1.0s");
2195        insta::assert_snapshot!(pp(2, 1.second()), @"1.00s");
2196
2197        insta::assert_snapshot!(p(1.second().milliseconds(500)), @"1.5s");
2198        insta::assert_snapshot!(pp(0, 1.second().milliseconds(500)), @"1s");
2199        insta::assert_snapshot!(pp(1, 1.second().milliseconds(500)), @"1.5s");
2200        insta::assert_snapshot!(pp(2, 1.second().milliseconds(500)), @"1.50s");
2201
2202        insta::assert_snapshot!(p(1.second().nanoseconds(1)), @"1.000000001s");
2203        insta::assert_snapshot!(p(1.nanosecond()), @"0.000000001s");
2204        insta::assert_snapshot!(p(0.seconds()), @"0s");
2205
2206        insta::assert_snapshot!(p(1.second().milliseconds(2000)), @"3s");
2207    }
2208
2209    #[test]
2210    fn print_span_designator_fractional_millisecond() {
2211        let printer = || {
2212            SpanPrinter::new().fractional(Some(FractionalUnit::Millisecond))
2213        };
2214        let p = |span| printer().span_to_string(&span);
2215        let pp = |precision, span| {
2216            printer().precision(Some(precision)).span_to_string(&span)
2217        };
2218
2219        insta::assert_snapshot!(p(1.hour()), @"1h");
2220        insta::assert_snapshot!(p(1.hour().minutes(30)), @"1h 30m");
2221        insta::assert_snapshot!(
2222            p(1.hour().minutes(30).seconds(10)),
2223            @"1h 30m 10s",
2224        );
2225
2226        insta::assert_snapshot!(p(1.second()), @"1s");
2227        insta::assert_snapshot!(pp(0, 1.second()), @"1s");
2228        insta::assert_snapshot!(pp(1, 1.second()), @"1s 0.0ms");
2229        insta::assert_snapshot!(pp(2, 1.second()), @"1s 0.00ms");
2230
2231        insta::assert_snapshot!(p(1.second().milliseconds(500)), @"1s 500ms");
2232        insta::assert_snapshot!(
2233            pp(0, 1.second().milliseconds(1).microseconds(500)),
2234            @"1s 1ms",
2235        );
2236        insta::assert_snapshot!(
2237            pp(1, 1.second().milliseconds(1).microseconds(500)),
2238            @"1s 1.5ms",
2239        );
2240        insta::assert_snapshot!(
2241            pp(2, 1.second().milliseconds(1).microseconds(500)),
2242            @"1s 1.50ms",
2243        );
2244
2245        insta::assert_snapshot!(p(1.millisecond().nanoseconds(1)), @"1.000001ms");
2246        insta::assert_snapshot!(p(1.nanosecond()), @"0.000001ms");
2247        insta::assert_snapshot!(p(0.seconds()), @"0ms");
2248    }
2249
2250    #[test]
2251    fn print_span_designator_fractional_microsecond() {
2252        let printer = || {
2253            SpanPrinter::new().fractional(Some(FractionalUnit::Microsecond))
2254        };
2255        let p = |span| printer().span_to_string(&span);
2256        let pp = |precision, span| {
2257            printer().precision(Some(precision)).span_to_string(&span)
2258        };
2259
2260        insta::assert_snapshot!(p(1.hour()), @"1h");
2261        insta::assert_snapshot!(p(1.hour().minutes(30)), @"1h 30m");
2262        insta::assert_snapshot!(
2263            p(1.hour().minutes(30).seconds(10)),
2264            @"1h 30m 10s",
2265        );
2266
2267        insta::assert_snapshot!(p(1.second()), @"1s");
2268        insta::assert_snapshot!(pp(0, 1.second()), @"1s");
2269        insta::assert_snapshot!(pp(1, 1.second()), @"1s 0.0µs");
2270        insta::assert_snapshot!(pp(2, 1.second()), @"1s 0.00µs");
2271
2272        insta::assert_snapshot!(p(1.second().milliseconds(500)), @"1s 500ms");
2273        insta::assert_snapshot!(
2274            pp(0, 1.second().milliseconds(1).microseconds(500)),
2275            @"1s 1ms 500µs",
2276        );
2277        insta::assert_snapshot!(
2278            pp(1, 1.second().milliseconds(1).microseconds(500)),
2279            @"1s 1ms 500.0µs",
2280        );
2281        insta::assert_snapshot!(
2282            pp(2, 1.second().milliseconds(1).microseconds(500)),
2283            @"1s 1ms 500.00µs",
2284        );
2285
2286        insta::assert_snapshot!(
2287            p(1.millisecond().nanoseconds(1)),
2288            @"1ms 0.001µs",
2289        );
2290        insta::assert_snapshot!(p(1.nanosecond()), @"0.001µs");
2291        insta::assert_snapshot!(p(0.second()), @"0µs");
2292    }
2293
2294    #[test]
2295    fn print_duration_designator_default() {
2296        let printer = || SpanPrinter::new();
2297        let p = |secs| {
2298            printer().duration_to_string(&SignedDuration::from_secs(secs))
2299        };
2300
2301        insta::assert_snapshot!(p(1), @"1s");
2302        insta::assert_snapshot!(p(2), @"2s");
2303        insta::assert_snapshot!(p(10), @"10s");
2304        insta::assert_snapshot!(p(100), @"1m 40s");
2305
2306        insta::assert_snapshot!(p(1 * 60), @"1m");
2307        insta::assert_snapshot!(p(2 * 60), @"2m");
2308        insta::assert_snapshot!(p(10 * 60), @"10m");
2309        insta::assert_snapshot!(p(100 * 60), @"1h 40m");
2310
2311        insta::assert_snapshot!(p(1 * 60 * 60), @"1h");
2312        insta::assert_snapshot!(p(2 * 60 * 60), @"2h");
2313        insta::assert_snapshot!(p(10 * 60 * 60), @"10h");
2314        insta::assert_snapshot!(p(100 * 60 * 60), @"100h");
2315
2316        insta::assert_snapshot!(
2317            p(60 * 60 + 60 + 1),
2318            @"1h 1m 1s",
2319        );
2320        insta::assert_snapshot!(
2321            p(2 * 60 * 60 + 2 * 60 + 2),
2322            @"2h 2m 2s",
2323        );
2324        insta::assert_snapshot!(
2325            p(10 * 60 * 60 + 10 * 60 + 10),
2326            @"10h 10m 10s",
2327        );
2328        insta::assert_snapshot!(
2329            p(100 * 60 * 60 + 100 * 60 + 100),
2330            @"101h 41m 40s",
2331        );
2332
2333        insta::assert_snapshot!(p(-1 * 60 * 60), @"1h ago");
2334        insta::assert_snapshot!(p(-1 * 60 * 60 - 30), @"1h 30s ago");
2335    }
2336
2337    #[test]
2338    fn print_duration_designator_verbose() {
2339        let printer = || SpanPrinter::new().designator(Designator::Verbose);
2340        let p = |secs| {
2341            printer().duration_to_string(&SignedDuration::from_secs(secs))
2342        };
2343
2344        insta::assert_snapshot!(p(1), @"1second");
2345        insta::assert_snapshot!(p(2), @"2seconds");
2346        insta::assert_snapshot!(p(10), @"10seconds");
2347        insta::assert_snapshot!(p(100), @"1minute 40seconds");
2348
2349        insta::assert_snapshot!(p(1 * 60), @"1minute");
2350        insta::assert_snapshot!(p(2 * 60), @"2minutes");
2351        insta::assert_snapshot!(p(10 * 60), @"10minutes");
2352        insta::assert_snapshot!(p(100 * 60), @"1hour 40minutes");
2353
2354        insta::assert_snapshot!(p(1 * 60 * 60), @"1hour");
2355        insta::assert_snapshot!(p(2 * 60 * 60), @"2hours");
2356        insta::assert_snapshot!(p(10 * 60 * 60), @"10hours");
2357        insta::assert_snapshot!(p(100 * 60 * 60), @"100hours");
2358
2359        insta::assert_snapshot!(
2360            p(60 * 60 + 60 + 1),
2361            @"1hour 1minute 1second",
2362        );
2363        insta::assert_snapshot!(
2364            p(2 * 60 * 60 + 2 * 60 + 2),
2365            @"2hours 2minutes 2seconds",
2366        );
2367        insta::assert_snapshot!(
2368            p(10 * 60 * 60 + 10 * 60 + 10),
2369            @"10hours 10minutes 10seconds",
2370        );
2371        insta::assert_snapshot!(
2372            p(100 * 60 * 60 + 100 * 60 + 100),
2373            @"101hours 41minutes 40seconds",
2374        );
2375
2376        insta::assert_snapshot!(p(-1 * 60 * 60), @"1hour ago");
2377        insta::assert_snapshot!(p(-1 * 60 * 60 - 30), @"1hour 30seconds ago");
2378    }
2379
2380    #[test]
2381    fn print_duration_designator_short() {
2382        let printer = || SpanPrinter::new().designator(Designator::Short);
2383        let p = |secs| {
2384            printer().duration_to_string(&SignedDuration::from_secs(secs))
2385        };
2386
2387        insta::assert_snapshot!(p(1), @"1sec");
2388        insta::assert_snapshot!(p(2), @"2secs");
2389        insta::assert_snapshot!(p(10), @"10secs");
2390        insta::assert_snapshot!(p(100), @"1min 40secs");
2391
2392        insta::assert_snapshot!(p(1 * 60), @"1min");
2393        insta::assert_snapshot!(p(2 * 60), @"2mins");
2394        insta::assert_snapshot!(p(10 * 60), @"10mins");
2395        insta::assert_snapshot!(p(100 * 60), @"1hr 40mins");
2396
2397        insta::assert_snapshot!(p(1 * 60 * 60), @"1hr");
2398        insta::assert_snapshot!(p(2 * 60 * 60), @"2hrs");
2399        insta::assert_snapshot!(p(10 * 60 * 60), @"10hrs");
2400        insta::assert_snapshot!(p(100 * 60 * 60), @"100hrs");
2401
2402        insta::assert_snapshot!(
2403            p(60 * 60 + 60 + 1),
2404            @"1hr 1min 1sec",
2405        );
2406        insta::assert_snapshot!(
2407            p(2 * 60 * 60 + 2 * 60 + 2),
2408            @"2hrs 2mins 2secs",
2409        );
2410        insta::assert_snapshot!(
2411            p(10 * 60 * 60 + 10 * 60 + 10),
2412            @"10hrs 10mins 10secs",
2413        );
2414        insta::assert_snapshot!(
2415            p(100 * 60 * 60 + 100 * 60 + 100),
2416            @"101hrs 41mins 40secs",
2417        );
2418
2419        insta::assert_snapshot!(p(-1 * 60 * 60), @"1hr ago");
2420        insta::assert_snapshot!(p(-1 * 60 * 60 - 30), @"1hr 30secs ago");
2421    }
2422
2423    #[test]
2424    fn print_duration_designator_compact() {
2425        let printer = || SpanPrinter::new().designator(Designator::Compact);
2426        let p = |secs| {
2427            printer().duration_to_string(&SignedDuration::from_secs(secs))
2428        };
2429
2430        insta::assert_snapshot!(p(1), @"1s");
2431        insta::assert_snapshot!(p(2), @"2s");
2432        insta::assert_snapshot!(p(10), @"10s");
2433        insta::assert_snapshot!(p(100), @"1m 40s");
2434
2435        insta::assert_snapshot!(p(1 * 60), @"1m");
2436        insta::assert_snapshot!(p(2 * 60), @"2m");
2437        insta::assert_snapshot!(p(10 * 60), @"10m");
2438        insta::assert_snapshot!(p(100 * 60), @"1h 40m");
2439
2440        insta::assert_snapshot!(p(1 * 60 * 60), @"1h");
2441        insta::assert_snapshot!(p(2 * 60 * 60), @"2h");
2442        insta::assert_snapshot!(p(10 * 60 * 60), @"10h");
2443        insta::assert_snapshot!(p(100 * 60 * 60), @"100h");
2444
2445        insta::assert_snapshot!(
2446            p(60 * 60 + 60 + 1),
2447            @"1h 1m 1s",
2448        );
2449        insta::assert_snapshot!(
2450            p(2 * 60 * 60 + 2 * 60 + 2),
2451            @"2h 2m 2s",
2452        );
2453        insta::assert_snapshot!(
2454            p(10 * 60 * 60 + 10 * 60 + 10),
2455            @"10h 10m 10s",
2456        );
2457        insta::assert_snapshot!(
2458            p(100 * 60 * 60 + 100 * 60 + 100),
2459            @"101h 41m 40s",
2460        );
2461
2462        insta::assert_snapshot!(p(-1 * 60 * 60), @"1h ago");
2463        insta::assert_snapshot!(p(-1 * 60 * 60 - 30), @"1h 30s ago");
2464    }
2465
2466    #[test]
2467    fn print_duration_designator_direction_force() {
2468        let printer = || SpanPrinter::new().direction(Direction::ForceSign);
2469        let p = |secs| {
2470            printer().duration_to_string(&SignedDuration::from_secs(secs))
2471        };
2472
2473        insta::assert_snapshot!(p(1), @"+1s");
2474        insta::assert_snapshot!(p(2), @"+2s");
2475        insta::assert_snapshot!(p(10), @"+10s");
2476        insta::assert_snapshot!(p(100), @"+1m 40s");
2477
2478        insta::assert_snapshot!(p(1 * 60), @"+1m");
2479        insta::assert_snapshot!(p(2 * 60), @"+2m");
2480        insta::assert_snapshot!(p(10 * 60), @"+10m");
2481        insta::assert_snapshot!(p(100 * 60), @"+1h 40m");
2482
2483        insta::assert_snapshot!(p(1 * 60 * 60), @"+1h");
2484        insta::assert_snapshot!(p(2 * 60 * 60), @"+2h");
2485        insta::assert_snapshot!(p(10 * 60 * 60), @"+10h");
2486        insta::assert_snapshot!(p(100 * 60 * 60), @"+100h");
2487
2488        insta::assert_snapshot!(
2489            p(60 * 60 + 60 + 1),
2490            @"+1h 1m 1s",
2491        );
2492        insta::assert_snapshot!(
2493            p(2 * 60 * 60 + 2 * 60 + 2),
2494            @"+2h 2m 2s",
2495        );
2496        insta::assert_snapshot!(
2497            p(10 * 60 * 60 + 10 * 60 + 10),
2498            @"+10h 10m 10s",
2499        );
2500        insta::assert_snapshot!(
2501            p(100 * 60 * 60 + 100 * 60 + 100),
2502            @"+101h 41m 40s",
2503        );
2504
2505        insta::assert_snapshot!(p(-1 * 60 * 60), @"-1h");
2506        insta::assert_snapshot!(p(-1 * 60 * 60 - 30), @"-1h 30s");
2507    }
2508
2509    #[test]
2510    fn print_duration_designator_padding() {
2511        let printer = || SpanPrinter::new().padding(2);
2512        let p = |secs| {
2513            printer().duration_to_string(&SignedDuration::from_secs(secs))
2514        };
2515
2516        insta::assert_snapshot!(p(1), @"01s");
2517        insta::assert_snapshot!(p(2), @"02s");
2518        insta::assert_snapshot!(p(10), @"10s");
2519        insta::assert_snapshot!(p(100), @"01m 40s");
2520
2521        insta::assert_snapshot!(p(1 * 60), @"01m");
2522        insta::assert_snapshot!(p(2 * 60), @"02m");
2523        insta::assert_snapshot!(p(10 * 60), @"10m");
2524        insta::assert_snapshot!(p(100 * 60), @"01h 40m");
2525
2526        insta::assert_snapshot!(p(1 * 60 * 60), @"01h");
2527        insta::assert_snapshot!(p(2 * 60 * 60), @"02h");
2528        insta::assert_snapshot!(p(10 * 60 * 60), @"10h");
2529        insta::assert_snapshot!(p(100 * 60 * 60), @"100h");
2530
2531        insta::assert_snapshot!(
2532            p(60 * 60 + 60 + 1),
2533            @"01h 01m 01s",
2534        );
2535        insta::assert_snapshot!(
2536            p(2 * 60 * 60 + 2 * 60 + 2),
2537            @"02h 02m 02s",
2538        );
2539        insta::assert_snapshot!(
2540            p(10 * 60 * 60 + 10 * 60 + 10),
2541            @"10h 10m 10s",
2542        );
2543        insta::assert_snapshot!(
2544            p(100 * 60 * 60 + 100 * 60 + 100),
2545            @"101h 41m 40s",
2546        );
2547
2548        insta::assert_snapshot!(p(-1 * 60 * 60), @"01h ago");
2549        insta::assert_snapshot!(p(-1 * 60 * 60 - 30), @"01h 30s ago");
2550    }
2551
2552    #[test]
2553    fn print_duration_designator_spacing_none() {
2554        let printer = || SpanPrinter::new().spacing(Spacing::None);
2555        let p = |secs| {
2556            printer().duration_to_string(&SignedDuration::from_secs(secs))
2557        };
2558
2559        insta::assert_snapshot!(p(1), @"1s");
2560        insta::assert_snapshot!(p(2), @"2s");
2561        insta::assert_snapshot!(p(10), @"10s");
2562        insta::assert_snapshot!(p(100), @"1m40s");
2563
2564        insta::assert_snapshot!(p(1 * 60), @"1m");
2565        insta::assert_snapshot!(p(2 * 60), @"2m");
2566        insta::assert_snapshot!(p(10 * 60), @"10m");
2567        insta::assert_snapshot!(p(100 * 60), @"1h40m");
2568
2569        insta::assert_snapshot!(p(1 * 60 * 60), @"1h");
2570        insta::assert_snapshot!(p(2 * 60 * 60), @"2h");
2571        insta::assert_snapshot!(p(10 * 60 * 60), @"10h");
2572        insta::assert_snapshot!(p(100 * 60 * 60), @"100h");
2573
2574        insta::assert_snapshot!(
2575            p(60 * 60 + 60 + 1),
2576            @"1h1m1s",
2577        );
2578        insta::assert_snapshot!(
2579            p(2 * 60 * 60 + 2 * 60 + 2),
2580            @"2h2m2s",
2581        );
2582        insta::assert_snapshot!(
2583            p(10 * 60 * 60 + 10 * 60 + 10),
2584            @"10h10m10s",
2585        );
2586        insta::assert_snapshot!(
2587            p(100 * 60 * 60 + 100 * 60 + 100),
2588            @"101h41m40s",
2589        );
2590
2591        insta::assert_snapshot!(p(-1 * 60 * 60), @"-1h");
2592        insta::assert_snapshot!(p(-1 * 60 * 60 - 30), @"-1h30s");
2593    }
2594
2595    #[test]
2596    fn print_duration_designator_spacing_more() {
2597        let printer =
2598            || SpanPrinter::new().spacing(Spacing::BetweenUnitsAndDesignators);
2599        let p = |secs| {
2600            printer().duration_to_string(&SignedDuration::from_secs(secs))
2601        };
2602
2603        insta::assert_snapshot!(p(1), @"1 s");
2604        insta::assert_snapshot!(p(2), @"2 s");
2605        insta::assert_snapshot!(p(10), @"10 s");
2606        insta::assert_snapshot!(p(100), @"1 m 40 s");
2607
2608        insta::assert_snapshot!(p(1 * 60), @"1 m");
2609        insta::assert_snapshot!(p(2 * 60), @"2 m");
2610        insta::assert_snapshot!(p(10 * 60), @"10 m");
2611        insta::assert_snapshot!(p(100 * 60), @"1 h 40 m");
2612
2613        insta::assert_snapshot!(p(1 * 60 * 60), @"1 h");
2614        insta::assert_snapshot!(p(2 * 60 * 60), @"2 h");
2615        insta::assert_snapshot!(p(10 * 60 * 60), @"10 h");
2616        insta::assert_snapshot!(p(100 * 60 * 60), @"100 h");
2617
2618        insta::assert_snapshot!(
2619            p(60 * 60 + 60 + 1),
2620            @"1 h 1 m 1 s",
2621        );
2622        insta::assert_snapshot!(
2623            p(2 * 60 * 60 + 2 * 60 + 2),
2624            @"2 h 2 m 2 s",
2625        );
2626        insta::assert_snapshot!(
2627            p(10 * 60 * 60 + 10 * 60 + 10),
2628            @"10 h 10 m 10 s",
2629        );
2630        insta::assert_snapshot!(
2631            p(100 * 60 * 60 + 100 * 60 + 100),
2632            @"101 h 41 m 40 s",
2633        );
2634
2635        insta::assert_snapshot!(p(-1 * 60 * 60), @"1 h ago");
2636        insta::assert_snapshot!(p(-1 * 60 * 60 - 30), @"1 h 30 s ago");
2637    }
2638
2639    #[test]
2640    fn print_duration_designator_spacing_comma() {
2641        let printer = || {
2642            SpanPrinter::new()
2643                .comma_after_designator(true)
2644                .spacing(Spacing::BetweenUnitsAndDesignators)
2645        };
2646        let p = |secs| {
2647            printer().duration_to_string(&SignedDuration::from_secs(secs))
2648        };
2649
2650        insta::assert_snapshot!(p(1), @"1 s");
2651        insta::assert_snapshot!(p(2), @"2 s");
2652        insta::assert_snapshot!(p(10), @"10 s");
2653        insta::assert_snapshot!(p(100), @"1 m, 40 s");
2654
2655        insta::assert_snapshot!(p(1 * 60), @"1 m");
2656        insta::assert_snapshot!(p(2 * 60), @"2 m");
2657        insta::assert_snapshot!(p(10 * 60), @"10 m");
2658        insta::assert_snapshot!(p(100 * 60), @"1 h, 40 m");
2659
2660        insta::assert_snapshot!(p(1 * 60 * 60), @"1 h");
2661        insta::assert_snapshot!(p(2 * 60 * 60), @"2 h");
2662        insta::assert_snapshot!(p(10 * 60 * 60), @"10 h");
2663        insta::assert_snapshot!(p(100 * 60 * 60), @"100 h");
2664
2665        insta::assert_snapshot!(
2666            p(60 * 60 + 60 + 1),
2667            @"1 h, 1 m, 1 s",
2668        );
2669        insta::assert_snapshot!(
2670            p(2 * 60 * 60 + 2 * 60 + 2),
2671            @"2 h, 2 m, 2 s",
2672        );
2673        insta::assert_snapshot!(
2674            p(10 * 60 * 60 + 10 * 60 + 10),
2675            @"10 h, 10 m, 10 s",
2676        );
2677        insta::assert_snapshot!(
2678            p(100 * 60 * 60 + 100 * 60 + 100),
2679            @"101 h, 41 m, 40 s",
2680        );
2681
2682        insta::assert_snapshot!(p(-1 * 60 * 60), @"1 h ago");
2683        insta::assert_snapshot!(p(-1 * 60 * 60 - 30), @"1 h, 30 s ago");
2684    }
2685
2686    #[test]
2687    fn print_duration_designator_fractional_hour() {
2688        let printer =
2689            || SpanPrinter::new().fractional(Some(FractionalUnit::Hour));
2690        let p = |secs, nanos| {
2691            printer().duration_to_string(&SignedDuration::new(secs, nanos))
2692        };
2693        let pp = |precision, secs, nanos| {
2694            printer()
2695                .precision(Some(precision))
2696                .duration_to_string(&SignedDuration::new(secs, nanos))
2697        };
2698
2699        insta::assert_snapshot!(p(1 * 60 * 60, 0), @"1h");
2700        insta::assert_snapshot!(pp(0, 1 * 60 * 60, 0), @"1h");
2701        insta::assert_snapshot!(pp(1, 1 * 60 * 60, 0), @"1.0h");
2702        insta::assert_snapshot!(pp(2, 1 * 60 * 60, 0), @"1.00h");
2703
2704        insta::assert_snapshot!(p(1 * 60 * 60 + 30 * 60, 0), @"1.5h");
2705        insta::assert_snapshot!(pp(0, 1 * 60 * 60 + 30 * 60, 0), @"1h");
2706        insta::assert_snapshot!(pp(1, 1 * 60 * 60 + 30 * 60, 0), @"1.5h");
2707        insta::assert_snapshot!(pp(2, 1 * 60 * 60 + 30 * 60, 0), @"1.50h");
2708
2709        insta::assert_snapshot!(p(1 * 60 * 60 + 3 * 60, 0), @"1.05h");
2710        insta::assert_snapshot!(p(1 * 60 * 60 + 3 * 60, 1), @"1.05h");
2711        insta::assert_snapshot!(p(1, 0), @"0.000277777h");
2712        // precision loss!
2713        insta::assert_snapshot!(p(1, 1), @"0.000277777h");
2714        insta::assert_snapshot!(p(0, 0), @"0h");
2715        // precision loss!
2716        insta::assert_snapshot!(p(0, 1), @"0h");
2717
2718        insta::assert_snapshot!(
2719            printer().duration_to_string(&SignedDuration::MIN),
2720            @"2562047788015215.502499999h ago",
2721        );
2722    }
2723
2724    #[test]
2725    fn print_duration_designator_fractional_minute() {
2726        let printer =
2727            || SpanPrinter::new().fractional(Some(FractionalUnit::Minute));
2728        let p = |secs, nanos| {
2729            printer().duration_to_string(&SignedDuration::new(secs, nanos))
2730        };
2731        let pp = |precision, secs, nanos| {
2732            printer()
2733                .precision(Some(precision))
2734                .duration_to_string(&SignedDuration::new(secs, nanos))
2735        };
2736
2737        insta::assert_snapshot!(p(1 * 60 * 60, 0), @"1h");
2738        insta::assert_snapshot!(p(1 * 60 * 60 + 30 * 60, 0), @"1h 30m");
2739
2740        insta::assert_snapshot!(p(60, 0), @"1m");
2741        insta::assert_snapshot!(pp(0, 60, 0), @"1m");
2742        insta::assert_snapshot!(pp(1, 60, 0), @"1.0m");
2743        insta::assert_snapshot!(pp(2, 60, 0), @"1.00m");
2744
2745        insta::assert_snapshot!(p(90, 0), @"1.5m");
2746        insta::assert_snapshot!(pp(0, 90, 0), @"1m");
2747        insta::assert_snapshot!(pp(1, 90, 0), @"1.5m");
2748        insta::assert_snapshot!(pp(2, 90, 0), @"1.50m");
2749
2750        insta::assert_snapshot!(p(1 * 60 * 60, 1), @"1h");
2751        insta::assert_snapshot!(p(63, 0), @"1.05m");
2752        insta::assert_snapshot!(p(63, 1), @"1.05m");
2753        insta::assert_snapshot!(p(1, 0), @"0.016666666m");
2754        // precision loss!
2755        insta::assert_snapshot!(p(1, 1), @"0.016666666m");
2756        insta::assert_snapshot!(p(0, 0), @"0m");
2757        // precision loss!
2758        insta::assert_snapshot!(p(0, 1), @"0m");
2759
2760        insta::assert_snapshot!(
2761            printer().duration_to_string(&SignedDuration::MIN),
2762            @"2562047788015215h 30.149999999m ago",
2763        );
2764    }
2765
2766    #[test]
2767    fn print_duration_designator_fractional_second() {
2768        let printer =
2769            || SpanPrinter::new().fractional(Some(FractionalUnit::Second));
2770        let p = |secs, nanos| {
2771            printer().duration_to_string(&SignedDuration::new(secs, nanos))
2772        };
2773        let pp = |precision, secs, nanos| {
2774            printer()
2775                .precision(Some(precision))
2776                .duration_to_string(&SignedDuration::new(secs, nanos))
2777        };
2778
2779        insta::assert_snapshot!(p(1 * 60 * 60, 0), @"1h");
2780        insta::assert_snapshot!(p(1 * 60 * 60 + 30 * 60, 0), @"1h 30m");
2781
2782        insta::assert_snapshot!(p(1, 0), @"1s");
2783        insta::assert_snapshot!(pp(0, 1, 0), @"1s");
2784        insta::assert_snapshot!(pp(1, 1, 0), @"1.0s");
2785        insta::assert_snapshot!(pp(2, 1, 0), @"1.00s");
2786
2787        insta::assert_snapshot!(p(1, 500_000_000), @"1.5s");
2788        insta::assert_snapshot!(pp(0, 1, 500_000_000), @"1s");
2789        insta::assert_snapshot!(pp(1, 1, 500_000_000), @"1.5s");
2790        insta::assert_snapshot!(pp(2, 1, 500_000_000), @"1.50s");
2791
2792        insta::assert_snapshot!(p(1, 1), @"1.000000001s");
2793        insta::assert_snapshot!(p(0, 1), @"0.000000001s");
2794        insta::assert_snapshot!(p(0, 0), @"0s");
2795
2796        insta::assert_snapshot!(
2797            printer().duration_to_string(&SignedDuration::MIN),
2798            @"2562047788015215h 30m 8.999999999s ago",
2799        );
2800    }
2801
2802    #[test]
2803    fn print_duration_designator_fractional_millisecond() {
2804        let printer = || {
2805            SpanPrinter::new().fractional(Some(FractionalUnit::Millisecond))
2806        };
2807        let p = |secs, nanos| {
2808            printer().duration_to_string(&SignedDuration::new(secs, nanos))
2809        };
2810        let pp = |precision, secs, nanos| {
2811            printer()
2812                .precision(Some(precision))
2813                .duration_to_string(&SignedDuration::new(secs, nanos))
2814        };
2815
2816        insta::assert_snapshot!(p(1 * 60 * 60, 0), @"1h");
2817        insta::assert_snapshot!(p(1 * 60 * 60 + 30 * 60, 0), @"1h 30m");
2818        insta::assert_snapshot!(
2819            p(1 * 60 * 60 + 30 * 60 + 10, 0),
2820            @"1h 30m 10s",
2821        );
2822
2823        insta::assert_snapshot!(p(1, 0), @"1s");
2824        insta::assert_snapshot!(pp(0, 1, 0), @"1s");
2825        insta::assert_snapshot!(pp(1, 1, 0), @"1s 0.0ms");
2826        insta::assert_snapshot!(pp(2, 1, 0), @"1s 0.00ms");
2827
2828        insta::assert_snapshot!(p(1, 500_000_000), @"1s 500ms");
2829        insta::assert_snapshot!(pp(0, 1, 1_500_000), @"1s 1ms");
2830        insta::assert_snapshot!(pp(1, 1, 1_500_000), @"1s 1.5ms");
2831        insta::assert_snapshot!(pp(2, 1, 1_500_000), @"1s 1.50ms");
2832
2833        insta::assert_snapshot!(p(0, 1_000_001), @"1.000001ms");
2834        insta::assert_snapshot!(p(0, 0_000_001), @"0.000001ms");
2835        insta::assert_snapshot!(p(0, 0), @"0ms");
2836
2837        insta::assert_snapshot!(
2838            printer().duration_to_string(&SignedDuration::MIN),
2839            @"2562047788015215h 30m 8s 999.999999ms ago",
2840        );
2841    }
2842
2843    #[test]
2844    fn print_duration_designator_fractional_microsecond() {
2845        let printer = || {
2846            SpanPrinter::new().fractional(Some(FractionalUnit::Microsecond))
2847        };
2848        let p = |secs, nanos| {
2849            printer().duration_to_string(&SignedDuration::new(secs, nanos))
2850        };
2851        let pp = |precision, secs, nanos| {
2852            printer()
2853                .precision(Some(precision))
2854                .duration_to_string(&SignedDuration::new(secs, nanos))
2855        };
2856
2857        insta::assert_snapshot!(p(1 * 60 * 60, 0), @"1h");
2858        insta::assert_snapshot!(p(1 * 60 * 60 + 30 * 60, 0), @"1h 30m");
2859        insta::assert_snapshot!(
2860            p(1 * 60 * 60 + 30 * 60 + 10, 0),
2861            @"1h 30m 10s",
2862        );
2863
2864        insta::assert_snapshot!(p(1, 0), @"1s");
2865        insta::assert_snapshot!(pp(0, 1, 0), @"1s");
2866        insta::assert_snapshot!(pp(1, 1, 0), @"1s 0.0µs");
2867        insta::assert_snapshot!(pp(2, 1, 0), @"1s 0.00µs");
2868
2869        insta::assert_snapshot!(p(1, 500_000_000), @"1s 500ms");
2870        insta::assert_snapshot!(pp(0, 1, 1_500_000), @"1s 1ms 500µs");
2871        insta::assert_snapshot!(pp(1, 1, 1_500_000), @"1s 1ms 500.0µs");
2872        insta::assert_snapshot!(pp(2, 1, 1_500_000), @"1s 1ms 500.00µs");
2873
2874        insta::assert_snapshot!(p(0, 1_000_001), @"1ms 0.001µs");
2875        insta::assert_snapshot!(p(0, 0_000_001), @"0.001µs");
2876        insta::assert_snapshot!(p(0, 0), @"0µs");
2877
2878        insta::assert_snapshot!(
2879            printer().duration_to_string(&SignedDuration::MIN),
2880            @"2562047788015215h 30m 8s 999ms 999.999µs ago",
2881        );
2882    }
2883
2884    #[test]
2885    fn print_span_hms() {
2886        let printer = || SpanPrinter::new().hours_minutes_seconds(true);
2887        let p = |span| printer().span_to_string(&span);
2888
2889        insta::assert_snapshot!(p(1.second()), @"00:00:01");
2890        insta::assert_snapshot!(p(2.seconds()), @"00:00:02");
2891        insta::assert_snapshot!(p(10.seconds()), @"00:00:10");
2892        insta::assert_snapshot!(p(100.seconds()), @"00:00:100");
2893
2894        insta::assert_snapshot!(p(1.minute()), @"00:01:00");
2895        insta::assert_snapshot!(p(2.minutes()), @"00:02:00");
2896        insta::assert_snapshot!(p(10.minutes()), @"00:10:00");
2897        insta::assert_snapshot!(p(100.minutes()), @"00:100:00");
2898
2899        insta::assert_snapshot!(p(1.hour()), @"01:00:00");
2900        insta::assert_snapshot!(p(2.hours()), @"02:00:00");
2901        insta::assert_snapshot!(p(10.hours()), @"10:00:00");
2902        insta::assert_snapshot!(p(100.hours()), @"100:00:00");
2903
2904        insta::assert_snapshot!(
2905            p(1.hour().minutes(1).seconds(1)),
2906            @"01:01:01",
2907        );
2908        insta::assert_snapshot!(
2909            p(2.hours().minutes(2).seconds(2)),
2910            @"02:02:02",
2911        );
2912        insta::assert_snapshot!(
2913            p(10.hours().minutes(10).seconds(10)),
2914            @"10:10:10",
2915        );
2916        insta::assert_snapshot!(
2917            p(100.hours().minutes(100).seconds(100)),
2918            @"100:100:100",
2919        );
2920
2921        insta::assert_snapshot!(
2922            p(1.day().hours(1).minutes(1).seconds(1)),
2923            @"1d 01:01:01",
2924        );
2925        insta::assert_snapshot!(
2926            p(1.day()),
2927            @"1d 00:00:00",
2928        );
2929        insta::assert_snapshot!(
2930            p(1.day().seconds(2)),
2931            @"1d 00:00:02",
2932        );
2933    }
2934
2935    #[test]
2936    fn print_span_hms_fmt() {
2937        let printer = || {
2938            SpanPrinter::new()
2939                .hours_minutes_seconds(true)
2940                .comma_after_designator(true)
2941                .spacing(Spacing::BetweenUnitsAndDesignators)
2942        };
2943        let p = |span| printer().span_to_string(&span);
2944
2945        insta::assert_snapshot!(
2946            p(1.day().hours(1).minutes(1).seconds(1)),
2947            @"1 d, 01:01:01",
2948        );
2949        insta::assert_snapshot!(
2950            p(1.year().months(1).weeks(1).days(1).hours(1).minutes(1).seconds(1)),
2951            @"1 y, 1 mo, 1 w, 1 d, 01:01:01",
2952        );
2953        insta::assert_snapshot!(
2954            p(1.day().hours(1).minutes(1).seconds(1).nanoseconds(1)),
2955            @"1 d, 01:01:01.000000001",
2956        );
2957    }
2958
2959    #[test]
2960    fn print_span_hms_sign() {
2961        let printer = |direction| {
2962            SpanPrinter::new().hours_minutes_seconds(true).direction(direction)
2963        };
2964        let p = |direction, span| printer(direction).span_to_string(&span);
2965
2966        insta::assert_snapshot!(
2967            p(Direction::Auto, 1.hour()),
2968            @"01:00:00",
2969        );
2970        insta::assert_snapshot!(
2971            p(Direction::Sign, 1.hour()),
2972            @"01:00:00",
2973        );
2974        insta::assert_snapshot!(
2975            p(Direction::ForceSign, 1.hour()),
2976            @"+01:00:00",
2977        );
2978        insta::assert_snapshot!(
2979            p(Direction::Suffix, 1.hour()),
2980            @"01:00:00",
2981        );
2982        insta::assert_snapshot!(
2983            p(Direction::Auto, -1.hour()),
2984            @"-01:00:00",
2985        );
2986        insta::assert_snapshot!(
2987            p(Direction::Sign, -1.hour()),
2988            @"-01:00:00",
2989        );
2990        insta::assert_snapshot!(
2991            p(Direction::ForceSign, -1.hour()),
2992            @"-01:00:00",
2993        );
2994        insta::assert_snapshot!(
2995            p(Direction::Suffix, -1.hour()),
2996            @"01:00:00 ago",
2997        );
2998
2999        insta::assert_snapshot!(
3000            p(Direction::Auto, 1.day().hours(1)),
3001            @"1d 01:00:00",
3002        );
3003        insta::assert_snapshot!(
3004            p(Direction::Sign, 1.day().hours(1)),
3005            @"1d 01:00:00",
3006        );
3007        insta::assert_snapshot!(
3008            p(Direction::ForceSign, 1.day().hours(1)),
3009            @"+1d 01:00:00",
3010        );
3011        insta::assert_snapshot!(
3012            p(Direction::Suffix, 1.day().hours(1)),
3013            @"1d 01:00:00",
3014        );
3015        // This is the main change from above. With non-zero
3016        // calendar units, the default for expressing a negative
3017        // sign switches to a suffix in the HH:MM:SS format.
3018        insta::assert_snapshot!(
3019            p(Direction::Auto, -1.day().hours(1)),
3020            @"1d 01:00:00 ago",
3021        );
3022        insta::assert_snapshot!(
3023            p(Direction::Sign, -1.day().hours(1)),
3024            @"-1d 01:00:00",
3025        );
3026        insta::assert_snapshot!(
3027            p(Direction::ForceSign, -1.day().hours(1)),
3028            @"-1d 01:00:00",
3029        );
3030        insta::assert_snapshot!(
3031            p(Direction::Suffix, -1.day().hours(1)),
3032            @"1d 01:00:00 ago",
3033        );
3034    }
3035
3036    #[test]
3037    fn print_span_hms_fraction_auto() {
3038        let printer = || SpanPrinter::new().hours_minutes_seconds(true);
3039        let p = |span| printer().span_to_string(&span);
3040
3041        insta::assert_snapshot!(p(1.nanosecond()), @"00:00:00.000000001");
3042        insta::assert_snapshot!(p(-1.nanosecond()), @"-00:00:00.000000001");
3043        insta::assert_snapshot!(
3044            printer().direction(Direction::ForceSign).span_to_string(&1.nanosecond()),
3045            @"+00:00:00.000000001",
3046        );
3047
3048        insta::assert_snapshot!(
3049            p(1.second().nanoseconds(123)),
3050            @"00:00:01.000000123",
3051        );
3052        insta::assert_snapshot!(
3053            p(1.second().milliseconds(123)),
3054            @"00:00:01.123",
3055        );
3056        insta::assert_snapshot!(
3057            p(1.second().milliseconds(1_123)),
3058            @"00:00:02.123",
3059        );
3060        insta::assert_snapshot!(
3061            p(1.second().milliseconds(61_123)),
3062            @"00:00:62.123",
3063        );
3064    }
3065
3066    #[test]
3067    fn print_span_hms_fraction_fixed_precision() {
3068        let printer = || SpanPrinter::new().hours_minutes_seconds(true);
3069        let p = |precision, span| {
3070            printer().precision(Some(precision)).span_to_string(&span)
3071        };
3072
3073        insta::assert_snapshot!(p(3, 1.second()), @"00:00:01.000");
3074        insta::assert_snapshot!(
3075            p(3, 1.second().milliseconds(1)),
3076            @"00:00:01.001",
3077        );
3078        insta::assert_snapshot!(
3079            p(3, 1.second().milliseconds(123)),
3080            @"00:00:01.123",
3081        );
3082        insta::assert_snapshot!(
3083            p(3, 1.second().milliseconds(100)),
3084            @"00:00:01.100",
3085        );
3086
3087        insta::assert_snapshot!(p(0, 1.second()), @"00:00:01");
3088        insta::assert_snapshot!(p(0, 1.second().milliseconds(1)), @"00:00:01");
3089        insta::assert_snapshot!(
3090            p(1, 1.second().milliseconds(999)),
3091            @"00:00:01.9",
3092        );
3093    }
3094
3095    #[test]
3096    fn print_duration_hms() {
3097        let printer = || SpanPrinter::new().hours_minutes_seconds(true);
3098        let p = |secs| {
3099            printer().duration_to_string(&SignedDuration::from_secs(secs))
3100        };
3101
3102        // Note the differences with `Span`, since with a `SignedDuration`,
3103        // all units are balanced.
3104
3105        insta::assert_snapshot!(p(1), @"00:00:01");
3106        insta::assert_snapshot!(p(2), @"00:00:02");
3107        insta::assert_snapshot!(p(10), @"00:00:10");
3108        insta::assert_snapshot!(p(100), @"00:01:40");
3109
3110        insta::assert_snapshot!(p(1 * 60), @"00:01:00");
3111        insta::assert_snapshot!(p(2 * 60), @"00:02:00");
3112        insta::assert_snapshot!(p(10 * 60), @"00:10:00");
3113        insta::assert_snapshot!(p(100 * 60), @"01:40:00");
3114
3115        insta::assert_snapshot!(p(1 * 60 * 60), @"01:00:00");
3116        insta::assert_snapshot!(p(2 * 60 * 60), @"02:00:00");
3117        insta::assert_snapshot!(p(10 * 60 * 60), @"10:00:00");
3118        insta::assert_snapshot!(p(100 * 60 * 60), @"100:00:00");
3119
3120        insta::assert_snapshot!(
3121            p(60 * 60 + 60 + 1),
3122            @"01:01:01",
3123        );
3124        insta::assert_snapshot!(
3125            p(2 * 60 * 60 + 2 * 60 + 2),
3126            @"02:02:02",
3127        );
3128        insta::assert_snapshot!(
3129            p(10 * 60 * 60 + 10 * 60 + 10),
3130            @"10:10:10",
3131        );
3132        insta::assert_snapshot!(
3133            p(100 * 60 * 60 + 100 * 60 + 100),
3134            @"101:41:40",
3135        );
3136    }
3137
3138    #[test]
3139    fn print_duration_hms_sign() {
3140        let printer = |direction| {
3141            SpanPrinter::new().hours_minutes_seconds(true).direction(direction)
3142        };
3143        let p = |direction, secs| {
3144            printer(direction)
3145                .duration_to_string(&SignedDuration::from_secs(secs))
3146        };
3147
3148        insta::assert_snapshot!(p(Direction::Auto, 1), @"00:00:01");
3149        insta::assert_snapshot!(p(Direction::Sign, 1), @"00:00:01");
3150        insta::assert_snapshot!(p(Direction::ForceSign, 1), @"+00:00:01");
3151        insta::assert_snapshot!(p(Direction::Suffix, 1), @"00:00:01");
3152
3153        insta::assert_snapshot!(p(Direction::Auto, -1), @"-00:00:01");
3154        insta::assert_snapshot!(p(Direction::Sign, -1), @"-00:00:01");
3155        insta::assert_snapshot!(p(Direction::ForceSign, -1), @"-00:00:01");
3156        insta::assert_snapshot!(p(Direction::Suffix, -1), @"00:00:01 ago");
3157    }
3158
3159    #[test]
3160    fn print_duration_hms_fraction_auto() {
3161        let printer = || SpanPrinter::new().hours_minutes_seconds(true);
3162        let p = |secs, nanos| {
3163            printer().duration_to_string(&SignedDuration::new(secs, nanos))
3164        };
3165
3166        insta::assert_snapshot!(p(0, 1), @"00:00:00.000000001");
3167        insta::assert_snapshot!(p(0, -1), @"-00:00:00.000000001");
3168        insta::assert_snapshot!(
3169            printer().direction(Direction::ForceSign).duration_to_string(
3170                &SignedDuration::new(0, 1),
3171            ),
3172            @"+00:00:00.000000001",
3173        );
3174
3175        insta::assert_snapshot!(
3176            p(1, 123),
3177            @"00:00:01.000000123",
3178        );
3179        insta::assert_snapshot!(
3180            p(1, 123_000_000),
3181            @"00:00:01.123",
3182        );
3183        insta::assert_snapshot!(
3184            p(1, 1_123_000_000),
3185            @"00:00:02.123",
3186        );
3187        insta::assert_snapshot!(
3188            p(61, 1_123_000_000),
3189            @"00:01:02.123",
3190        );
3191    }
3192
3193    #[test]
3194    fn print_duration_hms_fraction_fixed_precision() {
3195        let printer = || SpanPrinter::new().hours_minutes_seconds(true);
3196        let p = |precision, secs, nanos| {
3197            printer()
3198                .precision(Some(precision))
3199                .duration_to_string(&SignedDuration::new(secs, nanos))
3200        };
3201
3202        insta::assert_snapshot!(p(3, 1, 0), @"00:00:01.000");
3203        insta::assert_snapshot!(
3204            p(3, 1, 1_000_000),
3205            @"00:00:01.001",
3206        );
3207        insta::assert_snapshot!(
3208            p(3, 1, 123_000_000),
3209            @"00:00:01.123",
3210        );
3211        insta::assert_snapshot!(
3212            p(3, 1, 100_000_000),
3213            @"00:00:01.100",
3214        );
3215
3216        insta::assert_snapshot!(p(0, 1, 0), @"00:00:01");
3217        insta::assert_snapshot!(p(0, 1, 1_000_000), @"00:00:01");
3218        insta::assert_snapshot!(
3219            p(1, 1, 999_000_000),
3220            @"00:00:01.9",
3221        );
3222    }
3223}