1 | use crate::{ |
2 | fmt::{ |
3 | util::{DecimalFormatter, FractionalFormatter}, |
4 | Write, WriteExt, |
5 | }, |
6 | Error, SignedDuration, Span, Unit, |
7 | }; |
8 | |
9 | const SECS_PER_HOUR: i64 = MINS_PER_HOUR * SECS_PER_MIN; |
10 | const SECS_PER_MIN: i64 = 60; |
11 | const MINS_PER_HOUR: i64 = 60; |
12 | const NANOS_PER_HOUR: i128 = |
13 | (SECS_PER_MIN * MINS_PER_HOUR * NANOS_PER_SEC) as i128; |
14 | const NANOS_PER_MIN: i128 = (SECS_PER_MIN * NANOS_PER_SEC) as i128; |
15 | const NANOS_PER_SEC: i64 = 1_000_000_000; |
16 | const NANOS_PER_MILLI: i32 = 1_000_000; |
17 | const 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 ] |
46 | pub 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 ] |
131 | pub 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 | |
152 | impl 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 ] |
194 | pub 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 | |
221 | impl 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)] |
278 | enum 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 ] |
318 | pub 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 | |
337 | impl 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)] |
456 | pub 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 | |
468 | impl 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 | |
1376 | impl 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)] |
1388 | struct Designators { |
1389 | singular: &'static [&'static str], |
1390 | plural: &'static [&'static str], |
1391 | } |
1392 | |
1393 | impl 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)] |
1472 | struct 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 | |
1482 | impl<'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. |
1594 | struct FractionalPrinter { |
1595 | integer: i64, |
1596 | fraction: i64, |
1597 | fmtint: DecimalFormatter, |
1598 | fmtfraction: FractionalFormatter, |
1599 | } |
1600 | |
1601 | impl 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)] |
1734 | mod 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 | |