Skip to main content

jiff/fmt/friendly/
printer.rs

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