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::format_description::well_known::iso8601::EncodedConfig;
11use crate::format_description::well_known::{Iso8601, Rfc2822, Rfc3339};
12use crate::format_description::{BorrowedFormatItem, OwnedFormatItem};
13use crate::formatting::{
14    format_component, format_number_pad_zero, iso8601, write, MONTH_NAMES, WEEKDAY_NAMES,
15};
16use crate::{error, Date, Time, UtcOffset};
17
18/// A type that describes a format.
19///
20/// Implementors of [`Formattable`] are [format descriptions](crate::format_description).
21///
22/// [`Date::format`] and [`Time::format`] each use a format description to generate
23/// a String from their data. See the respective methods for usage examples.
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: Deref> Formattable for T where T::Target: Formattable {}
34
35/// Seal the trait to prevent downstream users from implementing it.
36mod sealed {
37    use super::*;
38
39    /// Format the item using a format description, the intended output, and the various components.
40    pub trait Sealed {
41        /// Format the item into the provided output, returning the number of bytes written.
42        fn format_into(
43            &self,
44            output: &mut (impl io::Write + ?Sized),
45            date: Option<Date>,
46            time: Option<Time>,
47            offset: Option<UtcOffset>,
48        ) -> Result<usize, error::Format>;
49
50        /// Format the item directly to a `String`.
51        #[inline]
52        fn format(
53            &self,
54            date: Option<Date>,
55            time: Option<Time>,
56            offset: Option<UtcOffset>,
57        ) -> Result<String, error::Format> {
58            let mut buf = Vec::new();
59            self.format_into(&mut buf, date, time, offset)?;
60            Ok(String::from_utf8_lossy(&buf).into_owned())
61        }
62    }
63}
64
65impl sealed::Sealed for BorrowedFormatItem<'_> {
66    #[inline]
67    fn format_into(
68        &self,
69        output: &mut (impl io::Write + ?Sized),
70        date: Option<Date>,
71        time: Option<Time>,
72        offset: Option<UtcOffset>,
73    ) -> Result<usize, error::Format> {
74        Ok(match *self {
75            Self::Literal(literal) => write(output, literal)?,
76            Self::Component(component) => format_component(output, component, date, time, offset)?,
77            Self::Compound(items) => items.format_into(output, date, time, offset)?,
78            Self::Optional(item) => item.format_into(output, date, time, offset)?,
79            Self::First(items) => match items {
80                [] => 0,
81                [item, ..] => item.format_into(output, date, time, offset)?,
82            },
83        })
84    }
85}
86
87impl sealed::Sealed for [BorrowedFormatItem<'_>] {
88    #[inline]
89    fn format_into(
90        &self,
91        output: &mut (impl io::Write + ?Sized),
92        date: Option<Date>,
93        time: Option<Time>,
94        offset: Option<UtcOffset>,
95    ) -> Result<usize, error::Format> {
96        let mut bytes = 0;
97        for item in self.iter() {
98            bytes += item.format_into(output, date, time, offset)?;
99        }
100        Ok(bytes)
101    }
102}
103
104impl sealed::Sealed for OwnedFormatItem {
105    #[inline]
106    fn format_into(
107        &self,
108        output: &mut (impl io::Write + ?Sized),
109        date: Option<Date>,
110        time: Option<Time>,
111        offset: Option<UtcOffset>,
112    ) -> Result<usize, error::Format> {
113        match self {
114            Self::Literal(literal) => Ok(write(output, literal)?),
115            Self::Component(component) => format_component(output, *component, date, time, offset),
116            Self::Compound(items) => items.format_into(output, date, time, offset),
117            Self::Optional(item) => item.format_into(output, date, time, offset),
118            Self::First(items) => match &**items {
119                [] => Ok(0),
120                [item, ..] => item.format_into(output, date, time, offset),
121            },
122        }
123    }
124}
125
126impl sealed::Sealed for [OwnedFormatItem] {
127    #[inline]
128    fn format_into(
129        &self,
130        output: &mut (impl io::Write + ?Sized),
131        date: Option<Date>,
132        time: Option<Time>,
133        offset: Option<UtcOffset>,
134    ) -> Result<usize, error::Format> {
135        let mut bytes = 0;
136        for item in self.iter() {
137            bytes += item.format_into(output, date, time, offset)?;
138        }
139        Ok(bytes)
140    }
141}
142
143impl<T> sealed::Sealed for T
144where
145    T: Deref<Target: sealed::Sealed>,
146{
147    #[inline]
148    fn format_into(
149        &self,
150        output: &mut (impl io::Write + ?Sized),
151        date: Option<Date>,
152        time: Option<Time>,
153        offset: Option<UtcOffset>,
154    ) -> Result<usize, error::Format> {
155        self.deref().format_into(output, date, time, offset)
156    }
157}
158
159impl sealed::Sealed for Rfc2822 {
160    fn format_into(
161        &self,
162        output: &mut (impl io::Write + ?Sized),
163        date: Option<Date>,
164        time: Option<Time>,
165        offset: Option<UtcOffset>,
166    ) -> Result<usize, error::Format> {
167        let date = date.ok_or(error::Format::InsufficientTypeInformation)?;
168        let time = time.ok_or(error::Format::InsufficientTypeInformation)?;
169        let offset = offset.ok_or(error::Format::InsufficientTypeInformation)?;
170
171        let mut bytes = 0;
172
173        let (year, month, day) = date.to_calendar_date();
174
175        if year < 1900 {
176            return Err(error::Format::InvalidComponent("year"));
177        }
178        if offset.seconds_past_minute() != 0 {
179            return Err(error::Format::InvalidComponent("offset_second"));
180        }
181
182        bytes += write(
183            output,
184            &WEEKDAY_NAMES[date.weekday().number_days_from_monday().extend::<usize>()][..3],
185        )?;
186        bytes += write(output, b", ")?;
187        bytes += format_number_pad_zero::<2>(output, day)?;
188        bytes += write(output, b" ")?;
189        bytes += write(
190            output,
191            &MONTH_NAMES[u8::from(month).extend::<usize>() - 1][..3],
192        )?;
193        bytes += write(output, b" ")?;
194        bytes += format_number_pad_zero::<4>(output, year.cast_unsigned())?;
195        bytes += write(output, b" ")?;
196        bytes += format_number_pad_zero::<2>(output, time.hour())?;
197        bytes += write(output, b":")?;
198        bytes += format_number_pad_zero::<2>(output, time.minute())?;
199        bytes += write(output, b":")?;
200        bytes += format_number_pad_zero::<2>(output, time.second())?;
201        bytes += write(output, b" ")?;
202        bytes += write(output, if offset.is_negative() { b"-" } else { b"+" })?;
203        bytes += format_number_pad_zero::<2>(output, offset.whole_hours().unsigned_abs())?;
204        bytes += format_number_pad_zero::<2>(output, offset.minutes_past_hour().unsigned_abs())?;
205
206        Ok(bytes)
207    }
208}
209
210impl sealed::Sealed for Rfc3339 {
211    fn format_into(
212        &self,
213        output: &mut (impl io::Write + ?Sized),
214        date: Option<Date>,
215        time: Option<Time>,
216        offset: Option<UtcOffset>,
217    ) -> Result<usize, error::Format> {
218        let date = date.ok_or(error::Format::InsufficientTypeInformation)?;
219        let time = time.ok_or(error::Format::InsufficientTypeInformation)?;
220        let offset = offset.ok_or(error::Format::InsufficientTypeInformation)?;
221
222        let mut bytes = 0;
223
224        let year = date.year();
225
226        if !(0..10_000).contains(&year) {
227            return Err(error::Format::InvalidComponent("year"));
228        }
229        if offset.whole_hours().unsigned_abs() > 23 {
230            return Err(error::Format::InvalidComponent("offset_hour"));
231        }
232        if offset.seconds_past_minute() != 0 {
233            return Err(error::Format::InvalidComponent("offset_second"));
234        }
235
236        bytes += format_number_pad_zero::<4>(output, year.cast_unsigned())?;
237        bytes += write(output, b"-")?;
238        bytes += format_number_pad_zero::<2>(output, u8::from(date.month()))?;
239        bytes += write(output, b"-")?;
240        bytes += format_number_pad_zero::<2>(output, date.day())?;
241        bytes += write(output, b"T")?;
242        bytes += format_number_pad_zero::<2>(output, time.hour())?;
243        bytes += write(output, b":")?;
244        bytes += format_number_pad_zero::<2>(output, time.minute())?;
245        bytes += write(output, b":")?;
246        bytes += format_number_pad_zero::<2>(output, time.second())?;
247
248        if time.nanosecond() != 0 {
249            let nanos = time.nanosecond();
250            bytes += write(output, b".")?;
251            bytes += if nanos % 10 != 0 {
252                format_number_pad_zero::<9>(output, nanos)
253            } else if (nanos / 10) % 10 != 0 {
254                format_number_pad_zero::<8>(output, nanos / 10)
255            } else if (nanos / 100) % 10 != 0 {
256                format_number_pad_zero::<7>(output, nanos / 100)
257            } else if (nanos / 1_000) % 10 != 0 {
258                format_number_pad_zero::<6>(output, nanos / 1_000)
259            } else if (nanos / 10_000) % 10 != 0 {
260                format_number_pad_zero::<5>(output, nanos / 10_000)
261            } else if (nanos / 100_000) % 10 != 0 {
262                format_number_pad_zero::<4>(output, nanos / 100_000)
263            } else if (nanos / 1_000_000) % 10 != 0 {
264                format_number_pad_zero::<3>(output, nanos / 1_000_000)
265            } else if (nanos / 10_000_000) % 10 != 0 {
266                format_number_pad_zero::<2>(output, nanos / 10_000_000)
267            } else {
268                format_number_pad_zero::<1>(output, nanos / 100_000_000)
269            }?;
270        }
271
272        if offset == UtcOffset::UTC {
273            bytes += write(output, b"Z")?;
274            return Ok(bytes);
275        }
276
277        bytes += write(output, if offset.is_negative() { b"-" } else { b"+" })?;
278        bytes += format_number_pad_zero::<2>(output, offset.whole_hours().unsigned_abs())?;
279        bytes += write(output, b":")?;
280        bytes += format_number_pad_zero::<2>(output, offset.minutes_past_hour().unsigned_abs())?;
281
282        Ok(bytes)
283    }
284}
285
286impl<const CONFIG: EncodedConfig> sealed::Sealed for Iso8601<CONFIG> {
287    #[inline]
288    fn format_into(
289        &self,
290        output: &mut (impl io::Write + ?Sized),
291        date: Option<Date>,
292        time: Option<Time>,
293        offset: Option<UtcOffset>,
294    ) -> Result<usize, error::Format> {
295        let mut bytes = 0;
296
297        if Self::FORMAT_DATE {
298            let date = date.ok_or(error::Format::InsufficientTypeInformation)?;
299            bytes += iso8601::format_date::<CONFIG>(output, date)?;
300        }
301        if Self::FORMAT_TIME {
302            let time = time.ok_or(error::Format::InsufficientTypeInformation)?;
303            bytes += iso8601::format_time::<CONFIG>(output, time)?;
304        }
305        if Self::FORMAT_OFFSET {
306            let offset = offset.ok_or(error::Format::InsufficientTypeInformation)?;
307            bytes += iso8601::format_offset::<CONFIG>(output, offset)?;
308        }
309
310        if bytes == 0 {
311            // The only reason there would be no bytes written is if the format was only for
312            // parsing.
313            panic!("attempted to format a parsing-only format description");
314        }
315
316        Ok(bytes)
317    }
318}