Skip to main content

time/formatting/
metadata.rs

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