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.unsigned_abs(),
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(
505 &FMT_FRACTION,
506 i32::from(fraction_nano).unsigned_abs(),
507 )?;
508 }
509 wtr.write_char(self.label('S'))?;
510 }
511 Ok(())
512 }
513
514 pub(super) fn print_signed_duration<W: Write>(
518 &self,
519 dur: &SignedDuration,
520 mut wtr: W,
521 ) -> Result<(), Error> {
522 static FMT_INT: DecimalFormatter = DecimalFormatter::new();
523 static FMT_FRACTION: FractionalFormatter = FractionalFormatter::new();
524
525 let mut non_zero_greater_than_second = false;
526 if dur.is_negative() {
527 wtr.write_str("-")?;
528 }
529 wtr.write_str("PT")?;
530
531 let mut secs = dur.as_secs();
532 let nanos = dur.subsec_nanos().abs();
534 let hours = (secs / (60 * 60)).abs();
536 secs %= 60 * 60;
537 let minutes = (secs / 60).abs();
539 secs = (secs % 60).abs();
541 if hours != 0 {
542 wtr.write_int(&FMT_INT, hours)?;
543 wtr.write_char(self.label('H'))?;
544 non_zero_greater_than_second = true;
545 }
546 if minutes != 0 {
547 wtr.write_int(&FMT_INT, minutes)?;
548 wtr.write_char(self.label('M'))?;
549 non_zero_greater_than_second = true;
550 }
551 if (secs != 0 || !non_zero_greater_than_second) && nanos == 0 {
552 wtr.write_int(&FMT_INT, secs)?;
553 wtr.write_char(self.label('S'))?;
554 } else if nanos != 0 {
555 wtr.write_int(&FMT_INT, secs)?;
556 wtr.write_str(".")?;
557 wtr.write_fraction(&FMT_FRACTION, nanos.unsigned_abs())?;
558 wtr.write_char(self.label('S'))?;
559 }
560 Ok(())
561 }
562
563 pub(super) fn print_unsigned_duration<W: Write>(
567 &self,
568 dur: &core::time::Duration,
569 mut wtr: W,
570 ) -> Result<(), Error> {
571 static FMT_INT: DecimalFormatter = DecimalFormatter::new();
572 static FMT_FRACTION: FractionalFormatter = FractionalFormatter::new();
573
574 let mut non_zero_greater_than_second = false;
575 wtr.write_str("PT")?;
576
577 let mut secs = dur.as_secs();
578 let nanos = dur.subsec_nanos();
579 let hours = secs / (60 * 60);
580 secs %= 60 * 60;
581 let minutes = secs / 60;
582 secs = secs % 60;
583 if hours != 0 {
584 wtr.write_uint(&FMT_INT, hours)?;
585 wtr.write_char(self.label('H'))?;
586 non_zero_greater_than_second = true;
587 }
588 if minutes != 0 {
589 wtr.write_uint(&FMT_INT, minutes)?;
590 wtr.write_char(self.label('M'))?;
591 non_zero_greater_than_second = true;
592 }
593 if (secs != 0 || !non_zero_greater_than_second) && nanos == 0 {
594 wtr.write_uint(&FMT_INT, secs)?;
595 wtr.write_char(self.label('S'))?;
596 } else if nanos != 0 {
597 wtr.write_uint(&FMT_INT, secs)?;
598 wtr.write_str(".")?;
599 wtr.write_fraction(&FMT_FRACTION, nanos)?;
600 wtr.write_char(self.label('S'))?;
601 }
602 Ok(())
603 }
604
605 fn label(&self, upper: char) -> char {
609 debug_assert!(upper.is_ascii());
610 if self.lowercase {
611 upper.to_ascii_lowercase()
612 } else {
613 upper
614 }
615 }
616}
617
618#[cfg(feature = "alloc")]
619#[cfg(test)]
620mod tests {
621 use alloc::string::String;
622
623 use crate::{civil::date, span::ToSpan};
624
625 use super::*;
626
627 #[test]
628 fn print_zoned() {
629 if crate::tz::db().is_definitively_empty() {
630 return;
631 }
632
633 let dt = date(2024, 3, 10).at(5, 34, 45, 0);
634 let zoned: Zoned = dt.in_tz("America/New_York").unwrap();
635 let mut buf = String::new();
636 DateTimePrinter::new().print_zoned(&zoned, &mut buf).unwrap();
637 assert_eq!(buf, "2024-03-10T05:34:45-04:00[America/New_York]");
638
639 let dt = date(2024, 3, 10).at(5, 34, 45, 0);
640 let zoned: Zoned = dt.in_tz("America/New_York").unwrap();
641 let zoned = zoned.with_time_zone(TimeZone::UTC);
642 let mut buf = String::new();
643 DateTimePrinter::new().print_zoned(&zoned, &mut buf).unwrap();
644 assert_eq!(buf, "2024-03-10T09:34:45+00:00[UTC]");
645 }
646
647 #[test]
648 fn print_timestamp() {
649 if crate::tz::db().is_definitively_empty() {
650 return;
651 }
652
653 let dt = date(2024, 3, 10).at(5, 34, 45, 0);
654 let zoned: Zoned = dt.in_tz("America/New_York").unwrap();
655 let mut buf = String::new();
656 DateTimePrinter::new()
657 .print_timestamp(&zoned.timestamp(), None, &mut buf)
658 .unwrap();
659 assert_eq!(buf, "2024-03-10T09:34:45Z");
660
661 let dt = date(-2024, 3, 10).at(5, 34, 45, 0);
662 let zoned: Zoned = dt.in_tz("America/New_York").unwrap();
663 let mut buf = String::new();
664 DateTimePrinter::new()
665 .print_timestamp(&zoned.timestamp(), None, &mut buf)
666 .unwrap();
667 assert_eq!(buf, "-002024-03-10T10:30:47Z");
668 }
669
670 #[test]
671 fn print_span_basic() {
672 let p = |span: Span| -> String {
673 let mut buf = String::new();
674 SpanPrinter::new().print_span(&span, &mut buf).unwrap();
675 buf
676 };
677
678 insta::assert_snapshot!(p(Span::new()), @"PT0S");
679 insta::assert_snapshot!(p(1.second()), @"PT1S");
680 insta::assert_snapshot!(p(-1.second()), @"-PT1S");
681 insta::assert_snapshot!(p(
682 1.second().milliseconds(1).microseconds(1).nanoseconds(1),
683 ), @"PT1.001001001S");
684 insta::assert_snapshot!(p(
685 0.second().milliseconds(999).microseconds(999).nanoseconds(999),
686 ), @"PT0.999999999S");
687 insta::assert_snapshot!(p(
688 1.year().months(1).weeks(1).days(1)
689 .hours(1).minutes(1).seconds(1)
690 .milliseconds(1).microseconds(1).nanoseconds(1),
691 ), @"P1Y1M1W1DT1H1M1.001001001S");
692 insta::assert_snapshot!(p(
693 -1.year().months(1).weeks(1).days(1)
694 .hours(1).minutes(1).seconds(1)
695 .milliseconds(1).microseconds(1).nanoseconds(1),
696 ), @"-P1Y1M1W1DT1H1M1.001001001S");
697 }
698
699 #[test]
700 fn print_span_subsecond_positive() {
701 let p = |span: Span| -> String {
702 let mut buf = String::new();
703 SpanPrinter::new().print_span(&span, &mut buf).unwrap();
704 buf
705 };
706
707 insta::assert_snapshot!(p(
709 0.second().milliseconds(1000).microseconds(1000).nanoseconds(1000),
710 ), @"PT1.001001S");
711 insta::assert_snapshot!(p(
712 1.second().milliseconds(1000).microseconds(1000).nanoseconds(1000),
713 ), @"PT2.001001S");
714 insta::assert_snapshot!(p(
715 0.second()
716 .milliseconds(t::SpanMilliseconds::MAX_REPR),
717 ), @"PT631107417600S");
718 insta::assert_snapshot!(p(
719 0.second()
720 .microseconds(t::SpanMicroseconds::MAX_REPR),
721 ), @"PT631107417600S");
722 insta::assert_snapshot!(p(
723 0.second()
724 .nanoseconds(t::SpanNanoseconds::MAX_REPR),
725 ), @"PT9223372036.854775807S");
726
727 insta::assert_snapshot!(p(
728 0.second()
729 .milliseconds(t::SpanMilliseconds::MAX_REPR)
730 .microseconds(999_999),
731 ), @"PT631107417600.999999S");
732 insta::assert_snapshot!(p(
735 0.second()
736 .milliseconds(t::SpanMilliseconds::MAX_REPR)
737 .microseconds(1_000_000),
738 ), @"PT631107417601S");
739 insta::assert_snapshot!(p(
740 0.second()
741 .milliseconds(t::SpanMilliseconds::MAX_REPR)
742 .microseconds(1_000_001),
743 ), @"PT631107417601.000001S");
744 insta::assert_snapshot!(p(
747 0.second()
748 .milliseconds(t::SpanMilliseconds::MAX_REPR)
749 .nanoseconds(1_000_000_000),
750 ), @"PT631107417601S");
751 insta::assert_snapshot!(p(
752 0.second()
753 .milliseconds(t::SpanMilliseconds::MAX_REPR)
754 .nanoseconds(1_000_000_001),
755 ), @"PT631107417601.000000001S");
756
757 insta::assert_snapshot!(p(
759 0.second()
760 .milliseconds(t::SpanMilliseconds::MAX_REPR)
761 .microseconds(t::SpanMicroseconds::MAX_REPR)
762 .nanoseconds(t::SpanNanoseconds::MAX_REPR),
763 ), @"PT1271438207236.854775807S");
764 insta::assert_snapshot!(p(
766 Span::new()
767 .seconds(t::SpanSeconds::MAX_REPR)
768 .milliseconds(t::SpanMilliseconds::MAX_REPR)
769 .microseconds(t::SpanMicroseconds::MAX_REPR)
770 .nanoseconds(t::SpanNanoseconds::MAX_REPR),
771 ), @"PT1902545624836.854775807S");
772 }
773
774 #[test]
775 fn print_span_subsecond_negative() {
776 let p = |span: Span| -> String {
777 let mut buf = String::new();
778 SpanPrinter::new().print_span(&span, &mut buf).unwrap();
779 buf
780 };
781
782 insta::assert_snapshot!(p(
784 -0.second().milliseconds(1000).microseconds(1000).nanoseconds(1000),
785 ), @"-PT1.001001S");
786 insta::assert_snapshot!(p(
787 -1.second().milliseconds(1000).microseconds(1000).nanoseconds(1000),
788 ), @"-PT2.001001S");
789 insta::assert_snapshot!(p(
790 0.second()
791 .milliseconds(t::SpanMilliseconds::MIN_REPR),
792 ), @"-PT631107417600S");
793 insta::assert_snapshot!(p(
794 0.second()
795 .microseconds(t::SpanMicroseconds::MIN_REPR),
796 ), @"-PT631107417600S");
797 insta::assert_snapshot!(p(
798 0.second()
799 .nanoseconds(t::SpanNanoseconds::MIN_REPR),
800 ), @"-PT9223372036.854775807S");
801
802 insta::assert_snapshot!(p(
803 0.second()
804 .milliseconds(t::SpanMilliseconds::MIN_REPR)
805 .microseconds(999_999),
806 ), @"-PT631107417600.999999S");
807 insta::assert_snapshot!(p(
810 0.second()
811 .milliseconds(t::SpanMilliseconds::MIN_REPR)
812 .microseconds(1_000_000),
813 ), @"-PT631107417601S");
814 insta::assert_snapshot!(p(
815 0.second()
816 .milliseconds(t::SpanMilliseconds::MIN_REPR)
817 .microseconds(1_000_001),
818 ), @"-PT631107417601.000001S");
819 insta::assert_snapshot!(p(
822 0.second()
823 .milliseconds(t::SpanMilliseconds::MIN_REPR)
824 .nanoseconds(1_000_000_000),
825 ), @"-PT631107417601S");
826 insta::assert_snapshot!(p(
827 0.second()
828 .milliseconds(t::SpanMilliseconds::MIN_REPR)
829 .nanoseconds(1_000_000_001),
830 ), @"-PT631107417601.000000001S");
831
832 insta::assert_snapshot!(p(
834 0.second()
835 .milliseconds(t::SpanMilliseconds::MIN_REPR)
836 .microseconds(t::SpanMicroseconds::MIN_REPR)
837 .nanoseconds(t::SpanNanoseconds::MIN_REPR),
838 ), @"-PT1271438207236.854775807S");
839 insta::assert_snapshot!(p(
841 Span::new()
842 .seconds(t::SpanSeconds::MIN_REPR)
843 .milliseconds(t::SpanMilliseconds::MIN_REPR)
844 .microseconds(t::SpanMicroseconds::MIN_REPR)
845 .nanoseconds(t::SpanNanoseconds::MIN_REPR),
846 ), @"-PT1902545624836.854775807S");
847 }
848
849 #[test]
850 fn print_signed_duration() {
851 let p = |secs, nanos| -> String {
852 let dur = SignedDuration::new(secs, nanos);
853 let mut buf = String::new();
854 SpanPrinter::new().print_signed_duration(&dur, &mut buf).unwrap();
855 buf
856 };
857
858 insta::assert_snapshot!(p(0, 0), @"PT0S");
859 insta::assert_snapshot!(p(0, 1), @"PT0.000000001S");
860 insta::assert_snapshot!(p(1, 0), @"PT1S");
861 insta::assert_snapshot!(p(59, 0), @"PT59S");
862 insta::assert_snapshot!(p(60, 0), @"PT1M");
863 insta::assert_snapshot!(p(60, 1), @"PT1M0.000000001S");
864 insta::assert_snapshot!(p(61, 1), @"PT1M1.000000001S");
865 insta::assert_snapshot!(p(3_600, 0), @"PT1H");
866 insta::assert_snapshot!(p(3_600, 1), @"PT1H0.000000001S");
867 insta::assert_snapshot!(p(3_660, 0), @"PT1H1M");
868 insta::assert_snapshot!(p(3_660, 1), @"PT1H1M0.000000001S");
869 insta::assert_snapshot!(p(3_661, 0), @"PT1H1M1S");
870 insta::assert_snapshot!(p(3_661, 1), @"PT1H1M1.000000001S");
871
872 insta::assert_snapshot!(p(0, -1), @"-PT0.000000001S");
873 insta::assert_snapshot!(p(-1, 0), @"-PT1S");
874 insta::assert_snapshot!(p(-59, 0), @"-PT59S");
875 insta::assert_snapshot!(p(-60, 0), @"-PT1M");
876 insta::assert_snapshot!(p(-60, -1), @"-PT1M0.000000001S");
877 insta::assert_snapshot!(p(-61, -1), @"-PT1M1.000000001S");
878 insta::assert_snapshot!(p(-3_600, 0), @"-PT1H");
879 insta::assert_snapshot!(p(-3_600, -1), @"-PT1H0.000000001S");
880 insta::assert_snapshot!(p(-3_660, 0), @"-PT1H1M");
881 insta::assert_snapshot!(p(-3_660, -1), @"-PT1H1M0.000000001S");
882 insta::assert_snapshot!(p(-3_661, 0), @"-PT1H1M1S");
883 insta::assert_snapshot!(p(-3_661, -1), @"-PT1H1M1.000000001S");
884
885 insta::assert_snapshot!(
886 p(i64::MIN, -999_999_999),
887 @"-PT2562047788015215H30M8.999999999S",
888 );
889 insta::assert_snapshot!(
890 p(i64::MAX, 999_999_999),
891 @"PT2562047788015215H30M7.999999999S",
892 );
893 }
894
895 #[test]
896 fn print_unsigned_duration() {
897 let p = |secs, nanos| -> String {
898 let dur = core::time::Duration::new(secs, nanos);
899 let mut buf = String::new();
900 SpanPrinter::new()
901 .print_unsigned_duration(&dur, &mut buf)
902 .unwrap();
903 buf
904 };
905
906 insta::assert_snapshot!(p(0, 0), @"PT0S");
907 insta::assert_snapshot!(p(0, 1), @"PT0.000000001S");
908 insta::assert_snapshot!(p(1, 0), @"PT1S");
909 insta::assert_snapshot!(p(59, 0), @"PT59S");
910 insta::assert_snapshot!(p(60, 0), @"PT1M");
911 insta::assert_snapshot!(p(60, 1), @"PT1M0.000000001S");
912 insta::assert_snapshot!(p(61, 1), @"PT1M1.000000001S");
913 insta::assert_snapshot!(p(3_600, 0), @"PT1H");
914 insta::assert_snapshot!(p(3_600, 1), @"PT1H0.000000001S");
915 insta::assert_snapshot!(p(3_660, 0), @"PT1H1M");
916 insta::assert_snapshot!(p(3_660, 1), @"PT1H1M0.000000001S");
917 insta::assert_snapshot!(p(3_661, 0), @"PT1H1M1S");
918 insta::assert_snapshot!(p(3_661, 1), @"PT1H1M1.000000001S");
919
920 insta::assert_snapshot!(
921 p(u64::MAX, 999_999_999),
922 @"PT5124095576030431H15.999999999S",
923 );
924 }
925}