Skip to main content

time/formatting/
metadata.rs

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