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