Skip to main content

time/formatting/
metadata.rs

1use core::iter::Sum;
2use core::ops::{Add, Deref};
3
4use crate::format_description::well_known::iso8601::EncodedConfig;
5use crate::format_description::well_known::{Iso8601, Rfc2822, Rfc3339};
6use crate::format_description::{BorrowedFormatItem, Component, OwnedFormatItem, modifier};
7
8/// Metadata about a format description.
9#[derive(Debug)]
10pub(crate) struct Metadata {
11    /// The maximum number of bytes needed for the provided format description.
12    ///
13    /// The number of bytes written should never exceed this value, but it may be less. This is
14    /// used to pre-allocate a buffer of the appropriate size for formatting.
15    pub(crate) max_bytes_needed: usize,
16    /// Whether the output of the provided format description is guaranteed to be valid UTF-8.
17    ///
18    /// This is used to determine whether the output can be soundly converted to a `String` without
19    /// checking for UTF-8 validity.
20    pub(crate) guaranteed_utf8: bool,
21}
22
23impl Default for Metadata {
24    #[inline]
25    fn default() -> Self {
26        Self {
27            max_bytes_needed: 0,
28            guaranteed_utf8: true,
29        }
30    }
31}
32
33impl Add for Metadata {
34    type Output = Self;
35
36    #[inline]
37    fn add(self, rhs: Self) -> Self::Output {
38        Self {
39            max_bytes_needed: self.max_bytes_needed + rhs.max_bytes_needed,
40            guaranteed_utf8: self.guaranteed_utf8 && rhs.guaranteed_utf8,
41        }
42    }
43}
44
45impl Sum for Metadata {
46    #[inline]
47    fn sum<I>(iter: I) -> Self
48    where
49        I: Iterator<Item = Self>,
50    {
51        iter.fold(Self::default(), Self::add)
52    }
53}
54
55/// A trait for computing metadata about a format description.
56pub(crate) trait ComputeMetadata {
57    /// Compute the metadata for a format description.
58    fn compute_metadata(&self) -> Metadata;
59}
60
61impl ComputeMetadata for Rfc2822 {
62    #[inline]
63    fn compute_metadata(&self) -> Metadata {
64        Metadata {
65            max_bytes_needed: 31,
66            guaranteed_utf8: true,
67        }
68    }
69}
70
71impl ComputeMetadata for Rfc3339 {
72    #[inline]
73    fn compute_metadata(&self) -> Metadata {
74        Metadata {
75            max_bytes_needed: 35,
76            guaranteed_utf8: true,
77        }
78    }
79}
80
81impl<const CONFIG: EncodedConfig> ComputeMetadata for Iso8601<CONFIG> {
82    #[inline]
83    fn compute_metadata(&self) -> Metadata {
84        const {
85            use crate::format_description::well_known::iso8601::{
86                DateKind, OffsetPrecision, TimePrecision,
87            };
88
89            let date_width = if Self::FORMAT_DATE {
90                let year_width = if Self::YEAR_IS_SIX_DIGITS {
91                    7 // sign + 6 digits
92                } else {
93                    4 // sign is not present when the year is four digits
94                };
95                let num_dashes = match Self::DATE_KIND {
96                    DateKind::Calendar if Self::USE_SEPARATORS => 2,
97                    DateKind::Week | DateKind::Ordinal if Self::USE_SEPARATORS => 1,
98                    DateKind::Calendar | DateKind::Week | DateKind::Ordinal => 0,
99                };
100                let part_of_year_width = match Self::DATE_KIND {
101                    DateKind::Calendar => 4,
102                    DateKind::Week => 4,
103                    DateKind::Ordinal => 3,
104                };
105
106                year_width + num_dashes + part_of_year_width
107            } else {
108                0
109            };
110
111            let time_width = if Self::FORMAT_TIME {
112                let t_separator = (Self::USE_SEPARATORS || Self::FORMAT_DATE) as usize;
113                let num_colons = match Self::TIME_PRECISION {
114                    TimePrecision::Minute { .. } if Self::USE_SEPARATORS => 1,
115                    TimePrecision::Second { .. } if Self::USE_SEPARATORS => 2,
116                    TimePrecision::Hour { .. }
117                    | TimePrecision::Minute { .. }
118                    | TimePrecision::Second { .. } => 0,
119                };
120                let pre_decimal_digits = match Self::TIME_PRECISION {
121                    TimePrecision::Hour { .. } => 2,
122                    TimePrecision::Minute { .. } => 4,
123                    TimePrecision::Second { .. } => 6,
124                };
125                let fractional_bytes = match Self::TIME_PRECISION {
126                    TimePrecision::Hour { decimal_digits }
127                    | TimePrecision::Minute { decimal_digits }
128                    | TimePrecision::Second { decimal_digits } => {
129                        if let Some(digits) = decimal_digits {
130                            // add one for decimal point
131                            1 + digits.get() as usize
132                        } else {
133                            0
134                        }
135                    }
136                };
137
138                t_separator + num_colons + pre_decimal_digits + fractional_bytes
139            } else {
140                0
141            };
142
143            let offset_width = if Self::FORMAT_OFFSET {
144                match Self::OFFSET_PRECISION {
145                    OffsetPrecision::Hour => 3,
146                    OffsetPrecision::Minute if Self::USE_SEPARATORS => 6,
147                    OffsetPrecision::Minute => 5,
148                }
149            } else {
150                0
151            };
152
153            Metadata {
154                max_bytes_needed: date_width + time_width + offset_width,
155                guaranteed_utf8: true,
156            }
157        }
158    }
159}
160
161impl ComputeMetadata for BorrowedFormatItem<'_> {
162    #[inline]
163    fn compute_metadata(&self) -> Metadata {
164        match self {
165            #[expect(deprecated)]
166            Self::Literal(bytes) => Metadata {
167                max_bytes_needed: bytes.len(),
168                guaranteed_utf8: false,
169            },
170            Self::StringLiteral(s) => Metadata {
171                max_bytes_needed: s.len(),
172                guaranteed_utf8: true,
173            },
174            Self::Component(component) => component.compute_metadata(),
175            Self::Compound(borrowed_format_items) => borrowed_format_items.compute_metadata(),
176            Self::Optional(borrowed_format_item) => borrowed_format_item.compute_metadata(),
177            Self::First(borrowed_format_items) => borrowed_format_items
178                .first()
179                .map_or_else(Metadata::default, ComputeMetadata::compute_metadata),
180        }
181    }
182}
183
184impl ComputeMetadata for OwnedFormatItem {
185    #[inline]
186    fn compute_metadata(&self) -> Metadata {
187        match self {
188            #[expect(deprecated)]
189            Self::Literal(bytes) => Metadata {
190                max_bytes_needed: bytes.len(),
191                guaranteed_utf8: false,
192            },
193            Self::StringLiteral(s) => Metadata {
194                max_bytes_needed: s.len(),
195                guaranteed_utf8: true,
196            },
197            Self::Component(component) => component.compute_metadata(),
198            Self::Compound(owned_format_items) => owned_format_items.compute_metadata(),
199            Self::Optional(owned_format_item) => owned_format_item.compute_metadata(),
200            Self::First(owned_format_items) => owned_format_items
201                .first()
202                .map_or_else(Metadata::default, ComputeMetadata::compute_metadata),
203        }
204    }
205}
206
207impl ComputeMetadata for Component {
208    #[inline]
209    fn compute_metadata(&self) -> Metadata {
210        let max_bytes_needed = match self {
211            Self::Day(_) => 2,
212            Self::MonthShort(_) => 3,
213            Self::MonthLong(_) => 9,
214            Self::MonthNumerical(_) => 2,
215            Self::Ordinal(_) => 3,
216            Self::WeekdayShort(_) => 3,
217            Self::WeekdayLong(_) => 9,
218            Self::WeekdaySunday(_) | Self::WeekdayMonday(_) => 1,
219            Self::WeekNumberIso(_) | Self::WeekNumberSunday(_) | Self::WeekNumberMonday(_) => 2,
220            Self::CalendarYearFullExtendedRange(_) => 7,
221            Self::CalendarYearFullStandardRange(_) => 5,
222            Self::IsoYearFullExtendedRange(_) => 7,
223            Self::IsoYearFullStandardRange(_) => 5,
224            Self::CalendarYearCenturyExtendedRange(_) => 5,
225            Self::CalendarYearCenturyStandardRange(_) => 3,
226            Self::IsoYearCenturyExtendedRange(_) => 5,
227            Self::IsoYearCenturyStandardRange(_) => 3,
228            Self::CalendarYearLastTwo(_) => 2,
229            Self::IsoYearLastTwo(_) => 2,
230            Self::Hour12(_) | Self::Hour24(_) => 2,
231            Self::Minute(_) | Self::Period(_) | Self::Second(_) => 2,
232            Self::Subsecond(modifier) => match modifier.digits {
233                modifier::SubsecondDigits::One => 1,
234                modifier::SubsecondDigits::Two => 2,
235                modifier::SubsecondDigits::Three => 3,
236                modifier::SubsecondDigits::Four => 4,
237                modifier::SubsecondDigits::Five => 5,
238                modifier::SubsecondDigits::Six => 6,
239                modifier::SubsecondDigits::Seven => 7,
240                modifier::SubsecondDigits::Eight => 8,
241                modifier::SubsecondDigits::Nine => 9,
242                modifier::SubsecondDigits::OneOrMore => 9,
243            },
244            Self::OffsetHour(_) => 3,
245            Self::OffsetMinute(_) | Self::OffsetSecond(_) => 2,
246            #[cfg(feature = "large-dates")]
247            Self::UnixTimestampSecond(_) => 15,
248            #[cfg(not(feature = "large-dates"))]
249            Self::UnixTimestampSecond(_) => 13,
250            #[cfg(feature = "large-dates")]
251            Self::UnixTimestampMillisecond(_) => 18,
252            #[cfg(not(feature = "large-dates"))]
253            Self::UnixTimestampMillisecond(_) => 16,
254            #[cfg(feature = "large-dates")]
255            Self::UnixTimestampMicrosecond(_) => 21,
256            #[cfg(not(feature = "large-dates"))]
257            Self::UnixTimestampMicrosecond(_) => 19,
258            #[cfg(feature = "large-dates")]
259            Self::UnixTimestampNanosecond(_) => 24,
260            #[cfg(not(feature = "large-dates"))]
261            Self::UnixTimestampNanosecond(_) => 22,
262            Self::Ignore(_) | Self::End(_) => 0,
263
264            // Start of deprecated components that are no longer emitted by macros or parsers.
265            #[expect(deprecated)]
266            Self::Month(modifier) => match modifier.repr {
267                modifier::MonthRepr::Numerical => 2,
268                modifier::MonthRepr::Long => 9,
269                modifier::MonthRepr::Short => 3,
270            },
271            #[expect(deprecated)]
272            Self::Weekday(modifier) => match modifier.repr {
273                modifier::WeekdayRepr::Short => 3,
274                modifier::WeekdayRepr::Long => 9,
275                modifier::WeekdayRepr::Sunday | modifier::WeekdayRepr::Monday => 1,
276            },
277            #[expect(deprecated)]
278            Self::WeekNumber(_) => 2,
279            #[expect(deprecated)]
280            Self::Hour(_) => 2,
281            #[cfg(feature = "large-dates")]
282            #[expect(deprecated)]
283            Self::UnixTimestamp(modifier) => match modifier.precision {
284                modifier::UnixTimestampPrecision::Second => 15,
285                modifier::UnixTimestampPrecision::Millisecond => 18,
286                modifier::UnixTimestampPrecision::Microsecond => 21,
287                modifier::UnixTimestampPrecision::Nanosecond => 24,
288            },
289            #[cfg(not(feature = "large-dates"))]
290            #[expect(deprecated)]
291            Self::UnixTimestamp(modifier) => match modifier.precision {
292                modifier::UnixTimestampPrecision::Second => 13,
293                modifier::UnixTimestampPrecision::Millisecond => 16,
294                modifier::UnixTimestampPrecision::Microsecond => 19,
295                modifier::UnixTimestampPrecision::Nanosecond => 22,
296            },
297            #[cfg(feature = "large-dates")]
298            #[expect(deprecated)]
299            Self::Year(modifier) => match modifier.repr {
300                modifier::YearRepr::Full => 7,
301                modifier::YearRepr::Century => 5,
302                modifier::YearRepr::LastTwo => 2,
303            },
304            #[cfg(not(feature = "large-dates"))]
305            #[expect(deprecated)]
306            Self::Year(modifier) => match modifier.repr {
307                modifier::YearRepr::Full => 5,
308                modifier::YearRepr::Century => 3,
309                modifier::YearRepr::LastTwo => 2,
310            },
311        };
312
313        Metadata {
314            max_bytes_needed,
315            guaranteed_utf8: true,
316        }
317    }
318}
319
320impl<T> ComputeMetadata for [T]
321where
322    T: ComputeMetadata,
323{
324    #[inline]
325    fn compute_metadata(&self) -> Metadata {
326        self.iter().map(ComputeMetadata::compute_metadata).sum()
327    }
328}
329
330impl<T> ComputeMetadata for T
331where
332    T: Deref<Target: ComputeMetadata>,
333{
334    #[inline]
335    fn compute_metadata(&self) -> Metadata {
336        self.deref().compute_metadata()
337    }
338}