1use alloc::string::String;
4use alloc::vec::Vec;
5use core::ops::Deref;
6use std::io;
7
8use deranged::{ru8, ru16};
9use num_conv::prelude::*;
10
11use crate::format_description::format_description_v3::FormatDescriptionV3Inner;
12use crate::format_description::modifier::Padding;
13use crate::format_description::well_known::iso8601::EncodedConfig;
14use crate::format_description::well_known::{Iso8601, Rfc2822, Rfc3339};
15use crate::format_description::{BorrowedFormatItem, FormatDescriptionV3, OwnedFormatItem};
16use crate::formatting::{
17 ComponentProvider, MONTH_NAMES, WEEKDAY_NAMES, format_four_digits_pad_zero, format_two_digits,
18 iso8601, write, write_bytes, write_if_else,
19};
20use crate::internal_macros::try_likely_ok;
21use crate::{error, num_fmt};
22
23#[cfg_attr(docsrs, doc(notable_trait))]
29pub trait Formattable: sealed::Sealed {}
30impl Formattable for FormatDescriptionV3<'_> {}
31impl Formattable for BorrowedFormatItem<'_> {}
32impl Formattable for [BorrowedFormatItem<'_>] {}
33impl Formattable for OwnedFormatItem {}
34impl Formattable for [OwnedFormatItem] {}
35impl Formattable for Rfc3339 {}
36impl Formattable for Rfc2822 {}
37impl<const CONFIG: EncodedConfig> Formattable for Iso8601<CONFIG> {}
38impl<T> Formattable for T where T: Deref<Target: Formattable> {}
39
40mod sealed {
42 use super::*;
43 use crate::formatting::ComponentProvider;
44 use crate::formatting::metadata::ComputeMetadata;
45
46 #[expect(
48 private_bounds,
49 private_interfaces,
50 reason = "irrelevant due to being a sealed trait"
51 )]
52 pub trait Sealed: ComputeMetadata {
53 fn format_into<V>(
55 &self,
56 output: &mut (impl io::Write + ?Sized),
57 value: &V,
58 state: &mut V::State,
59 ) -> Result<usize, error::Format>
60 where
61 V: ComponentProvider;
62
63 #[inline]
65 fn format<V>(&self, value: &V, state: &mut V::State) -> Result<String, error::Format>
66 where
67 V: ComponentProvider,
68 {
69 let crate::formatting::metadata::Metadata {
70 max_bytes_needed,
71 guaranteed_utf8,
72 } = self.compute_metadata();
73
74 let mut buf = Vec::with_capacity(max_bytes_needed);
75 try_likely_ok!(self.format_into(&mut buf, value, state));
76 Ok(if guaranteed_utf8 {
77 unsafe { String::from_utf8_unchecked(buf) }
79 } else {
80 String::from_utf8_lossy(&buf).into_owned()
81 })
82 }
83 }
84}
85
86impl sealed::Sealed for FormatDescriptionV3<'_> {
87 #[expect(
88 private_bounds,
89 private_interfaces,
90 reason = "irrelevant due to being a sealed trait"
91 )]
92 #[inline]
93 fn format_into<V>(
94 &self,
95 output: &mut (impl io::Write + ?Sized),
96 value: &V,
97 state: &mut V::State,
98 ) -> Result<usize, error::Format>
99 where
100 V: ComponentProvider,
101 {
102 self.inner.format_into(output, value, state)
103 }
104}
105
106impl sealed::Sealed for FormatDescriptionV3Inner<'_> {
107 #[expect(
108 private_bounds,
109 private_interfaces,
110 reason = "irrelevant due to being a sealed trait"
111 )]
112 #[inline]
113 fn format_into<V>(
114 &self,
115 output: &mut (impl io::Write + ?Sized),
116 value: &V,
117 state: &mut V::State,
118 ) -> Result<usize, error::Format>
119 where
120 V: ComponentProvider,
121 {
122 use FormatDescriptionV3Inner::*;
123
124 use crate::formatting::*;
125
126 match &self {
127 Day(modifier) if V::SUPPLIES_DATE => {
128 fmt_day(output, value.day(state), *modifier).map_err(Into::into)
129 }
130 MonthShort(modifier) if V::SUPPLIES_DATE => {
131 fmt_month_short(output, value.month(state), *modifier).map_err(Into::into)
132 }
133 MonthLong(modifier) if V::SUPPLIES_DATE => {
134 fmt_month_long(output, value.month(state), *modifier).map_err(Into::into)
135 }
136 MonthNumerical(modifier) if V::SUPPLIES_DATE => {
137 fmt_month_numerical(output, value.month(state), *modifier).map_err(Into::into)
138 }
139 Ordinal(modifier) if V::SUPPLIES_DATE => {
140 fmt_ordinal(output, value.ordinal(state), *modifier).map_err(Into::into)
141 }
142 WeekdayShort(modifier) if V::SUPPLIES_DATE => {
143 fmt_weekday_short(output, value.weekday(state), *modifier).map_err(Into::into)
144 }
145 WeekdayLong(modifier) if V::SUPPLIES_DATE => {
146 fmt_weekday_long(output, value.weekday(state), *modifier).map_err(Into::into)
147 }
148 WeekdaySunday(modifier) if V::SUPPLIES_DATE => {
149 fmt_weekday_sunday(output, value.weekday(state), *modifier).map_err(Into::into)
150 }
151 WeekdayMonday(modifier) if V::SUPPLIES_DATE => {
152 fmt_weekday_monday(output, value.weekday(state), *modifier).map_err(Into::into)
153 }
154 WeekNumberIso(modifier) if V::SUPPLIES_DATE => {
155 fmt_week_number_iso(output, value.iso_week_number(state), *modifier)
156 .map_err(Into::into)
157 }
158 WeekNumberSunday(modifier) if V::SUPPLIES_DATE => {
159 fmt_week_number_sunday(output, value.sunday_based_week(state), *modifier)
160 .map_err(Into::into)
161 }
162 WeekNumberMonday(modifier) if V::SUPPLIES_DATE => {
163 fmt_week_number_monday(output, value.monday_based_week(state), *modifier)
164 .map_err(Into::into)
165 }
166 CalendarYearFullExtendedRange(modifier) if V::SUPPLIES_DATE => {
167 fmt_calendar_year_full_extended_range(output, value.calendar_year(state), *modifier)
168 .map_err(Into::into)
169 }
170 CalendarYearFullStandardRange(modifier) if V::SUPPLIES_DATE => {
171 fmt_calendar_year_full_standard_range(
172 output,
173 try_likely_ok!(
174 value
175 .calendar_year(state)
176 .narrow::<-9_999, 9_999>()
177 .ok_or_else(|| error::ComponentRange::conditional("year"))
178 )
179 .into(),
180 *modifier,
181 )
182 .map_err(Into::into)
183 }
184 IsoYearFullExtendedRange(modifier) if V::SUPPLIES_DATE => {
185 fmt_iso_year_full_extended_range(output, value.iso_year(state), *modifier)
186 .map_err(Into::into)
187 }
188 IsoYearFullStandardRange(modifier) if V::SUPPLIES_DATE => {
189 fmt_iso_year_full_standard_range(
190 output,
191 try_likely_ok!(
192 value
193 .iso_year(state)
194 .narrow::<-9_999, 9_999>()
195 .ok_or_else(|| error::ComponentRange::conditional("year"))
196 )
197 .into(),
198 *modifier,
199 )
200 .map_err(Into::into)
201 }
202 CalendarYearCenturyExtendedRange(modifier) if V::SUPPLIES_DATE => {
203 let year = value.calendar_year(state);
204 let century = unsafe { ri16::new_unchecked((year.get() / 100).truncate()) };
207 fmt_calendar_year_century_extended_range(
208 output,
209 century,
210 year.is_negative(),
211 *modifier,
212 )
213 .map_err(Into::into)
214 }
215 CalendarYearCenturyStandardRange(modifier) if V::SUPPLIES_DATE => {
216 let year = value.calendar_year(state);
217 let is_negative = year.is_negative();
218 let year =
221 unsafe { ri16::<-9_999, 9_999>::new_unchecked((year.get() / 100).truncate()) };
222 fmt_calendar_year_century_standard_range(
223 output,
224 year.narrow::<-99, 99>()
225 .ok_or_else(|| error::ComponentRange::conditional("year"))?
226 .into(),
227 is_negative,
228 *modifier,
229 )
230 .map_err(Into::into)
231 }
232 IsoYearCenturyExtendedRange(modifier) if V::SUPPLIES_DATE => {
233 let year = value.iso_year(state);
234 let is_negative = year.is_negative();
235 let century = unsafe { ri16::new_unchecked((year.get() / 100).truncate()) };
238 fmt_iso_year_century_extended_range(output, century, is_negative, *modifier)
239 .map_err(Into::into)
240 }
241 IsoYearCenturyStandardRange(modifier) if V::SUPPLIES_DATE => {
242 let year = value.iso_year(state);
243 let is_negative = year.is_negative();
244 let year =
247 unsafe { ri16::<-9_999, 9_999>::new_unchecked((year.get() / 100).truncate()) };
248 fmt_iso_year_century_standard_range(
249 output,
250 year.narrow::<-99, 99>()
251 .ok_or_else(|| error::ComponentRange::conditional("year"))?
252 .into(),
253 is_negative,
254 *modifier,
255 )
256 .map_err(Into::into)
257 }
258 CalendarYearLastTwo(modifier) if V::SUPPLIES_DATE => {
259 let last_two = unsafe {
262 ru8::new_unchecked(
263 (value.calendar_year(state).get().unsigned_abs() % 100).truncate(),
264 )
265 };
266 fmt_calendar_year_last_two(output, last_two, *modifier).map_err(Into::into)
267 }
268 IsoYearLastTwo(modifier) if V::SUPPLIES_DATE => {
269 let last_two = unsafe {
272 ru8::new_unchecked(
273 (value.iso_year(state).get().unsigned_abs() % 100).truncate(),
274 )
275 };
276 fmt_iso_year_last_two(output, last_two, *modifier).map_err(Into::into)
277 }
278 Hour12(modifier) if V::SUPPLIES_TIME => {
279 fmt_hour_12(output, value.hour(state), *modifier).map_err(Into::into)
280 }
281 Hour24(modifier) if V::SUPPLIES_TIME => {
282 fmt_hour_24(output, value.hour(state), *modifier).map_err(Into::into)
283 }
284 Minute(modifier) if V::SUPPLIES_TIME => {
285 fmt_minute(output, value.minute(state), *modifier).map_err(Into::into)
286 }
287 Period(modifier) if V::SUPPLIES_TIME => {
288 fmt_period(output, value.period(state), *modifier).map_err(Into::into)
289 }
290 Second(modifier) if V::SUPPLIES_TIME => {
291 fmt_second(output, value.second(state), *modifier).map_err(Into::into)
292 }
293 Subsecond(modifier) if V::SUPPLIES_TIME => {
294 fmt_subsecond(output, value.nanosecond(state), *modifier).map_err(Into::into)
295 }
296 OffsetHour(modifier) if V::SUPPLIES_OFFSET => fmt_offset_hour(
297 output,
298 value.offset_is_negative(state),
299 value.offset_hour(state),
300 *modifier,
301 )
302 .map_err(Into::into),
303 OffsetMinute(modifier) if V::SUPPLIES_OFFSET => {
304 fmt_offset_minute(output, value.offset_minute(state), *modifier).map_err(Into::into)
305 }
306 OffsetSecond(modifier) if V::SUPPLIES_OFFSET => {
307 fmt_offset_second(output, value.offset_second(state), *modifier).map_err(Into::into)
308 }
309 Ignore(_) => Ok(0),
310 UnixTimestampSecond(modifier) if V::SUPPLIES_TIMESTAMP => {
311 fmt_unix_timestamp_second(output, value.unix_timestamp_seconds(state), *modifier)
312 .map_err(Into::into)
313 }
314 UnixTimestampMillisecond(modifier) if V::SUPPLIES_TIMESTAMP => {
315 fmt_unix_timestamp_millisecond(
316 output,
317 value.unix_timestamp_milliseconds(state),
318 *modifier,
319 )
320 .map_err(Into::into)
321 }
322 UnixTimestampMicrosecond(modifier) if V::SUPPLIES_TIMESTAMP => {
323 fmt_unix_timestamp_microsecond(
324 output,
325 value.unix_timestamp_microseconds(state),
326 *modifier,
327 )
328 .map_err(Into::into)
329 }
330 UnixTimestampNanosecond(modifier) if V::SUPPLIES_TIMESTAMP => {
331 fmt_unix_timestamp_nanosecond(
332 output,
333 value.unix_timestamp_nanoseconds(state),
334 *modifier,
335 )
336 .map_err(Into::into)
337 }
338 End(modifier::End { trailing_input: _ }) => Ok(0),
339 Self::BorrowedLiteral(literal) => {
340 write_bytes(output, literal.as_bytes()).map_err(Into::into)
341 }
342 Self::BorrowedCompound(items) => {
343 let mut bytes = 0;
344 for item in *items {
345 bytes += try_likely_ok!(item.format_into(output, value, state));
346 }
347 Ok(bytes)
348 }
349 Self::BorrowedOptional {
350 format: should_format,
351 item,
352 } => {
353 if *should_format {
354 item.format_into(output, value, state)
355 } else {
356 Ok(0)
357 }
358 }
359 Self::BorrowedFirst(items) => match items {
360 [] => Ok(0),
361 [item, ..] => item.format_into(output, value, state),
362 },
363 Self::OwnedLiteral(literal) => {
364 write_bytes(output, literal.as_bytes()).map_err(Into::into)
365 }
366 Self::OwnedCompound(items) => {
367 let mut bytes = 0;
368 for item in &**items {
369 bytes += try_likely_ok!(item.format_into(output, value, state));
370 }
371 Ok(bytes)
372 }
373 Self::OwnedOptional {
374 format: should_format,
375 item,
376 } => {
377 if *should_format {
378 item.format_into(output, value, state)
379 } else {
380 Ok(0)
381 }
382 }
383 Self::OwnedFirst(items) => match &items[..] {
384 [] => Ok(0),
385 [item, ..] => item.format_into(output, value, state),
386 },
387
388 #[allow(unreachable_patterns)]
393 Day(_)
394 | MonthShort(_)
395 | MonthLong(_)
396 | MonthNumerical(_)
397 | Ordinal(_)
398 | WeekdayShort(_)
399 | WeekdayLong(_)
400 | WeekdaySunday(_)
401 | WeekdayMonday(_)
402 | WeekNumberIso(_)
403 | WeekNumberSunday(_)
404 | WeekNumberMonday(_)
405 | CalendarYearFullExtendedRange(_)
406 | CalendarYearFullStandardRange(_)
407 | IsoYearFullExtendedRange(_)
408 | IsoYearFullStandardRange(_)
409 | CalendarYearCenturyExtendedRange(_)
410 | CalendarYearCenturyStandardRange(_)
411 | IsoYearCenturyExtendedRange(_)
412 | IsoYearCenturyStandardRange(_)
413 | CalendarYearLastTwo(_)
414 | IsoYearLastTwo(_)
415 | Hour12(_)
416 | Hour24(_)
417 | Minute(_)
418 | Period(_)
419 | Second(_)
420 | Subsecond(_)
421 | OffsetHour(_)
422 | OffsetMinute(_)
423 | OffsetSecond(_)
424 | Ignore(_)
425 | UnixTimestampSecond(_)
426 | UnixTimestampMillisecond(_)
427 | UnixTimestampMicrosecond(_)
428 | UnixTimestampNanosecond(_)
429 | End(_) => Err(error::Format::InsufficientTypeInformation),
430 }
431 }
432}
433
434impl sealed::Sealed for BorrowedFormatItem<'_> {
435 #[expect(
436 private_bounds,
437 private_interfaces,
438 reason = "irrelevant due to being a sealed trait"
439 )]
440 #[inline]
441 fn format_into<V>(
442 &self,
443 output: &mut (impl io::Write + ?Sized),
444 value: &V,
445 state: &mut V::State,
446 ) -> Result<usize, error::Format>
447 where
448 V: ComponentProvider,
449 {
450 Ok(match *self {
451 #[expect(deprecated)]
452 Self::Literal(literal) => try_likely_ok!(write_bytes(output, literal)),
453 Self::StringLiteral(literal) => try_likely_ok!(write(output, literal)),
454 Self::Component(component) => {
455 FormatDescriptionV3Inner::<'_>::from(component).format_into(output, value, state)?
456 }
457 Self::Compound(items) => try_likely_ok!((*items).format_into(output, value, state)),
458 Self::Optional(item) => try_likely_ok!((*item).format_into(output, value, state)),
459 Self::First(items) => match items {
460 [] => 0,
461 [item, ..] => try_likely_ok!((*item).format_into(output, value, state)),
462 },
463 })
464 }
465}
466
467impl sealed::Sealed for [BorrowedFormatItem<'_>] {
468 #[expect(
469 private_bounds,
470 private_interfaces,
471 reason = "irrelevant due to being a sealed trait"
472 )]
473 #[inline]
474 fn format_into<V>(
475 &self,
476 output: &mut (impl io::Write + ?Sized),
477 value: &V,
478 state: &mut V::State,
479 ) -> Result<usize, error::Format>
480 where
481 V: ComponentProvider,
482 {
483 let mut bytes = 0;
484 for item in self.iter() {
485 bytes += try_likely_ok!(item.format_into(output, value, state));
486 }
487 Ok(bytes)
488 }
489}
490
491impl sealed::Sealed for OwnedFormatItem {
492 #[expect(
493 private_bounds,
494 private_interfaces,
495 reason = "irrelevant due to being a sealed trait"
496 )]
497 #[inline]
498 fn format_into<V>(
499 &self,
500 output: &mut (impl io::Write + ?Sized),
501 value: &V,
502 state: &mut V::State,
503 ) -> Result<usize, error::Format>
504 where
505 V: ComponentProvider,
506 {
507 match self {
508 #[expect(deprecated)]
509 Self::Literal(literal) => Ok(try_likely_ok!(write_bytes(output, literal))),
510 Self::StringLiteral(literal) => Ok(try_likely_ok!(write(output, literal))),
511 Self::Component(component) => {
512 FormatDescriptionV3Inner::<'_>::from(*component).format_into(output, value, state)
513 }
514 Self::Compound(items) => (**items).format_into(output, value, state),
515 Self::Optional(item) => (**item).format_into(output, value, state),
516 Self::First(items) => match &**items {
517 [] => Ok(0),
518 [item, ..] => (*item).format_into(output, value, state),
519 },
520 }
521 }
522}
523
524impl sealed::Sealed for [OwnedFormatItem] {
525 #[expect(
526 private_bounds,
527 private_interfaces,
528 reason = "irrelevant due to being a sealed trait"
529 )]
530 #[inline]
531 fn format_into<V>(
532 &self,
533 output: &mut (impl io::Write + ?Sized),
534 value: &V,
535 state: &mut V::State,
536 ) -> Result<usize, error::Format>
537 where
538 V: ComponentProvider,
539 {
540 let mut bytes = 0;
541 for item in self.iter() {
542 bytes += try_likely_ok!(item.format_into(output, value, state));
543 }
544 Ok(bytes)
545 }
546}
547
548impl<T> sealed::Sealed for T
549where
550 T: Deref<Target: sealed::Sealed>,
551{
552 #[expect(
553 private_bounds,
554 private_interfaces,
555 reason = "irrelevant due to being a sealed trait"
556 )]
557 #[inline]
558 fn format_into<V>(
559 &self,
560 output: &mut (impl io::Write + ?Sized),
561 value: &V,
562 state: &mut V::State,
563 ) -> Result<usize, error::Format>
564 where
565 V: ComponentProvider,
566 {
567 self.deref().format_into(output, value, state)
568 }
569}
570
571#[expect(
572 private_bounds,
573 private_interfaces,
574 reason = "irrelevant due to being a sealed trait"
575)]
576impl sealed::Sealed for Rfc2822 {
577 fn format_into<V>(
578 &self,
579 output: &mut (impl io::Write + ?Sized),
580 value: &V,
581 state: &mut V::State,
582 ) -> Result<usize, error::Format>
583 where
584 V: ComponentProvider,
585 {
586 const {
587 assert!(
588 V::SUPPLIES_DATE && V::SUPPLIES_TIME && V::SUPPLIES_OFFSET,
589 "Rfc2822 requires date, time, and offset components, but not all can be provided \
590 by this type"
591 );
592 }
593
594 let mut bytes = 0;
595
596 if value.calendar_year(state).get() < 1900
597 || (cfg!(feature = "large-dates") && value.calendar_year(state).get() >= 10_000)
599 {
600 crate::hint::cold_path();
601 return Err(error::Format::InvalidComponent("year"));
602 }
603 if value.offset_second(state).get() != 0 {
604 crate::hint::cold_path();
605 return Err(error::Format::InvalidComponent("offset_second"));
606 }
607
608 bytes += try_likely_ok!(write(output, unsafe {
610 WEEKDAY_NAMES[value
611 .weekday(state)
612 .number_days_from_monday()
613 .extend::<usize>()]
614 .get_unchecked(..3)
615 }));
616 bytes += try_likely_ok!(write(output, ", "));
617 bytes += try_likely_ok!(format_two_digits(
618 output,
619 value.day(state).expand(),
620 Padding::Zero
621 ));
622 bytes += try_likely_ok!(write(output, " "));
623 bytes += try_likely_ok!(write(output, unsafe {
625 MONTH_NAMES[u8::from(value.month(state)).extend::<usize>() - 1].get_unchecked(..3)
626 }));
627 bytes += try_likely_ok!(write(output, " "));
628 bytes += try_likely_ok!(format_four_digits_pad_zero(output, unsafe {
630 ru16::new_unchecked(value.calendar_year(state).get().cast_unsigned().truncate())
631 }));
632 bytes += try_likely_ok!(write(output, " "));
633 bytes += try_likely_ok!(format_two_digits(
634 output,
635 value.hour(state).expand(),
636 Padding::Zero
637 ));
638 bytes += try_likely_ok!(write(output, ":"));
639 bytes += try_likely_ok!(format_two_digits(
640 output,
641 value.minute(state).expand(),
642 Padding::Zero
643 ));
644 bytes += try_likely_ok!(write(output, ":"));
645 bytes += try_likely_ok!(format_two_digits(
646 output,
647 value.second(state).expand(),
648 Padding::Zero
649 ));
650 bytes += try_likely_ok!(write(output, " "));
651 bytes += try_likely_ok!(write_if_else(
652 output,
653 value.offset_is_negative(state),
654 "-",
655 "+"
656 ));
657 bytes += try_likely_ok!(format_two_digits(
658 output,
659 unsafe { ru8::new_unchecked(value.offset_hour(state).get().unsigned_abs()) },
662 Padding::Zero,
663 ));
664 bytes += try_likely_ok!(format_two_digits(
665 output,
666 unsafe { ru8::new_unchecked(value.offset_minute(state).get().unsigned_abs()) },
669 Padding::Zero,
670 ));
671
672 Ok(bytes)
673 }
674}
675
676#[expect(
677 private_bounds,
678 private_interfaces,
679 reason = "irrelevant due to being a sealed trait"
680)]
681impl sealed::Sealed for Rfc3339 {
682 fn format_into<V>(
683 &self,
684 output: &mut (impl io::Write + ?Sized),
685 value: &V,
686 state: &mut V::State,
687 ) -> Result<usize, error::Format>
688 where
689 V: ComponentProvider,
690 {
691 const {
692 assert!(
693 V::SUPPLIES_DATE && V::SUPPLIES_TIME && V::SUPPLIES_OFFSET,
694 "Rfc3339 requires date, time, and offset components, but not all can be provided \
695 by this type"
696 );
697 }
698
699 let offset_hour = value.offset_hour(state);
700 let mut bytes = 0;
701
702 if !(0..10_000).contains(&value.calendar_year(state).get()) {
703 crate::hint::cold_path();
704 return Err(error::Format::InvalidComponent("year"));
705 }
706 if offset_hour.get().unsigned_abs() > 23 {
707 crate::hint::cold_path();
708 return Err(error::Format::InvalidComponent("offset_hour"));
709 }
710 if value.offset_second(state).get() != 0 {
711 crate::hint::cold_path();
712 return Err(error::Format::InvalidComponent("offset_second"));
713 }
714
715 bytes += try_likely_ok!(format_four_digits_pad_zero(output, unsafe {
717 ru16::new_unchecked(value.calendar_year(state).get().cast_unsigned().truncate())
718 }));
719 bytes += try_likely_ok!(write(output, "-"));
720 bytes += try_likely_ok!(format_two_digits(
721 output,
722 unsafe { ru8::new_unchecked(u8::from(value.month(state))) },
724 Padding::Zero,
725 ));
726 bytes += try_likely_ok!(write(output, "-"));
727 bytes += try_likely_ok!(format_two_digits(
728 output,
729 value.day(state).expand(),
730 Padding::Zero
731 ));
732 bytes += try_likely_ok!(write(output, "T"));
733 bytes += try_likely_ok!(format_two_digits(
734 output,
735 value.hour(state).expand(),
736 Padding::Zero
737 ));
738 bytes += try_likely_ok!(write(output, ":"));
739 bytes += try_likely_ok!(format_two_digits(
740 output,
741 value.minute(state).expand(),
742 Padding::Zero
743 ));
744 bytes += try_likely_ok!(write(output, ":"));
745 bytes += try_likely_ok!(format_two_digits(
746 output,
747 value.second(state).expand(),
748 Padding::Zero
749 ));
750
751 let nanos = value.nanosecond(state);
752 if nanos.get() != 0 {
753 bytes += try_likely_ok!(write(output, "."));
754 try_likely_ok!(write(
755 output,
756 &num_fmt::truncated_subsecond_from_nanos(nanos)
757 ));
758 }
759
760 if value.offset_is_utc(state) {
761 bytes += try_likely_ok!(write(output, "Z"));
762 return Ok(bytes);
763 }
764
765 bytes += try_likely_ok!(write_if_else(
766 output,
767 value.offset_is_negative(state),
768 "-",
769 "+"
770 ));
771 bytes += try_likely_ok!(format_two_digits(
772 output,
773 unsafe { ru8::new_unchecked(offset_hour.get().unsigned_abs()) },
776 Padding::Zero,
777 ));
778 bytes += try_likely_ok!(write(output, ":"));
779 bytes += try_likely_ok!(format_two_digits(
780 output,
781 unsafe { ru8::new_unchecked(value.offset_minute(state).get().unsigned_abs()) },
784 Padding::Zero,
785 ));
786
787 Ok(bytes)
788 }
789}
790
791impl<const CONFIG: EncodedConfig> sealed::Sealed for Iso8601<CONFIG> {
792 #[expect(
793 private_bounds,
794 private_interfaces,
795 reason = "irrelevant due to being a sealed trait"
796 )]
797 #[inline]
798 fn format_into<V>(
799 &self,
800 output: &mut (impl io::Write + ?Sized),
801 value: &V,
802 state: &mut V::State,
803 ) -> Result<usize, error::Format>
804 where
805 V: ComponentProvider,
806 {
807 let mut bytes = 0;
808
809 const {
810 assert!(
811 !Self::FORMAT_DATE || V::SUPPLIES_DATE,
812 "this Iso8601 configuration formats date components, but this type cannot provide \
813 them"
814 );
815 assert!(
816 !Self::FORMAT_TIME || V::SUPPLIES_TIME,
817 "this Iso8601 configuration formats time components, but this type cannot provide \
818 them"
819 );
820 assert!(
821 !Self::FORMAT_OFFSET || V::SUPPLIES_OFFSET,
822 "this Iso8601 configuration formats offset components, but this type cannot \
823 provide them"
824 );
825 assert!(
826 Self::FORMAT_DATE || Self::FORMAT_TIME || Self::FORMAT_OFFSET,
827 "this Iso8601 configuration does not format any components"
828 );
829 }
830
831 if Self::FORMAT_DATE {
832 bytes += try_likely_ok!(iso8601::format_date::<_, CONFIG>(output, value, state));
833 }
834 if Self::FORMAT_TIME {
835 bytes += try_likely_ok!(iso8601::format_time::<_, CONFIG>(output, value, state));
836 }
837 if Self::FORMAT_OFFSET {
838 bytes += try_likely_ok!(iso8601::format_offset::<_, CONFIG>(output, value, state));
839 }
840
841 Ok(bytes)
842 }
843}