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::{
16    BorrowedFormatItem, Component, FormatDescriptionV3, OwnedFormatItem,
17};
18use crate::formatting::{
19    ComponentProvider, MONTH_NAMES, WEEKDAY_NAMES, format_four_digits_pad_zero, format_two_digits,
20    iso8601, write, write_bytes, write_if_else,
21};
22use crate::internal_macros::try_likely_ok;
23use crate::{PrivateMethod, error, num_fmt};
24
25macro_rules! fmt_component_match {
26    ($self:expr, $output:ident, $value:ident, $state:ident, $($extra:tt)*) => {
27        match $self {
28            Self::Day(modifier) if V::SUPPLIES_DATE => {
29                fmt_day($output, $value.day($state), *modifier).map_err(Into::into)
30            }
31            Self::MonthShort(modifier) if V::SUPPLIES_DATE => {
32                fmt_month_short($output, $value.month($state), *modifier).map_err(Into::into)
33            }
34            Self::MonthLong(modifier) if V::SUPPLIES_DATE => {
35                fmt_month_long($output, $value.month($state), *modifier).map_err(Into::into)
36            }
37            Self::MonthNumerical(modifier) if V::SUPPLIES_DATE => {
38                fmt_month_numerical($output, $value.month($state), *modifier).map_err(Into::into)
39            }
40            Self::Ordinal(modifier) if V::SUPPLIES_DATE => {
41                fmt_ordinal($output, $value.ordinal($state), *modifier).map_err(Into::into)
42            }
43            Self::WeekdayShort(modifier) if V::SUPPLIES_DATE => {
44                fmt_weekday_short($output, $value.weekday($state), *modifier).map_err(Into::into)
45            }
46            Self::WeekdayLong(modifier) if V::SUPPLIES_DATE => {
47                fmt_weekday_long($output, $value.weekday($state), *modifier).map_err(Into::into)
48            }
49            Self::WeekdaySunday(modifier) if V::SUPPLIES_DATE => {
50                fmt_weekday_sunday($output, $value.weekday($state), *modifier).map_err(Into::into)
51            }
52            Self::WeekdayMonday(modifier) if V::SUPPLIES_DATE => {
53                fmt_weekday_monday($output, $value.weekday($state), *modifier).map_err(Into::into)
54            }
55            Self::WeekNumberIso(modifier) if V::SUPPLIES_DATE => {
56                fmt_week_number_iso($output, $value.iso_week_number($state), *modifier)
57                    .map_err(Into::into)
58            }
59            Self::WeekNumberSunday(modifier) if V::SUPPLIES_DATE => {
60                fmt_week_number_sunday($output, $value.sunday_based_week($state), *modifier)
61                    .map_err(Into::into)
62            }
63            Self::WeekNumberMonday(modifier) if V::SUPPLIES_DATE => {
64                fmt_week_number_monday($output, $value.monday_based_week($state), *modifier)
65                    .map_err(Into::into)
66            }
67            Self::CalendarYearFullExtendedRange(modifier) if V::SUPPLIES_DATE => {
68                fmt_calendar_year_full_extended_range(
69                    $output,
70                    $value.calendar_year($state),
71                    *modifier
72                ).map_err(Into::into)
73            }
74            Self::CalendarYearFullStandardRange(modifier) if V::SUPPLIES_DATE => {
75                fmt_calendar_year_full_standard_range(
76                    $output,
77                    try_likely_ok!(
78                        $value
79                            .calendar_year($state)
80                            .narrow::<-9_999, 9_999>()
81                            .ok_or_else(|| error::ComponentRange::conditional("year"))
82                    )
83                    .into(),
84                    *modifier,
85                )
86                .map_err(Into::into)
87            }
88            Self::IsoYearFullExtendedRange(modifier) if V::SUPPLIES_DATE => {
89                fmt_iso_year_full_extended_range($output, $value.iso_year($state), *modifier)
90                    .map_err(Into::into)
91            }
92            Self::IsoYearFullStandardRange(modifier) if V::SUPPLIES_DATE => {
93                fmt_iso_year_full_standard_range(
94                    $output,
95                    try_likely_ok!(
96                        $value
97                            .iso_year($state)
98                            .narrow::<-9_999, 9_999>()
99                            .ok_or_else(|| error::ComponentRange::conditional("year"))
100                    )
101                    .into(),
102                    *modifier,
103                )
104                .map_err(Into::into)
105            }
106            Self::CalendarYearCenturyExtendedRange(modifier) if V::SUPPLIES_DATE => {
107                let year = $value.calendar_year($state);
108                // Safety: Given the range of `year`, the range of the century is `-9_999..=9_999`.
109                let century = unsafe { ri16::new_unchecked((year.get() / 100).truncate()) };
110                fmt_calendar_year_century_extended_range(
111                    $output,
112                    century,
113                    year.is_negative(),
114                    *modifier,
115                )
116                .map_err(Into::into)
117            }
118            Self::CalendarYearCenturyStandardRange(modifier) if V::SUPPLIES_DATE => {
119                let year = $value.calendar_year($state);
120                let is_negative = year.is_negative();
121                // Safety: Given the range of `year`, the range of the century is `-9_999..=9_999`.
122                let year = unsafe {
123                    ri16::<-9_999, 9_999>::new_unchecked((year.get() / 100).truncate())
124                };
125                fmt_calendar_year_century_standard_range(
126                    $output,
127                    year.narrow::<-99, 99>()
128                        .ok_or_else(|| error::ComponentRange::conditional("year"))?
129                        .into(),
130                    is_negative,
131                    *modifier,
132                )
133                .map_err(Into::into)
134            }
135            Self::IsoYearCenturyExtendedRange(modifier) if V::SUPPLIES_DATE => {
136                let year = $value.iso_year($state);
137                let is_negative = year.is_negative();
138                // Safety: Given the range of `year`, the range of the century is `-9_999..=9_999`.
139                let century = unsafe { ri16::new_unchecked((year.get() / 100).truncate()) };
140                fmt_iso_year_century_extended_range($output, century, is_negative, *modifier)
141                    .map_err(Into::into)
142            }
143            Self::IsoYearCenturyStandardRange(modifier) if V::SUPPLIES_DATE => {
144                let year = $value.iso_year($state);
145                let is_negative = year.is_negative();
146                // Safety: Given the range of `year`, the range of the century is `-9_999..=9_999`.
147                let year = unsafe {
148                    ri16::<-9_999, 9_999>::new_unchecked((year.get() / 100).truncate())
149                };
150                fmt_iso_year_century_standard_range(
151                    $output,
152                    year.narrow::<-99, 99>()
153                        .ok_or_else(|| error::ComponentRange::conditional("year"))?
154                        .into(),
155                    is_negative,
156                    *modifier,
157                )
158                .map_err(Into::into)
159            }
160            Self::CalendarYearLastTwo(modifier) if V::SUPPLIES_DATE => {
161                // Safety: Modulus of 100 followed by `.unsigned_abs()` guarantees that the $value
162                // is in the range `0..=99`.
163                let last_two = unsafe {
164                    ru8::new_unchecked(
165                        ($value.calendar_year($state).get().unsigned_abs() % 100).truncate(),
166                    )
167                };
168                fmt_calendar_year_last_two($output, last_two, *modifier).map_err(Into::into)
169            }
170            Self::IsoYearLastTwo(modifier) if V::SUPPLIES_DATE => {
171                // Safety: Modulus of 100 followed by `.unsigned_abs()` guarantees that the $value
172                // is in the range `0..=99`.
173                let last_two = unsafe {
174                    ru8::new_unchecked(
175                        ($value.iso_year($state).get().unsigned_abs() % 100).truncate(),
176                    )
177                };
178                fmt_iso_year_last_two($output, last_two, *modifier).map_err(Into::into)
179            }
180            Self::Hour12(modifier) if V::SUPPLIES_TIME => {
181                fmt_hour_12($output, $value.hour($state), *modifier).map_err(Into::into)
182            }
183            Self::Hour24(modifier) if V::SUPPLIES_TIME => {
184                fmt_hour_24($output, $value.hour($state), *modifier).map_err(Into::into)
185            }
186            Self::Minute(modifier) if V::SUPPLIES_TIME => {
187                fmt_minute($output, $value.minute($state), *modifier).map_err(Into::into)
188            }
189            Self::Period(modifier) if V::SUPPLIES_TIME => {
190                fmt_period($output, $value.period($state), *modifier).map_err(Into::into)
191            }
192            Self::Second(modifier) if V::SUPPLIES_TIME => {
193                fmt_second($output, $value.second($state), *modifier).map_err(Into::into)
194            }
195            Self::Subsecond(modifier) if V::SUPPLIES_TIME => {
196                fmt_subsecond($output, $value.nanosecond($state), *modifier).map_err(Into::into)
197            }
198            Self::OffsetHour(modifier) if V::SUPPLIES_OFFSET => fmt_offset_hour(
199                $output,
200                $value.offset_is_negative($state),
201                $value.offset_hour($state),
202                *modifier,
203            )
204            .map_err(Into::into),
205            Self::OffsetMinute(modifier) if V::SUPPLIES_OFFSET => {
206                fmt_offset_minute($output, $value.offset_minute($state), *modifier)
207                    .map_err(Into::into)
208            }
209            Self::OffsetSecond(modifier) if V::SUPPLIES_OFFSET => {
210                fmt_offset_second($output, $value.offset_second($state), *modifier)
211                    .map_err(Into::into)
212            }
213            Self::Ignore(_) => Ok(0),
214            Self::UnixTimestampSecond(modifier) if V::SUPPLIES_TIMESTAMP => {
215                fmt_unix_timestamp_second($output, $value.unix_timestamp_seconds($state), *modifier)
216                    .map_err(Into::into)
217            }
218            Self::UnixTimestampMillisecond(modifier) if V::SUPPLIES_TIMESTAMP => {
219                fmt_unix_timestamp_millisecond(
220                    $output,
221                    $value.unix_timestamp_milliseconds($state),
222                    *modifier,
223                )
224                .map_err(Into::into)
225            }
226            Self::UnixTimestampMicrosecond(modifier) if V::SUPPLIES_TIMESTAMP => {
227                fmt_unix_timestamp_microsecond(
228                    $output,
229                    $value.unix_timestamp_microseconds($state),
230                    *modifier,
231                )
232                .map_err(Into::into)
233            }
234            Self::UnixTimestampNanosecond(modifier) if V::SUPPLIES_TIMESTAMP => {
235                fmt_unix_timestamp_nanosecond(
236                    $output,
237                    $value.unix_timestamp_nanoseconds($state),
238                    *modifier,
239                )
240                .map_err(Into::into)
241            }
242            Self::End(modifier::End { trailing_input: _ }) => Ok(0),
243            $($extra)*
244        }
245    };
246}
247
248/// A type that describes a format.
249///
250/// Implementors of [`Formattable`] are [format descriptions](crate::format_description).
251///
252/// To format a value into a String, use the `format` method on the respective type.
253#[cfg_attr(docsrs, doc(notable_trait))]
254pub trait Formattable: sealed::Sealed {}
255impl Formattable for FormatDescriptionV3<'_> {}
256impl Formattable for BorrowedFormatItem<'_> {}
257impl Formattable for [BorrowedFormatItem<'_>] {}
258impl Formattable for OwnedFormatItem {}
259impl Formattable for [OwnedFormatItem] {}
260impl Formattable for Rfc3339 {}
261impl Formattable for Rfc2822 {}
262impl<const CONFIG: EncodedConfig> Formattable for Iso8601<CONFIG> {}
263impl<T> Formattable for T where T: Deref<Target: Formattable> {}
264
265/// Seal the trait to prevent downstream users from implementing it.
266mod sealed {
267    use super::*;
268    use crate::formatting::ComponentProvider;
269    use crate::formatting::metadata::ComputeMetadata;
270
271    /// Format the item using a format description, the intended output, and the various components.
272    #[expect(
273        private_bounds,
274        private_interfaces,
275        reason = "irrelevant due to being a sealed trait"
276    )]
277    pub trait Sealed: ComputeMetadata {
278        /// Format the item into the provided output, returning the number of bytes written.
279        fn format_into<V>(
280            &self,
281            output: &mut (impl io::Write + ?Sized),
282            value: &V,
283            state: &mut V::State,
284            _: PrivateMethod,
285        ) -> Result<usize, error::Format>
286        where
287            V: ComponentProvider;
288
289        /// Format the item directly to a `String`.
290        #[inline]
291        fn format<V>(
292            &self,
293            value: &V,
294            state: &mut V::State,
295            _: PrivateMethod,
296        ) -> Result<String, error::Format>
297        where
298            V: ComponentProvider,
299        {
300            let crate::formatting::metadata::Metadata {
301                max_bytes_needed,
302                guaranteed_utf8,
303            } = self.compute_metadata(PrivateMethod);
304
305            let mut buf = Vec::with_capacity(max_bytes_needed);
306            try_likely_ok!(self.format_into(&mut buf, value, state, PrivateMethod));
307            Ok(if guaranteed_utf8 {
308                // Safety: The output is guaranteed to be UTF-8.
309                unsafe { String::from_utf8_unchecked(buf) }
310            } else {
311                String::from_utf8_lossy(&buf).into_owned()
312            })
313        }
314    }
315}
316
317impl sealed::Sealed for FormatDescriptionV3<'_> {
318    #[expect(
319        private_bounds,
320        private_interfaces,
321        reason = "irrelevant due to being a sealed trait"
322    )]
323    #[inline]
324    fn format_into<V>(
325        &self,
326        output: &mut (impl io::Write + ?Sized),
327        value: &V,
328        state: &mut V::State,
329        _: PrivateMethod,
330    ) -> Result<usize, error::Format>
331    where
332        V: ComponentProvider,
333    {
334        self.inner.format_into(output, value, state, PrivateMethod)
335    }
336}
337
338impl sealed::Sealed for FormatDescriptionV3Inner<'_> {
339    #[expect(
340        private_bounds,
341        private_interfaces,
342        reason = "irrelevant due to being a sealed trait"
343    )]
344    #[inline]
345    fn format_into<V>(
346        &self,
347        output: &mut (impl io::Write + ?Sized),
348        value: &V,
349        state: &mut V::State,
350        _: PrivateMethod,
351    ) -> Result<usize, error::Format>
352    where
353        V: ComponentProvider,
354    {
355        use crate::formatting::*;
356
357        fmt_component_match! { &self, output, value, state,
358            Self::BorrowedLiteral(literal) => {
359                write_bytes(output, literal.as_bytes()).map_err(Into::into)
360            }
361            Self::BorrowedCompound(items) => {
362                let mut bytes = 0;
363                for item in *items {
364                    bytes += try_likely_ok!(item.format_into(output, value, state, PrivateMethod));
365                }
366                Ok(bytes)
367            }
368            Self::BorrowedOptional {
369                format: should_format,
370                item,
371            } => {
372                if *should_format {
373                    item.format_into(output, value, state, PrivateMethod)
374                } else {
375                    Ok(0)
376                }
377            }
378            Self::BorrowedFirst(items) => match items {
379                [] => Ok(0),
380                [item, ..] => item.format_into(output, value, state, PrivateMethod),
381            },
382            Self::OwnedLiteral(literal) => {
383                write_bytes(output, literal.as_bytes()).map_err(Into::into)
384            }
385            Self::OwnedCompound(items) => {
386                let mut bytes = 0;
387                for item in &**items {
388                    bytes += try_likely_ok!(item.format_into(output, value, state, PrivateMethod));
389                }
390                Ok(bytes)
391            }
392            Self::OwnedOptional {
393                format: should_format,
394                item,
395            } => {
396                if *should_format {
397                    item.format_into(output, value, state, PrivateMethod)
398                } else {
399                    Ok(0)
400                }
401            }
402            Self::OwnedFirst(items) => match &items[..] {
403                [] => Ok(0),
404                [item, ..] => item.format_into(output, value, state, PrivateMethod),
405            },
406
407            // This is functionally the same as a wildcard arm, but it will cause an error
408            // if a new component is added. This is to avoid a bug where
409            // a new component, the code compiles, and formatting fails.
410            // Allow unreachable patterns because some branches may be fully matched above.
411            #[allow(unreachable_patterns)]
412            Self::Day(_)
413            | Self::MonthShort(_)
414            | Self::MonthLong(_)
415            | Self::MonthNumerical(_)
416            | Self::Ordinal(_)
417            | Self::WeekdayShort(_)
418            | Self::WeekdayLong(_)
419            | Self::WeekdaySunday(_)
420            | Self::WeekdayMonday(_)
421            | Self::WeekNumberIso(_)
422            | Self::WeekNumberSunday(_)
423            | Self::WeekNumberMonday(_)
424            | Self::CalendarYearFullExtendedRange(_)
425            | Self::CalendarYearFullStandardRange(_)
426            | Self::IsoYearFullExtendedRange(_)
427            | Self::IsoYearFullStandardRange(_)
428            | Self::CalendarYearCenturyExtendedRange(_)
429            | Self::CalendarYearCenturyStandardRange(_)
430            | Self::IsoYearCenturyExtendedRange(_)
431            | Self::IsoYearCenturyStandardRange(_)
432            | Self::CalendarYearLastTwo(_)
433            | Self::IsoYearLastTwo(_)
434            | Self::Hour12(_)
435            | Self::Hour24(_)
436            | Self::Minute(_)
437            | Self::Period(_)
438            | Self::Second(_)
439            | Self::Subsecond(_)
440            | Self::OffsetHour(_)
441            | Self::OffsetMinute(_)
442            | Self::OffsetSecond(_)
443            | Self::Ignore(_)
444            | Self::UnixTimestampSecond(_)
445            | Self::UnixTimestampMillisecond(_)
446            | Self::UnixTimestampMicrosecond(_)
447            | Self::UnixTimestampNanosecond(_)
448            | Self::End(_) => Err(error::Format::InsufficientTypeInformation),
449        }
450    }
451}
452
453impl Component {
454    /// Format the component directly into the provided output.
455    #[inline]
456    #[allow(deprecated)]
457    pub(crate) fn format_into<V>(
458        &self,
459        output: &mut (impl io::Write + ?Sized),
460        value: &V,
461        state: &mut V::State,
462    ) -> Result<usize, error::Format>
463    where
464        V: ComponentProvider,
465    {
466        use sealed::Sealed;
467
468        use crate::formatting::*;
469
470        fmt_component_match! { self, output, value, state,
471            // Deprecated component variants: delegate through the existing conversion.
472            Self::Month(_) | Self::Weekday(_) | Self::WeekNumber(_)
473                if V::SUPPLIES_DATE =>
474            {
475                FormatDescriptionV3Inner::from(*self)
476                    .format_into(output, value, state, PrivateMethod)
477            }
478            Self::Hour(_) if V::SUPPLIES_TIME => {
479                FormatDescriptionV3Inner::from(*self)
480                    .format_into(output, value, state, PrivateMethod)
481            }
482            Self::UnixTimestamp(_) if V::SUPPLIES_TIMESTAMP => {
483                FormatDescriptionV3Inner::from(*self)
484                    .format_into(output, value, state, PrivateMethod)
485            }
486            Self::Year(_) if V::SUPPLIES_DATE => {
487                FormatDescriptionV3Inner::from(*self)
488                    .format_into(output, value, state, PrivateMethod)
489            }
490
491            // Unmatched component (e.g. time component on a date-only type).
492            #[allow(unreachable_patterns)]
493            Self::Day(_)
494            | Self::MonthShort(_)
495            | Self::MonthLong(_)
496            | Self::MonthNumerical(_)
497            | Self::Ordinal(_)
498            | Self::WeekdayShort(_)
499            | Self::WeekdayLong(_)
500            | Self::WeekdaySunday(_)
501            | Self::WeekdayMonday(_)
502            | Self::WeekNumberIso(_)
503            | Self::WeekNumberSunday(_)
504            | Self::WeekNumberMonday(_)
505            | Self::CalendarYearFullExtendedRange(_)
506            | Self::CalendarYearFullStandardRange(_)
507            | Self::IsoYearFullExtendedRange(_)
508            | Self::IsoYearFullStandardRange(_)
509            | Self::CalendarYearCenturyExtendedRange(_)
510            | Self::CalendarYearCenturyStandardRange(_)
511            | Self::IsoYearCenturyExtendedRange(_)
512            | Self::IsoYearCenturyStandardRange(_)
513            | Self::CalendarYearLastTwo(_)
514            | Self::IsoYearLastTwo(_)
515            | Self::Hour12(_)
516            | Self::Hour24(_)
517            | Self::Minute(_)
518            | Self::Period(_)
519            | Self::Second(_)
520            | Self::Subsecond(_)
521            | Self::OffsetHour(_)
522            | Self::OffsetMinute(_)
523            | Self::OffsetSecond(_)
524            | Self::Ignore(_)
525            | Self::UnixTimestampSecond(_)
526            | Self::UnixTimestampMillisecond(_)
527            | Self::UnixTimestampMicrosecond(_)
528            | Self::UnixTimestampNanosecond(_)
529            // Deprecated variants not matched by guarded arms above.
530            | Self::Month(_)
531            | Self::Weekday(_)
532            | Self::WeekNumber(_)
533            | Self::Hour(_)
534            | Self::UnixTimestamp(_)
535            | Self::Year(_) => Err(error::Format::InsufficientTypeInformation),
536        }
537    }
538}
539
540impl sealed::Sealed for BorrowedFormatItem<'_> {
541    #[expect(
542        private_bounds,
543        private_interfaces,
544        reason = "irrelevant due to being a sealed trait"
545    )]
546    #[inline]
547    fn format_into<V>(
548        &self,
549        output: &mut (impl io::Write + ?Sized),
550        value: &V,
551        state: &mut V::State,
552        _: PrivateMethod,
553    ) -> Result<usize, error::Format>
554    where
555        V: ComponentProvider,
556    {
557        Ok(match *self {
558            #[expect(deprecated)]
559            Self::Literal(literal) => try_likely_ok!(write_bytes(output, literal)),
560            Self::StringLiteral(literal) => try_likely_ok!(write(output, literal)),
561            Self::Component(component) => component.format_into(output, value, state)?,
562            Self::Compound(items) => {
563                try_likely_ok!((*items).format_into(output, value, state, PrivateMethod))
564            }
565            Self::Optional(item) => {
566                try_likely_ok!((*item).format_into(output, value, state, PrivateMethod))
567            }
568            Self::First(items) => match items {
569                [] => 0,
570                [item, ..] => {
571                    try_likely_ok!((*item).format_into(output, value, state, PrivateMethod))
572                }
573            },
574        })
575    }
576}
577
578impl sealed::Sealed for [BorrowedFormatItem<'_>] {
579    #[expect(
580        private_bounds,
581        private_interfaces,
582        reason = "irrelevant due to being a sealed trait"
583    )]
584    #[inline]
585    fn format_into<V>(
586        &self,
587        output: &mut (impl io::Write + ?Sized),
588        value: &V,
589        state: &mut V::State,
590        _: PrivateMethod,
591    ) -> Result<usize, error::Format>
592    where
593        V: ComponentProvider,
594    {
595        let mut bytes = 0;
596        for item in self.iter() {
597            bytes += try_likely_ok!(item.format_into(output, value, state, PrivateMethod));
598        }
599        Ok(bytes)
600    }
601}
602
603impl sealed::Sealed for OwnedFormatItem {
604    #[expect(
605        private_bounds,
606        private_interfaces,
607        reason = "irrelevant due to being a sealed trait"
608    )]
609    #[inline]
610    fn format_into<V>(
611        &self,
612        output: &mut (impl io::Write + ?Sized),
613        value: &V,
614        state: &mut V::State,
615        _: PrivateMethod,
616    ) -> Result<usize, error::Format>
617    where
618        V: ComponentProvider,
619    {
620        match self {
621            #[expect(deprecated)]
622            Self::Literal(literal) => Ok(try_likely_ok!(write_bytes(output, literal))),
623            Self::StringLiteral(literal) => Ok(try_likely_ok!(write(output, literal))),
624            Self::Component(component) => FormatDescriptionV3Inner::<'_>::from(*component)
625                .format_into(output, value, state, PrivateMethod),
626            Self::Compound(items) => (**items).format_into(output, value, state, PrivateMethod),
627            Self::Optional(item) => (**item).format_into(output, value, state, PrivateMethod),
628            Self::First(items) => match &**items {
629                [] => Ok(0),
630                [item, ..] => (*item).format_into(output, value, state, PrivateMethod),
631            },
632        }
633    }
634}
635
636impl sealed::Sealed for [OwnedFormatItem] {
637    #[expect(
638        private_bounds,
639        private_interfaces,
640        reason = "irrelevant due to being a sealed trait"
641    )]
642    #[inline]
643    fn format_into<V>(
644        &self,
645        output: &mut (impl io::Write + ?Sized),
646        value: &V,
647        state: &mut V::State,
648        _: PrivateMethod,
649    ) -> Result<usize, error::Format>
650    where
651        V: ComponentProvider,
652    {
653        let mut bytes = 0;
654        for item in self.iter() {
655            bytes += try_likely_ok!(item.format_into(output, value, state, PrivateMethod));
656        }
657        Ok(bytes)
658    }
659}
660
661impl<T> sealed::Sealed for T
662where
663    T: Deref<Target: sealed::Sealed>,
664{
665    #[expect(
666        private_bounds,
667        private_interfaces,
668        reason = "irrelevant due to being a sealed trait"
669    )]
670    #[inline]
671    fn format_into<V>(
672        &self,
673        output: &mut (impl io::Write + ?Sized),
674        value: &V,
675        state: &mut V::State,
676        _: PrivateMethod,
677    ) -> Result<usize, error::Format>
678    where
679        V: ComponentProvider,
680    {
681        self.deref()
682            .format_into(output, value, state, PrivateMethod)
683    }
684}
685
686#[expect(
687    private_bounds,
688    private_interfaces,
689    reason = "irrelevant due to being a sealed trait"
690)]
691impl sealed::Sealed for Rfc2822 {
692    fn format_into<V>(
693        &self,
694        output: &mut (impl io::Write + ?Sized),
695        value: &V,
696        state: &mut V::State,
697        _: PrivateMethod,
698    ) -> Result<usize, error::Format>
699    where
700        V: ComponentProvider,
701    {
702        const {
703            assert!(
704                V::SUPPLIES_DATE && V::SUPPLIES_TIME && V::SUPPLIES_OFFSET,
705                "Rfc2822 requires date, time, and offset components, but not all can be provided \
706                 by this type"
707            );
708        }
709
710        let mut bytes = 0;
711
712        if value.calendar_year(state).get() < 1900
713            // The RFC requires years be exactly four digits.
714            || (cfg!(feature = "large-dates") && value.calendar_year(state).get() >= 10_000)
715        {
716            crate::hint::cold_path();
717            return Err(error::Format::InvalidComponent("year"));
718        }
719        if value.offset_second(state).get() != 0 {
720            crate::hint::cold_path();
721            return Err(error::Format::InvalidComponent("offset_second"));
722        }
723
724        // Safety: All weekday names are at least 3 bytes long.
725        bytes += try_likely_ok!(write(output, unsafe {
726            WEEKDAY_NAMES[value
727                .weekday(state)
728                .number_days_from_monday()
729                .widen::<usize>()]
730            .get_unchecked(..3)
731        }));
732        bytes += try_likely_ok!(write(output, ", "));
733        bytes += try_likely_ok!(format_two_digits(
734            output,
735            value.day(state).expand(),
736            Padding::Zero
737        ));
738        bytes += try_likely_ok!(write(output, " "));
739        // Safety: All month names are at least 3 bytes long.
740        bytes += try_likely_ok!(write(output, unsafe {
741            MONTH_NAMES[u8::from(value.month(state)).widen::<usize>() - 1].get_unchecked(..3)
742        }));
743        bytes += try_likely_ok!(write(output, " "));
744        // Safety: Years with five or more digits were rejected above. Likewise with negative years.
745        bytes += try_likely_ok!(format_four_digits_pad_zero(output, unsafe {
746            ru16::new_unchecked(value.calendar_year(state).get().cast_unsigned().truncate())
747        }));
748        bytes += try_likely_ok!(write(output, " "));
749        bytes += try_likely_ok!(format_two_digits(
750            output,
751            value.hour(state).expand(),
752            Padding::Zero
753        ));
754        bytes += try_likely_ok!(write(output, ":"));
755        bytes += try_likely_ok!(format_two_digits(
756            output,
757            value.minute(state).expand(),
758            Padding::Zero
759        ));
760        bytes += try_likely_ok!(write(output, ":"));
761        bytes += try_likely_ok!(format_two_digits(
762            output,
763            value.second(state).expand(),
764            Padding::Zero
765        ));
766        bytes += try_likely_ok!(write(output, " "));
767        bytes += try_likely_ok!(write_if_else(
768            output,
769            value.offset_is_negative(state),
770            "-",
771            "+"
772        ));
773        bytes += try_likely_ok!(format_two_digits(
774            output,
775            // Safety: `OffsetHours` is guaranteed to be in the range `-25..=25`, so the absolute
776            // value is guaranteed to be in the range `0..=25`.
777            unsafe { ru8::new_unchecked(value.offset_hour(state).get().unsigned_abs()) },
778            Padding::Zero,
779        ));
780        bytes += try_likely_ok!(format_two_digits(
781            output,
782            // Safety: `OffsetMinutes` is guaranteed to be in the range `-59..=59`, so the absolute
783            // value is guaranteed to be in the range `0..=59`.
784            unsafe { ru8::new_unchecked(value.offset_minute(state).get().unsigned_abs()) },
785            Padding::Zero,
786        ));
787
788        Ok(bytes)
789    }
790}
791
792#[expect(
793    private_bounds,
794    private_interfaces,
795    reason = "irrelevant due to being a sealed trait"
796)]
797impl sealed::Sealed for Rfc3339 {
798    fn format_into<V>(
799        &self,
800        output: &mut (impl io::Write + ?Sized),
801        value: &V,
802        state: &mut V::State,
803        _: PrivateMethod,
804    ) -> Result<usize, error::Format>
805    where
806        V: ComponentProvider,
807    {
808        const {
809            assert!(
810                V::SUPPLIES_DATE && V::SUPPLIES_TIME && V::SUPPLIES_OFFSET,
811                "Rfc3339 requires date, time, and offset components, but not all can be provided \
812                 by this type"
813            );
814        }
815
816        let offset_hour = value.offset_hour(state);
817        let mut bytes = 0;
818
819        if !(0..10_000).contains(&value.calendar_year(state).get()) {
820            crate::hint::cold_path();
821            return Err(error::Format::InvalidComponent("year"));
822        }
823        if offset_hour.get().unsigned_abs() > 23 {
824            crate::hint::cold_path();
825            return Err(error::Format::InvalidComponent("offset_hour"));
826        }
827        if value.offset_second(state).get() != 0 {
828            crate::hint::cold_path();
829            return Err(error::Format::InvalidComponent("offset_second"));
830        }
831
832        // Safety: Years outside this range were rejected above.
833        bytes += try_likely_ok!(format_four_digits_pad_zero(output, unsafe {
834            ru16::new_unchecked(value.calendar_year(state).get().cast_unsigned().truncate())
835        }));
836        bytes += try_likely_ok!(write(output, "-"));
837        bytes += try_likely_ok!(format_two_digits(
838            output,
839            // Safety: `month` is guaranteed to be in the range `1..=12`.
840            unsafe { ru8::new_unchecked(u8::from(value.month(state))) },
841            Padding::Zero,
842        ));
843        bytes += try_likely_ok!(write(output, "-"));
844        bytes += try_likely_ok!(format_two_digits(
845            output,
846            value.day(state).expand(),
847            Padding::Zero
848        ));
849        bytes += try_likely_ok!(write(output, "T"));
850        bytes += try_likely_ok!(format_two_digits(
851            output,
852            value.hour(state).expand(),
853            Padding::Zero
854        ));
855        bytes += try_likely_ok!(write(output, ":"));
856        bytes += try_likely_ok!(format_two_digits(
857            output,
858            value.minute(state).expand(),
859            Padding::Zero
860        ));
861        bytes += try_likely_ok!(write(output, ":"));
862        bytes += try_likely_ok!(format_two_digits(
863            output,
864            value.second(state).expand(),
865            Padding::Zero
866        ));
867
868        let nanos = value.nanosecond(state);
869        if nanos.get() != 0 {
870            bytes += try_likely_ok!(write(output, "."));
871            try_likely_ok!(write(
872                output,
873                &num_fmt::truncated_subsecond_from_nanos(nanos)
874            ));
875        }
876
877        if value.offset_is_utc(state) {
878            bytes += try_likely_ok!(write(output, "Z"));
879            return Ok(bytes);
880        }
881
882        bytes += try_likely_ok!(write_if_else(
883            output,
884            value.offset_is_negative(state),
885            "-",
886            "+"
887        ));
888        bytes += try_likely_ok!(format_two_digits(
889            output,
890            // Safety: `OffsetHours` is guaranteed to be in the range `-23..=23`, so the absolute
891            // value is guaranteed to be in the range `0..=23`.
892            unsafe { ru8::new_unchecked(offset_hour.get().unsigned_abs()) },
893            Padding::Zero,
894        ));
895        bytes += try_likely_ok!(write(output, ":"));
896        bytes += try_likely_ok!(format_two_digits(
897            output,
898            // Safety: `OffsetMinutes` is guaranteed to be in the range `-59..=59`, so the absolute
899            // value is guaranteed to be in the range `0..=59`.
900            unsafe { ru8::new_unchecked(value.offset_minute(state).get().unsigned_abs()) },
901            Padding::Zero,
902        ));
903
904        Ok(bytes)
905    }
906}
907
908impl<const CONFIG: EncodedConfig> sealed::Sealed for Iso8601<CONFIG> {
909    #[expect(
910        private_bounds,
911        private_interfaces,
912        reason = "irrelevant due to being a sealed trait"
913    )]
914    #[inline]
915    fn format_into<V>(
916        &self,
917        output: &mut (impl io::Write + ?Sized),
918        value: &V,
919        state: &mut V::State,
920        _: PrivateMethod,
921    ) -> Result<usize, error::Format>
922    where
923        V: ComponentProvider,
924    {
925        let mut bytes = 0;
926
927        const {
928            assert!(
929                !Self::FORMAT_DATE || V::SUPPLIES_DATE,
930                "this Iso8601 configuration formats date components, but this type cannot provide \
931                 them"
932            );
933            assert!(
934                !Self::FORMAT_TIME || V::SUPPLIES_TIME,
935                "this Iso8601 configuration formats time components, but this type cannot provide \
936                 them"
937            );
938            assert!(
939                !Self::FORMAT_OFFSET || V::SUPPLIES_OFFSET,
940                "this Iso8601 configuration formats offset components, but this type cannot \
941                 provide them"
942            );
943            assert!(
944                Self::FORMAT_DATE || Self::FORMAT_TIME || Self::FORMAT_OFFSET,
945                "this Iso8601 configuration does not format any components"
946            );
947        }
948
949        if Self::FORMAT_DATE {
950            bytes += try_likely_ok!(iso8601::format_date::<_, CONFIG>(output, value, state));
951        }
952        if Self::FORMAT_TIME {
953            bytes += try_likely_ok!(iso8601::format_time::<_, CONFIG>(output, value, state));
954        }
955        if Self::FORMAT_OFFSET {
956            bytes += try_likely_ok!(iso8601::format_offset::<_, CONFIG>(output, value, state));
957        }
958
959        Ok(bytes)
960    }
961}