Skip to main content

time/formatting/
formattable.rs

1//! A trait that can be used to format an item from its components.
2
3use 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/// A type that describes a format.
24///
25/// Implementors of [`Formattable`] are [format descriptions](crate::format_description).
26///
27/// To format a value into a String, use the `format` method on the respective type.
28#[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
40/// Seal the trait to prevent downstream users from implementing it.
41mod sealed {
42    use super::*;
43    use crate::formatting::ComponentProvider;
44    use crate::formatting::metadata::ComputeMetadata;
45
46    /// Format the item using a format description, the intended output, and the various components.
47    #[expect(
48        private_bounds,
49        private_interfaces,
50        reason = "irrelevant due to being a sealed trait"
51    )]
52    pub trait Sealed: ComputeMetadata {
53        /// Format the item into the provided output, returning the number of bytes written.
54        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        /// Format the item directly to a `String`.
64        #[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                // Safety: The output is guaranteed to be UTF-8.
78                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                // Safety: Given the range of `year`, the range of the century is
205                // `-9_999..=9_999`.
206                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                // Safety: Given the range of `year`, the range of the century is
219                // `-9_999..=9_999`.
220                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                // Safety: Given the range of `year`, the range of the century is
236                // `-9_999..=9_999`.
237                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                // Safety: Given the range of `year`, the range of the century is
245                // `-9_999..=9_999`.
246                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                // Safety: Modulus of 100 followed by `.unsigned_abs()` guarantees that the
260                // value is in the range `0..=99`.
261                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                // Safety: Modulus of 100 followed by `.unsigned_abs()` guarantees that the
270                // value is in the range `0..=99`.
271                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            // This is functionally the same as a wildcard arm, but it will cause an error
389            // if a new component is added. This is to avoid a bug where
390            // a new component, the code compiles, and formatting fails.
391            // Allow unreachable patterns because some branches may be fully matched above.
392            #[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            // The RFC requires years be exactly four digits.
598            || (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        // Safety: All weekday names are at least 3 bytes long.
609        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        // Safety: All month names are at least 3 bytes long.
624        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        // Safety: Years with five or more digits were rejected above. Likewise with negative years.
629        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            // Safety: `OffsetHours` is guaranteed to be in the range `-25..=25`, so the absolute
660            // value is guaranteed to be in the range `0..=25`.
661            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            // Safety: `OffsetMinutes` is guaranteed to be in the range `-59..=59`, so the absolute
667            // value is guaranteed to be in the range `0..=59`.
668            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        // Safety: Years outside this range were rejected above.
716        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            // Safety: `month` is guaranteed to be in the range `1..=12`.
723            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            // Safety: `OffsetHours` is guaranteed to be in the range `-23..=23`, so the absolute
774            // value is guaranteed to be in the range `0..=23`.
775            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            // Safety: `OffsetMinutes` is guaranteed to be in the range `-59..=59`, so the absolute
782            // value is guaranteed to be in the range `0..=59`.
783            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}