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