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 num_conv::prelude::*;
9
10use crate::error;
11use crate::format_description::well_known::iso8601::EncodedConfig;
12use crate::format_description::well_known::{Iso8601, Rfc2822, Rfc3339};
13use crate::format_description::{BorrowedFormatItem, OwnedFormatItem};
14use crate::formatting::{
15    ComponentProvider, MONTH_NAMES, WEEKDAY_NAMES, format_component, format_number_pad_zero,
16    iso8601, write, write_if_else,
17};
18
19/// A type that describes a format.
20///
21/// Implementors of [`Formattable`] are [format descriptions](crate::format_description).
22///
23/// [`Date::format`] and [`Time::format`] each use a format description to generate
24/// a String from their data. See the respective methods for usage examples.
25#[cfg_attr(docsrs, doc(notable_trait))]
26pub trait Formattable: sealed::Sealed {}
27impl Formattable for BorrowedFormatItem<'_> {}
28impl Formattable for [BorrowedFormatItem<'_>] {}
29impl Formattable for OwnedFormatItem {}
30impl Formattable for [OwnedFormatItem] {}
31impl Formattable for Rfc3339 {}
32impl Formattable for Rfc2822 {}
33impl<const CONFIG: EncodedConfig> Formattable for Iso8601<CONFIG> {}
34impl<T> Formattable for T where T: Deref<Target: Formattable> {}
35
36/// Seal the trait to prevent downstream users from implementing it.
37mod sealed {
38    use super::*;
39    use crate::formatting::ComponentProvider;
40
41    /// Format the item using a format description, the intended output, and the various components.
42    #[expect(
43        private_bounds,
44        private_interfaces,
45        reason = "irrelevant due to being a sealed trait"
46    )]
47    pub trait Sealed {
48        /// Format the item into the provided output, returning the number of bytes written.
49        fn format_into<V>(
50            &self,
51            output: &mut (impl io::Write + ?Sized),
52            value: &V,
53            state: &mut V::State,
54        ) -> Result<usize, error::Format>
55        where
56            V: ComponentProvider;
57
58        /// Format the item directly to a `String`.
59        #[inline]
60        fn format<V>(&self, value: &V, state: &mut V::State) -> Result<String, error::Format>
61        where
62            V: ComponentProvider,
63        {
64            let mut buf = Vec::new();
65            self.format_into(&mut buf, value, state)?;
66            Ok(String::from_utf8_lossy(&buf).into_owned())
67        }
68    }
69}
70
71impl sealed::Sealed for BorrowedFormatItem<'_> {
72    #[expect(
73        private_bounds,
74        private_interfaces,
75        reason = "irrelevant due to being a sealed trait"
76    )]
77    #[inline]
78    fn format_into<V>(
79        &self,
80        output: &mut (impl io::Write + ?Sized),
81        value: &V,
82        state: &mut V::State,
83    ) -> Result<usize, error::Format>
84    where
85        V: ComponentProvider,
86    {
87        Ok(match *self {
88            Self::Literal(literal) => write(output, literal)?,
89            Self::Component(component) => format_component(output, component, value, state)?,
90            Self::Compound(items) => (*items).format_into(output, value, state)?,
91            Self::Optional(item) => (*item).format_into(output, value, state)?,
92            Self::First(items) => match items {
93                [] => 0,
94                [item, ..] => (*item).format_into(output, value, state)?,
95            },
96        })
97    }
98}
99
100impl sealed::Sealed for [BorrowedFormatItem<'_>] {
101    #[expect(
102        private_bounds,
103        private_interfaces,
104        reason = "irrelevant due to being a sealed trait"
105    )]
106    #[inline]
107    fn format_into<V>(
108        &self,
109        output: &mut (impl io::Write + ?Sized),
110        value: &V,
111        state: &mut V::State,
112    ) -> Result<usize, error::Format>
113    where
114        V: ComponentProvider,
115    {
116        let mut bytes = 0;
117        for item in self.iter() {
118            bytes += (*item).format_into(output, value, state)?;
119        }
120        Ok(bytes)
121    }
122}
123
124impl sealed::Sealed for OwnedFormatItem {
125    #[expect(
126        private_bounds,
127        private_interfaces,
128        reason = "irrelevant due to being a sealed trait"
129    )]
130    #[inline]
131    fn format_into<V>(
132        &self,
133        output: &mut (impl io::Write + ?Sized),
134        value: &V,
135        state: &mut V::State,
136    ) -> Result<usize, error::Format>
137    where
138        V: ComponentProvider,
139    {
140        match self {
141            Self::Literal(literal) => Ok(write(output, literal)?),
142            Self::Component(component) => format_component(output, *component, value, state),
143            Self::Compound(items) => (**items).format_into(output, value, state),
144            Self::Optional(item) => (**item).format_into(output, value, state),
145            Self::First(items) => match &**items {
146                [] => Ok(0),
147                [item, ..] => (*item).format_into(output, value, state),
148            },
149        }
150    }
151}
152
153impl sealed::Sealed for [OwnedFormatItem] {
154    #[expect(
155        private_bounds,
156        private_interfaces,
157        reason = "irrelevant due to being a sealed trait"
158    )]
159    #[inline]
160    fn format_into<V>(
161        &self,
162        output: &mut (impl io::Write + ?Sized),
163        value: &V,
164        state: &mut V::State,
165    ) -> Result<usize, error::Format>
166    where
167        V: ComponentProvider,
168    {
169        let mut bytes = 0;
170        for item in self.iter() {
171            bytes += item.format_into(output, value, state)?;
172        }
173        Ok(bytes)
174    }
175}
176
177impl<T> sealed::Sealed for T
178where
179    T: Deref<Target: sealed::Sealed>,
180{
181    #[expect(
182        private_bounds,
183        private_interfaces,
184        reason = "irrelevant due to being a sealed trait"
185    )]
186    #[inline]
187    fn format_into<V>(
188        &self,
189        output: &mut (impl io::Write + ?Sized),
190        value: &V,
191        state: &mut V::State,
192    ) -> Result<usize, error::Format>
193    where
194        V: ComponentProvider,
195    {
196        self.deref().format_into(output, value, state)
197    }
198}
199
200impl sealed::Sealed for Rfc2822 {
201    #[expect(
202        private_bounds,
203        private_interfaces,
204        reason = "irrelevant due to being a sealed trait"
205    )]
206    fn format_into<V>(
207        &self,
208        output: &mut (impl io::Write + ?Sized),
209        value: &V,
210        state: &mut V::State,
211    ) -> Result<usize, error::Format>
212    where
213        V: ComponentProvider,
214    {
215        const {
216            assert!(
217                V::SUPPLIES_DATE && V::SUPPLIES_TIME && V::SUPPLIES_OFFSET,
218                "Rfc2822 requires date, time, and offset components, but not all can be provided \
219                 by this type"
220            );
221        }
222
223        let mut bytes = 0;
224
225        if value.calendar_year(state) < 1900 {
226            return Err(error::Format::InvalidComponent("year"));
227        }
228        if value.offset_second(state) != 0 {
229            return Err(error::Format::InvalidComponent("offset_second"));
230        }
231
232        // Safety: All weekday names are at least 3 bytes long.
233        bytes += write(output, unsafe {
234            WEEKDAY_NAMES[value
235                .weekday(state)
236                .number_days_from_monday()
237                .extend::<usize>()]
238            .get_unchecked(..3)
239        })?;
240        bytes += write(output, b", ")?;
241        bytes += format_number_pad_zero::<2>(output, value.day(state))?;
242        bytes += write(output, b" ")?;
243        // Safety: All month names are at least 3 bytes long.
244        bytes += write(output, unsafe {
245            MONTH_NAMES[u8::from(value.month(state)).extend::<usize>() - 1].get_unchecked(..3)
246        })?;
247        bytes += write(output, b" ")?;
248        bytes += format_number_pad_zero::<4>(output, value.calendar_year(state).cast_unsigned())?;
249        bytes += write(output, b" ")?;
250        bytes += format_number_pad_zero::<2>(output, value.hour(state))?;
251        bytes += write(output, b":")?;
252        bytes += format_number_pad_zero::<2>(output, value.minute(state))?;
253        bytes += write(output, b":")?;
254        bytes += format_number_pad_zero::<2>(output, value.second(state))?;
255        bytes += write(output, b" ")?;
256        bytes += write_if_else(output, value.offset_is_negative(state), b"-", b"+")?;
257        bytes += format_number_pad_zero::<2>(output, value.offset_hour(state).unsigned_abs())?;
258        bytes += format_number_pad_zero::<2>(output, value.offset_minute(state).unsigned_abs())?;
259
260        Ok(bytes)
261    }
262}
263
264impl sealed::Sealed for Rfc3339 {
265    #[expect(
266        private_bounds,
267        private_interfaces,
268        reason = "irrelevant due to being a sealed trait"
269    )]
270    fn format_into<V>(
271        &self,
272        output: &mut (impl io::Write + ?Sized),
273        value: &V,
274        state: &mut V::State,
275    ) -> Result<usize, error::Format>
276    where
277        V: ComponentProvider,
278    {
279        const {
280            assert!(
281                V::SUPPLIES_DATE && V::SUPPLIES_TIME && V::SUPPLIES_OFFSET,
282                "Rfc3339 requires date, time, and offset components, but not all can be provided \
283                 by this type"
284            );
285        }
286
287        let offset_hour = value.offset_hour(state);
288        let mut bytes = 0;
289
290        if !(0..10_000).contains(&value.calendar_year(state)) {
291            return Err(error::Format::InvalidComponent("year"));
292        }
293        if offset_hour.unsigned_abs() > 23 {
294            return Err(error::Format::InvalidComponent("offset_hour"));
295        }
296        if value.offset_second(state) != 0 {
297            return Err(error::Format::InvalidComponent("offset_second"));
298        }
299
300        bytes += format_number_pad_zero::<4>(output, value.calendar_year(state).cast_unsigned())?;
301        bytes += write(output, b"-")?;
302        bytes += format_number_pad_zero::<2>(output, u8::from(value.month(state)))?;
303        bytes += write(output, b"-")?;
304        bytes += format_number_pad_zero::<2>(output, value.day(state))?;
305        bytes += write(output, b"T")?;
306        bytes += format_number_pad_zero::<2>(output, value.hour(state))?;
307        bytes += write(output, b":")?;
308        bytes += format_number_pad_zero::<2>(output, value.minute(state))?;
309        bytes += write(output, b":")?;
310        bytes += format_number_pad_zero::<2>(output, value.second(state))?;
311
312        let nanos = value.nanosecond(state);
313        if nanos != 0 {
314            bytes += write(output, b".")?;
315            bytes += if nanos % 10 != 0 {
316                format_number_pad_zero::<9>(output, nanos)
317            } else if (nanos / 10) % 10 != 0 {
318                format_number_pad_zero::<8>(output, nanos / 10)
319            } else if (nanos / 100) % 10 != 0 {
320                format_number_pad_zero::<7>(output, nanos / 100)
321            } else if (nanos / 1_000) % 10 != 0 {
322                format_number_pad_zero::<6>(output, nanos / 1_000)
323            } else if (nanos / 10_000) % 10 != 0 {
324                format_number_pad_zero::<5>(output, nanos / 10_000)
325            } else if (nanos / 100_000) % 10 != 0 {
326                format_number_pad_zero::<4>(output, nanos / 100_000)
327            } else if (nanos / 1_000_000) % 10 != 0 {
328                format_number_pad_zero::<3>(output, nanos / 1_000_000)
329            } else if (nanos / 10_000_000) % 10 != 0 {
330                format_number_pad_zero::<2>(output, nanos / 10_000_000)
331            } else {
332                format_number_pad_zero::<1>(output, nanos / 100_000_000)
333            }?;
334        }
335
336        if value.offset_is_utc(state) {
337            bytes += write(output, b"Z")?;
338            return Ok(bytes);
339        }
340
341        bytes += write_if_else(output, value.offset_is_negative(state), b"-", b"+")?;
342        bytes += format_number_pad_zero::<2>(output, offset_hour.unsigned_abs())?;
343        bytes += write(output, b":")?;
344        bytes += format_number_pad_zero::<2>(output, value.offset_minute(state).unsigned_abs())?;
345
346        Ok(bytes)
347    }
348}
349
350impl<const CONFIG: EncodedConfig> sealed::Sealed for Iso8601<CONFIG> {
351    #[expect(
352        private_bounds,
353        private_interfaces,
354        reason = "irrelevant due to being a sealed trait"
355    )]
356    #[inline]
357    fn format_into<V>(
358        &self,
359        output: &mut (impl io::Write + ?Sized),
360        value: &V,
361        state: &mut V::State,
362    ) -> Result<usize, error::Format>
363    where
364        V: ComponentProvider,
365    {
366        let mut bytes = 0;
367
368        const {
369            assert!(
370                !Self::FORMAT_DATE || V::SUPPLIES_DATE,
371                "this Iso8601 configuration formats date components, but this type cannot provide \
372                 them"
373            );
374            assert!(
375                !Self::FORMAT_TIME || V::SUPPLIES_TIME,
376                "this Iso8601 configuration formats time components, but this type cannot provide \
377                 them"
378            );
379            assert!(
380                !Self::FORMAT_OFFSET || V::SUPPLIES_OFFSET,
381                "this Iso8601 configuration formats offset components, but this type cannot \
382                 provide them"
383            );
384            assert!(
385                Self::FORMAT_DATE || Self::FORMAT_TIME || Self::FORMAT_OFFSET,
386                "this Iso8601 configuration does not format any components"
387            );
388        }
389
390        if Self::FORMAT_DATE {
391            bytes += iso8601::format_date::<_, CONFIG>(output, value, state)?;
392        }
393        if Self::FORMAT_TIME {
394            bytes += iso8601::format_time::<_, CONFIG>(output, value, state)?;
395        }
396        if Self::FORMAT_OFFSET {
397            bytes += iso8601::format_offset::<_, CONFIG>(output, value, state)?;
398        }
399
400        Ok(bytes)
401    }
402}