1use crate::{
2 civil::{Date, DateTime, Time},
3 error::{err, Error},
4 fmt::{
5 temporal::{Pieces, PiecesOffset, TimeZoneAnnotationKind},
6 util::{DecimalFormatter, FractionalFormatter},
7 Write, WriteExt,
8 },
9 span::Span,
10 tz::{Offset, TimeZone},
11 util::{rangeint::RFrom, t},
12 SignedDuration, Timestamp, Zoned,
13};
14
15#[derive(Clone, Debug)]
16pub(super) struct DateTimePrinter {
17 lowercase: bool,
18 separator: u8,
19 rfc9557: bool,
20 precision: Option<u8>,
21}
22
23impl DateTimePrinter {
24 pub(super) const fn new() -> DateTimePrinter {
25 DateTimePrinter {
26 lowercase: false,
27 separator: b'T',
28 rfc9557: true,
29 precision: None,
30 }
31 }
32
33 pub(super) const fn lowercase(self, yes: bool) -> DateTimePrinter {
34 DateTimePrinter { lowercase: yes, ..self }
35 }
36
37 pub(super) const fn separator(self, ascii_char: u8) -> DateTimePrinter {
38 assert!(ascii_char.is_ascii(), "RFC3339 separator must be ASCII");
39 DateTimePrinter { separator: ascii_char, ..self }
40 }
41
42 pub(super) const fn precision(
43 self,
44 precision: Option<u8>,
45 ) -> DateTimePrinter {
46 DateTimePrinter { precision, ..self }
47 }
48
49 pub(super) fn print_zoned<W: Write>(
50 &self,
51 zdt: &Zoned,
52 mut wtr: W,
53 ) -> Result<(), Error> {
54 let timestamp = zdt.timestamp();
55 let tz = zdt.time_zone();
56 let offset = tz.to_offset(timestamp);
57 let dt = offset.to_datetime(timestamp);
58 self.print_datetime(&dt, &mut wtr)?;
59 if tz.is_unknown() {
60 wtr.write_str("Z[Etc/Unknown]")?;
61 } else {
62 self.print_offset_rounded(&offset, &mut wtr)?;
63 self.print_time_zone_annotation(&tz, &offset, &mut wtr)?;
64 }
65 Ok(())
66 }
67
68 pub(super) fn print_timestamp<W: Write>(
69 &self,
70 timestamp: &Timestamp,
71 offset: Option<Offset>,
72 mut wtr: W,
73 ) -> Result<(), Error> {
74 let Some(offset) = offset else {
75 let dt = TimeZone::UTC.to_datetime(*timestamp);
76 self.print_datetime(&dt, &mut wtr)?;
77 self.print_zulu(&mut wtr)?;
78 return Ok(());
79 };
80 let dt = offset.to_datetime(*timestamp);
81 self.print_datetime(&dt, &mut wtr)?;
82 self.print_offset_rounded(&offset, &mut wtr)?;
83 Ok(())
84 }
85
86 pub(super) fn print_datetime<W: Write>(
88 &self,
89 dt: &DateTime,
90 mut wtr: W,
91 ) -> Result<(), Error> {
92 self.print_date(&dt.date(), &mut wtr)?;
93 wtr.write_char(char::from(if self.lowercase {
94 self.separator.to_ascii_lowercase()
95 } else {
96 self.separator
97 }))?;
98 self.print_time(&dt.time(), &mut wtr)?;
99 Ok(())
100 }
101
102 pub(super) fn print_date<W: Write>(
104 &self,
105 date: &Date,
106 mut wtr: W,
107 ) -> Result<(), Error> {
108 static FMT_YEAR_POSITIVE: DecimalFormatter =
109 DecimalFormatter::new().padding(4);
110 static FMT_YEAR_NEGATIVE: DecimalFormatter =
111 DecimalFormatter::new().padding(6);
112 static FMT_TWO: DecimalFormatter = DecimalFormatter::new().padding(2);
113
114 if date.year() >= 0 {
115 wtr.write_int(&FMT_YEAR_POSITIVE, date.year())?;
116 } else {
117 wtr.write_int(&FMT_YEAR_NEGATIVE, date.year())?;
118 }
119 wtr.write_str("-")?;
120 wtr.write_int(&FMT_TWO, date.month())?;
121 wtr.write_str("-")?;
122 wtr.write_int(&FMT_TWO, date.day())?;
123 Ok(())
124 }
125
126 pub(super) fn print_time<W: Write>(
128 &self,
129 time: &Time,
130 mut wtr: W,
131 ) -> Result<(), Error> {
132 static FMT_TWO: DecimalFormatter = DecimalFormatter::new().padding(2);
133 static FMT_FRACTION: FractionalFormatter = FractionalFormatter::new();
134
135 wtr.write_int(&FMT_TWO, time.hour())?;
136 wtr.write_str(":")?;
137 wtr.write_int(&FMT_TWO, time.minute())?;
138 wtr.write_str(":")?;
139 wtr.write_int(&FMT_TWO, time.second())?;
140 let fractional_nanosecond = time.subsec_nanosecond();
141 if self.precision.map_or(fractional_nanosecond != 0, |p| p > 0) {
142 wtr.write_str(".")?;
143 wtr.write_fraction(
144 &FMT_FRACTION.precision(self.precision),
145 fractional_nanosecond,
146 )?;
147 }
148 Ok(())
149 }
150
151 pub(super) fn print_time_zone<W: Write>(
153 &self,
154 tz: &TimeZone,
155 mut wtr: W,
156 ) -> Result<(), Error> {
157 if let Some(iana_name) = tz.iana_name() {
158 return wtr.write_str(iana_name);
159 }
160 if tz.is_unknown() {
161 return wtr.write_str("Etc/Unknown");
162 }
163 if let Ok(offset) = tz.to_fixed_offset() {
164 return self.print_offset_full_precision(&offset, wtr);
165 }
166 #[cfg(feature = "alloc")]
172 {
173 if let Some(posix_tz) = tz.posix_tz() {
174 let s = alloc::string::ToString::to_string(posix_tz);
182 return wtr.write_str(&s);
183 }
184 }
185 Err(err!(
195 "time zones without IANA identifiers that aren't either \
196 fixed offsets or a POSIX time zone can't be serialized \
197 (this typically occurs when this is a system time zone \
198 derived from `/etc/localtime` on Unix systems that \
199 isn't symlinked to an entry in `/usr/share/zoneinfo`)",
200 ))
201 }
202
203 pub(super) fn print_pieces<W: Write>(
204 &self,
205 pieces: &Pieces,
206 mut wtr: W,
207 ) -> Result<(), Error> {
208 if let Some(time) = pieces.time() {
209 let dt = DateTime::from_parts(pieces.date(), time);
210 self.print_datetime(&dt, &mut wtr)?;
211 if let Some(poffset) = pieces.offset() {
212 self.print_pieces_offset(&poffset, &mut wtr)?;
213 }
214 } else if let Some(poffset) = pieces.offset() {
215 let dt = DateTime::from_parts(pieces.date(), Time::midnight());
219 self.print_datetime(&dt, &mut wtr)?;
220 self.print_pieces_offset(&poffset, &mut wtr)?;
221 } else {
222 self.print_date(&pieces.date(), &mut wtr)?;
226 }
227 if let Some(ann) = pieces.time_zone_annotation() {
232 wtr.write_str("[")?;
236 if ann.is_critical() {
237 wtr.write_str("!")?;
238 }
239 match *ann.kind() {
240 TimeZoneAnnotationKind::Named(ref name) => {
241 wtr.write_str(name.as_str())?
242 }
243 TimeZoneAnnotationKind::Offset(offset) => {
244 self.print_offset_rounded(&offset, &mut wtr)?
245 }
246 }
247 wtr.write_str("]")?;
248 }
249 Ok(())
250 }
251
252 fn print_pieces_offset<W: Write>(
254 &self,
255 poffset: &PiecesOffset,
256 mut wtr: W,
257 ) -> Result<(), Error> {
258 match *poffset {
259 PiecesOffset::Zulu => self.print_zulu(wtr),
260 PiecesOffset::Numeric(ref noffset) => {
261 if noffset.offset().is_zero() && noffset.is_negative() {
262 wtr.write_str("-00:00")
263 } else {
264 self.print_offset_rounded(&noffset.offset(), wtr)
265 }
266 }
267 }
268 }
269
270 fn print_offset_rounded<W: Write>(
275 &self,
276 offset: &Offset,
277 mut wtr: W,
278 ) -> Result<(), Error> {
279 static FMT_TWO: DecimalFormatter = DecimalFormatter::new().padding(2);
280
281 wtr.write_str(if offset.is_negative() { "-" } else { "+" })?;
282 let mut hours = offset.part_hours_ranged().abs().get();
283 let mut minutes = offset.part_minutes_ranged().abs().get();
284 if offset.part_seconds_ranged().abs() >= 30 {
291 if minutes == 59 {
292 hours = hours.saturating_add(1);
293 minutes = 0;
294 } else {
295 minutes = minutes.saturating_add(1);
296 }
297 }
298 wtr.write_int(&FMT_TWO, hours)?;
299 wtr.write_str(":")?;
300 wtr.write_int(&FMT_TWO, minutes)?;
301 Ok(())
302 }
303
304 fn print_offset_full_precision<W: Write>(
310 &self,
311 offset: &Offset,
312 mut wtr: W,
313 ) -> Result<(), Error> {
314 static FMT_TWO: DecimalFormatter = DecimalFormatter::new().padding(2);
315
316 wtr.write_str(if offset.is_negative() { "-" } else { "+" })?;
317 let hours = offset.part_hours_ranged().abs().get();
318 let minutes = offset.part_minutes_ranged().abs().get();
319 let seconds = offset.part_seconds_ranged().abs().get();
320 wtr.write_int(&FMT_TWO, hours)?;
321 wtr.write_str(":")?;
322 wtr.write_int(&FMT_TWO, minutes)?;
323 if seconds > 0 {
324 wtr.write_str(":")?;
325 wtr.write_int(&FMT_TWO, seconds)?;
326 }
327 Ok(())
328 }
329
330 fn print_zulu<W: Write>(&self, mut wtr: W) -> Result<(), Error> {
335 wtr.write_str(if self.lowercase { "z" } else { "Z" })
336 }
337
338 fn print_time_zone_annotation<W: Write>(
347 &self,
348 time_zone: &TimeZone,
349 offset: &Offset,
350 mut wtr: W,
351 ) -> Result<(), Error> {
352 if !self.rfc9557 {
353 return Ok(());
354 }
355 wtr.write_str("[")?;
356 if let Some(iana_name) = time_zone.iana_name() {
357 wtr.write_str(iana_name)?;
358 } else {
359 self.print_offset_rounded(offset, &mut wtr)?;
360 }
361 wtr.write_str("]")?;
362 Ok(())
363 }
364}
365
366impl Default for DateTimePrinter {
367 fn default() -> DateTimePrinter {
368 DateTimePrinter::new()
369 }
370}
371
372#[derive(Debug)]
376pub(super) struct SpanPrinter {
377 lowercase: bool,
379}
380
381impl SpanPrinter {
382 pub(super) const fn new() -> SpanPrinter {
384 SpanPrinter { lowercase: false }
385 }
386
387 pub(super) const fn lowercase(self, yes: bool) -> SpanPrinter {
391 SpanPrinter { lowercase: yes }
392 }
393
394 pub(super) fn print_span<W: Write>(
398 &self,
399 span: &Span,
400 mut wtr: W,
401 ) -> Result<(), Error> {
402 static FMT_INT: DecimalFormatter = DecimalFormatter::new();
403 static FMT_FRACTION: FractionalFormatter = FractionalFormatter::new();
404
405 if span.is_negative() {
406 wtr.write_str("-")?;
407 }
408 wtr.write_str("P")?;
409
410 let mut non_zero_greater_than_second = false;
411 if span.get_years_ranged() != 0 {
412 wtr.write_int(&FMT_INT, span.get_years_ranged().get().abs())?;
413 wtr.write_char(self.label('Y'))?;
414 non_zero_greater_than_second = true;
415 }
416 if span.get_months_ranged() != 0 {
417 wtr.write_int(&FMT_INT, span.get_months_ranged().get().abs())?;
418 wtr.write_char(self.label('M'))?;
419 non_zero_greater_than_second = true;
420 }
421 if span.get_weeks_ranged() != 0 {
422 wtr.write_int(&FMT_INT, span.get_weeks_ranged().get().abs())?;
423 wtr.write_char(self.label('W'))?;
424 non_zero_greater_than_second = true;
425 }
426 if span.get_days_ranged() != 0 {
427 wtr.write_int(&FMT_INT, span.get_days_ranged().get().abs())?;
428 wtr.write_char(self.label('D'))?;
429 non_zero_greater_than_second = true;
430 }
431
432 let mut printed_time_prefix = false;
433 if span.get_hours_ranged() != 0 {
434 if !printed_time_prefix {
435 wtr.write_str("T")?;
436 printed_time_prefix = true;
437 }
438 wtr.write_int(&FMT_INT, span.get_hours_ranged().get().abs())?;
439 wtr.write_char(self.label('H'))?;
440 non_zero_greater_than_second = true;
441 }
442 if span.get_minutes_ranged() != 0 {
443 if !printed_time_prefix {
444 wtr.write_str("T")?;
445 printed_time_prefix = true;
446 }
447 wtr.write_int(&FMT_INT, span.get_minutes_ranged().get().abs())?;
448 wtr.write_char(self.label('M'))?;
449 non_zero_greater_than_second = true;
450 }
451
452 let (seconds, millis, micros, nanos) = (
457 span.get_seconds_ranged().abs(),
458 span.get_milliseconds_ranged().abs(),
459 span.get_microseconds_ranged().abs(),
460 span.get_nanoseconds_ranged().abs(),
461 );
462 if (seconds != 0 || !non_zero_greater_than_second)
463 && millis == 0
464 && micros == 0
465 && nanos == 0
466 {
467 if !printed_time_prefix {
468 wtr.write_str("T")?;
469 }
470 wtr.write_int(&FMT_INT, seconds.get())?;
471 wtr.write_char(self.label('S'))?;
472 } else if millis != 0 || micros != 0 || nanos != 0 {
473 if !printed_time_prefix {
474 wtr.write_str("T")?;
475 }
476 let combined_as_nanos =
482 t::SpanSecondsOrLowerNanoseconds::rfrom(nanos)
483 + (t::SpanSecondsOrLowerNanoseconds::rfrom(micros)
484 * t::NANOS_PER_MICRO)
485 + (t::SpanSecondsOrLowerNanoseconds::rfrom(millis)
486 * t::NANOS_PER_MILLI)
487 + (t::SpanSecondsOrLowerNanoseconds::rfrom(seconds)
488 * t::NANOS_PER_SECOND);
489 let fraction_second = t::SpanSecondsOrLower::rfrom(
490 combined_as_nanos / t::NANOS_PER_SECOND,
491 );
492 let fraction_nano = t::SubsecNanosecond::rfrom(
493 combined_as_nanos % t::NANOS_PER_SECOND,
494 );
495 wtr.write_int(&FMT_INT, fraction_second.get())?;
496 if fraction_nano != 0 {
497 wtr.write_str(".")?;
498 wtr.write_fraction(&FMT_FRACTION, fraction_nano.get())?;
499 }
500 wtr.write_char(self.label('S'))?;
501 }
502 Ok(())
503 }
504
505 pub(super) fn print_duration<W: Write>(
509 &self,
510 dur: &SignedDuration,
511 mut wtr: W,
512 ) -> Result<(), Error> {
513 static FMT_INT: DecimalFormatter = DecimalFormatter::new();
514 static FMT_FRACTION: FractionalFormatter = FractionalFormatter::new();
515
516 let mut non_zero_greater_than_second = false;
517 if dur.is_negative() {
518 wtr.write_str("-")?;
519 }
520 wtr.write_str("PT")?;
521
522 let mut secs = dur.as_secs();
523 let nanos = dur.subsec_nanos().abs();
525 let hours = (secs / (60 * 60)).abs();
527 secs %= 60 * 60;
528 let minutes = (secs / 60).abs();
530 secs = (secs % 60).abs();
532 if hours != 0 {
533 wtr.write_int(&FMT_INT, hours)?;
534 wtr.write_char(self.label('H'))?;
535 non_zero_greater_than_second = true;
536 }
537 if minutes != 0 {
538 wtr.write_int(&FMT_INT, minutes)?;
539 wtr.write_char(self.label('M'))?;
540 non_zero_greater_than_second = true;
541 }
542 if (secs != 0 || !non_zero_greater_than_second) && nanos == 0 {
543 wtr.write_int(&FMT_INT, secs)?;
544 wtr.write_char(self.label('S'))?;
545 } else if nanos != 0 {
546 wtr.write_int(&FMT_INT, secs)?;
547 wtr.write_str(".")?;
548 wtr.write_fraction(&FMT_FRACTION, nanos)?;
549 wtr.write_char(self.label('S'))?;
550 }
551 Ok(())
552 }
553
554 fn label(&self, upper: char) -> char {
558 debug_assert!(upper.is_ascii());
559 if self.lowercase {
560 upper.to_ascii_lowercase()
561 } else {
562 upper
563 }
564 }
565}
566
567#[cfg(test)]
568mod tests {
569 use alloc::string::String;
570
571 use crate::{civil::date, span::ToSpan};
572
573 use super::*;
574
575 #[test]
576 fn print_zoned() {
577 if crate::tz::db().is_definitively_empty() {
578 return;
579 }
580
581 let dt = date(2024, 3, 10).at(5, 34, 45, 0);
582 let zoned: Zoned = dt.in_tz("America/New_York").unwrap();
583 let mut buf = String::new();
584 DateTimePrinter::new().print_zoned(&zoned, &mut buf).unwrap();
585 assert_eq!(buf, "2024-03-10T05:34:45-04:00[America/New_York]");
586
587 let dt = date(2024, 3, 10).at(5, 34, 45, 0);
588 let zoned: Zoned = dt.in_tz("America/New_York").unwrap();
589 let zoned = zoned.with_time_zone(TimeZone::UTC);
590 let mut buf = String::new();
591 DateTimePrinter::new().print_zoned(&zoned, &mut buf).unwrap();
592 assert_eq!(buf, "2024-03-10T09:34:45+00:00[UTC]");
593 }
594
595 #[test]
596 fn print_timestamp() {
597 if crate::tz::db().is_definitively_empty() {
598 return;
599 }
600
601 let dt = date(2024, 3, 10).at(5, 34, 45, 0);
602 let zoned: Zoned = dt.in_tz("America/New_York").unwrap();
603 let mut buf = String::new();
604 DateTimePrinter::new()
605 .print_timestamp(&zoned.timestamp(), None, &mut buf)
606 .unwrap();
607 assert_eq!(buf, "2024-03-10T09:34:45Z");
608
609 let dt = date(-2024, 3, 10).at(5, 34, 45, 0);
610 let zoned: Zoned = dt.in_tz("America/New_York").unwrap();
611 let mut buf = String::new();
612 DateTimePrinter::new()
613 .print_timestamp(&zoned.timestamp(), None, &mut buf)
614 .unwrap();
615 assert_eq!(buf, "-002024-03-10T10:30:47Z");
616 }
617
618 #[test]
619 fn print_span_basic() {
620 let p = |span: Span| -> String {
621 let mut buf = String::new();
622 SpanPrinter::new().print_span(&span, &mut buf).unwrap();
623 buf
624 };
625
626 insta::assert_snapshot!(p(Span::new()), @"PT0S");
627 insta::assert_snapshot!(p(1.second()), @"PT1S");
628 insta::assert_snapshot!(p(-1.second()), @"-PT1S");
629 insta::assert_snapshot!(p(
630 1.second().milliseconds(1).microseconds(1).nanoseconds(1),
631 ), @"PT1.001001001S");
632 insta::assert_snapshot!(p(
633 0.second().milliseconds(999).microseconds(999).nanoseconds(999),
634 ), @"PT0.999999999S");
635 insta::assert_snapshot!(p(
636 1.year().months(1).weeks(1).days(1)
637 .hours(1).minutes(1).seconds(1)
638 .milliseconds(1).microseconds(1).nanoseconds(1),
639 ), @"P1Y1M1W1DT1H1M1.001001001S");
640 insta::assert_snapshot!(p(
641 -1.year().months(1).weeks(1).days(1)
642 .hours(1).minutes(1).seconds(1)
643 .milliseconds(1).microseconds(1).nanoseconds(1),
644 ), @"-P1Y1M1W1DT1H1M1.001001001S");
645 }
646
647 #[test]
648 fn print_span_subsecond_positive() {
649 let p = |span: Span| -> String {
650 let mut buf = String::new();
651 SpanPrinter::new().print_span(&span, &mut buf).unwrap();
652 buf
653 };
654
655 insta::assert_snapshot!(p(
657 0.second().milliseconds(1000).microseconds(1000).nanoseconds(1000),
658 ), @"PT1.001001S");
659 insta::assert_snapshot!(p(
660 1.second().milliseconds(1000).microseconds(1000).nanoseconds(1000),
661 ), @"PT2.001001S");
662 insta::assert_snapshot!(p(
663 0.second()
664 .milliseconds(t::SpanMilliseconds::MAX_REPR),
665 ), @"PT631107417600S");
666 insta::assert_snapshot!(p(
667 0.second()
668 .microseconds(t::SpanMicroseconds::MAX_REPR),
669 ), @"PT631107417600S");
670 insta::assert_snapshot!(p(
671 0.second()
672 .nanoseconds(t::SpanNanoseconds::MAX_REPR),
673 ), @"PT9223372036.854775807S");
674
675 insta::assert_snapshot!(p(
676 0.second()
677 .milliseconds(t::SpanMilliseconds::MAX_REPR)
678 .microseconds(999_999),
679 ), @"PT631107417600.999999S");
680 insta::assert_snapshot!(p(
683 0.second()
684 .milliseconds(t::SpanMilliseconds::MAX_REPR)
685 .microseconds(1_000_000),
686 ), @"PT631107417601S");
687 insta::assert_snapshot!(p(
688 0.second()
689 .milliseconds(t::SpanMilliseconds::MAX_REPR)
690 .microseconds(1_000_001),
691 ), @"PT631107417601.000001S");
692 insta::assert_snapshot!(p(
695 0.second()
696 .milliseconds(t::SpanMilliseconds::MAX_REPR)
697 .nanoseconds(1_000_000_000),
698 ), @"PT631107417601S");
699 insta::assert_snapshot!(p(
700 0.second()
701 .milliseconds(t::SpanMilliseconds::MAX_REPR)
702 .nanoseconds(1_000_000_001),
703 ), @"PT631107417601.000000001S");
704
705 insta::assert_snapshot!(p(
707 0.second()
708 .milliseconds(t::SpanMilliseconds::MAX_REPR)
709 .microseconds(t::SpanMicroseconds::MAX_REPR)
710 .nanoseconds(t::SpanNanoseconds::MAX_REPR),
711 ), @"PT1271438207236.854775807S");
712 insta::assert_snapshot!(p(
714 Span::new()
715 .seconds(t::SpanSeconds::MAX_REPR)
716 .milliseconds(t::SpanMilliseconds::MAX_REPR)
717 .microseconds(t::SpanMicroseconds::MAX_REPR)
718 .nanoseconds(t::SpanNanoseconds::MAX_REPR),
719 ), @"PT1902545624836.854775807S");
720 }
721
722 #[test]
723 fn print_span_subsecond_negative() {
724 let p = |span: Span| -> String {
725 let mut buf = String::new();
726 SpanPrinter::new().print_span(&span, &mut buf).unwrap();
727 buf
728 };
729
730 insta::assert_snapshot!(p(
732 -0.second().milliseconds(1000).microseconds(1000).nanoseconds(1000),
733 ), @"-PT1.001001S");
734 insta::assert_snapshot!(p(
735 -1.second().milliseconds(1000).microseconds(1000).nanoseconds(1000),
736 ), @"-PT2.001001S");
737 insta::assert_snapshot!(p(
738 0.second()
739 .milliseconds(t::SpanMilliseconds::MIN_REPR),
740 ), @"-PT631107417600S");
741 insta::assert_snapshot!(p(
742 0.second()
743 .microseconds(t::SpanMicroseconds::MIN_REPR),
744 ), @"-PT631107417600S");
745 insta::assert_snapshot!(p(
746 0.second()
747 .nanoseconds(t::SpanNanoseconds::MIN_REPR),
748 ), @"-PT9223372036.854775807S");
749
750 insta::assert_snapshot!(p(
751 0.second()
752 .milliseconds(t::SpanMilliseconds::MIN_REPR)
753 .microseconds(999_999),
754 ), @"-PT631107417600.999999S");
755 insta::assert_snapshot!(p(
758 0.second()
759 .milliseconds(t::SpanMilliseconds::MIN_REPR)
760 .microseconds(1_000_000),
761 ), @"-PT631107417601S");
762 insta::assert_snapshot!(p(
763 0.second()
764 .milliseconds(t::SpanMilliseconds::MIN_REPR)
765 .microseconds(1_000_001),
766 ), @"-PT631107417601.000001S");
767 insta::assert_snapshot!(p(
770 0.second()
771 .milliseconds(t::SpanMilliseconds::MIN_REPR)
772 .nanoseconds(1_000_000_000),
773 ), @"-PT631107417601S");
774 insta::assert_snapshot!(p(
775 0.second()
776 .milliseconds(t::SpanMilliseconds::MIN_REPR)
777 .nanoseconds(1_000_000_001),
778 ), @"-PT631107417601.000000001S");
779
780 insta::assert_snapshot!(p(
782 0.second()
783 .milliseconds(t::SpanMilliseconds::MIN_REPR)
784 .microseconds(t::SpanMicroseconds::MIN_REPR)
785 .nanoseconds(t::SpanNanoseconds::MIN_REPR),
786 ), @"-PT1271438207236.854775807S");
787 insta::assert_snapshot!(p(
789 Span::new()
790 .seconds(t::SpanSeconds::MIN_REPR)
791 .milliseconds(t::SpanMilliseconds::MIN_REPR)
792 .microseconds(t::SpanMicroseconds::MIN_REPR)
793 .nanoseconds(t::SpanNanoseconds::MIN_REPR),
794 ), @"-PT1902545624836.854775807S");
795 }
796
797 #[test]
798 fn print_duration() {
799 let p = |secs, nanos| -> String {
800 let dur = SignedDuration::new(secs, nanos);
801 let mut buf = String::new();
802 SpanPrinter::new().print_duration(&dur, &mut buf).unwrap();
803 buf
804 };
805
806 insta::assert_snapshot!(p(0, 0), @"PT0S");
807 insta::assert_snapshot!(p(0, 1), @"PT0.000000001S");
808 insta::assert_snapshot!(p(1, 0), @"PT1S");
809 insta::assert_snapshot!(p(59, 0), @"PT59S");
810 insta::assert_snapshot!(p(60, 0), @"PT1M");
811 insta::assert_snapshot!(p(60, 1), @"PT1M0.000000001S");
812 insta::assert_snapshot!(p(61, 1), @"PT1M1.000000001S");
813 insta::assert_snapshot!(p(3_600, 0), @"PT1H");
814 insta::assert_snapshot!(p(3_600, 1), @"PT1H0.000000001S");
815 insta::assert_snapshot!(p(3_660, 0), @"PT1H1M");
816 insta::assert_snapshot!(p(3_660, 1), @"PT1H1M0.000000001S");
817 insta::assert_snapshot!(p(3_661, 0), @"PT1H1M1S");
818 insta::assert_snapshot!(p(3_661, 1), @"PT1H1M1.000000001S");
819
820 insta::assert_snapshot!(p(0, -1), @"-PT0.000000001S");
821 insta::assert_snapshot!(p(-1, 0), @"-PT1S");
822 insta::assert_snapshot!(p(-59, 0), @"-PT59S");
823 insta::assert_snapshot!(p(-60, 0), @"-PT1M");
824 insta::assert_snapshot!(p(-60, -1), @"-PT1M0.000000001S");
825 insta::assert_snapshot!(p(-61, -1), @"-PT1M1.000000001S");
826 insta::assert_snapshot!(p(-3_600, 0), @"-PT1H");
827 insta::assert_snapshot!(p(-3_600, -1), @"-PT1H0.000000001S");
828 insta::assert_snapshot!(p(-3_660, 0), @"-PT1H1M");
829 insta::assert_snapshot!(p(-3_660, -1), @"-PT1H1M0.000000001S");
830 insta::assert_snapshot!(p(-3_661, 0), @"-PT1H1M1S");
831 insta::assert_snapshot!(p(-3_661, -1), @"-PT1H1M1.000000001S");
832
833 insta::assert_snapshot!(
834 p(i64::MIN, -999_999_999),
835 @"-PT2562047788015215H30M8.999999999S",
836 );
837 insta::assert_snapshot!(
838 p(i64::MAX, 999_999_999),
839 @"PT2562047788015215H30M7.999999999S",
840 );
841 }
842}