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