time_fmt/parse/
time_format_item.rs

1use std::slice::SliceIndex;
2
3use thiserror::Error;
4use time::format_description::{modifier, Component, FormatItem};
5
6use super::desc_parser::Collector;
7
8#[derive(Error, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
9#[non_exhaustive]
10pub enum Error {
11    #[error("Unknown specifier `%{0}`")]
12    UnknownSpecifier(char),
13    #[error("No FormatItem that represent {0}")]
14    NoCorrespondingFormatItem(&'static str),
15}
16
17struct ToFormatItemCollector<'a> {
18    fmt: &'a [u8],
19    items: Vec<FormatItem<'a>>,
20}
21
22impl<'a> ToFormatItemCollector<'a> {
23    fn new(fmt: &'a [u8]) -> Self {
24        Self {
25            fmt,
26            items: Default::default(),
27        }
28    }
29}
30
31macro_rules! all_paddings {
32    ($ret: expr, $create_base: expr, $component_builder: expr) => {
33        const fn with_padding(pad: modifier::Padding) -> Component {
34            let mut m = $create_base;
35            m.padding = pad;
36            $component_builder(m)
37        }
38        static ITEMS: [FormatItem; 3] = [
39            FormatItem::Component(with_padding(modifier::Padding::Zero)),
40            FormatItem::Component(with_padding(modifier::Padding::Space)),
41            FormatItem::Component(with_padding(modifier::Padding::None)),
42        ];
43        $ret.push(FormatItem::First(&ITEMS));
44    };
45}
46
47impl<'a> Collector for ToFormatItemCollector<'a> {
48    type Output = Vec<FormatItem<'a>>;
49    type Error = Error;
50
51    #[inline]
52    fn spaces(&mut self) -> Result<(), Self::Error> {
53        // TODO: How to allow more than one?
54        self.items.push(FormatItem::Optional(&FormatItem::First(&[
55            FormatItem::Literal(b" "),
56            FormatItem::Literal(b"\n"),
57            FormatItem::Literal(b"\t"),
58        ])));
59        Ok(())
60    }
61
62    #[inline]
63    fn day_of_week_name(&mut self) -> Result<(), Self::Error> {
64        const fn short() -> FormatItem<'static> {
65            let mut short = modifier::Weekday::default();
66            short.repr = modifier::WeekdayRepr::Short;
67            short.case_sensitive = false;
68            FormatItem::Component(Component::Weekday(short))
69        }
70        const fn long() -> FormatItem<'static> {
71            let mut long = modifier::Weekday::default();
72            long.repr = modifier::WeekdayRepr::Long;
73            long.case_sensitive = false;
74            FormatItem::Component(Component::Weekday(long))
75        }
76        static ITEMS: [FormatItem; 2] = [long(), short()];
77        self.items.push(FormatItem::First(&ITEMS));
78        Ok(())
79    }
80
81    #[inline]
82    fn month_name(&mut self) -> Result<(), Self::Error> {
83        const fn short() -> FormatItem<'static> {
84            let mut short = modifier::Month::default();
85            short.repr = modifier::MonthRepr::Short;
86            short.case_sensitive = false;
87            FormatItem::Component(Component::Month(short))
88        }
89        const fn long() -> FormatItem<'static> {
90            let mut long = modifier::Month::default();
91            long.repr = modifier::MonthRepr::Long;
92            long.case_sensitive = false;
93            FormatItem::Component(Component::Month(long))
94        }
95        static ITEMS: [FormatItem; 2] = [long(), short()];
96        self.items.push(FormatItem::First(&ITEMS));
97        Ok(())
98    }
99
100    #[inline]
101    fn year_prefix(&mut self) -> Result<(), Self::Error> {
102        Err(Self::Error::NoCorrespondingFormatItem("%C"))
103    }
104
105    #[inline]
106    fn day_of_month(&mut self) -> Result<(), Self::Error> {
107        all_paddings!(self.items, modifier::Day::default(), Component::Day);
108        Ok(())
109    }
110
111    #[inline]
112    fn hour_of_day(&mut self) -> Result<(), Self::Error> {
113        all_paddings!(self.items, modifier::Hour::default(), Component::Hour);
114        Ok(())
115    }
116
117    #[inline]
118    fn hour_of_day_12(&mut self) -> Result<(), Self::Error> {
119        all_paddings!(
120            self.items,
121            {
122                let mut base = modifier::Hour::default();
123                base.is_12_hour_clock = true;
124                base
125            },
126            Component::Hour
127        );
128        Ok(())
129    }
130
131    #[inline]
132    fn day_of_year(&mut self) -> Result<(), Self::Error> {
133        all_paddings!(self.items, modifier::Ordinal::default(), Component::Ordinal);
134        Ok(())
135    }
136
137    #[inline]
138    fn month_of_year(&mut self) -> Result<(), Self::Error> {
139        all_paddings!(self.items, modifier::Month::default(), Component::Month);
140        Ok(())
141    }
142
143    #[inline]
144    fn minute_of_hour(&mut self) -> Result<(), Self::Error> {
145        all_paddings!(self.items, modifier::Minute::default(), Component::Minute);
146        Ok(())
147    }
148
149    #[inline]
150    fn ampm(&mut self) -> Result<(), Self::Error> {
151        let mut modifier = modifier::Period::default();
152        modifier.case_sensitive = false;
153        self.items
154            .push(FormatItem::Component(Component::Period(modifier)));
155        Ok(())
156    }
157
158    #[inline]
159    fn second_of_minute(&mut self) -> Result<(), Self::Error> {
160        all_paddings!(self.items, modifier::Second::default(), Component::Second);
161        Ok(())
162    }
163
164    #[inline]
165    fn nanosecond_of_second(&mut self) -> Result<(), Self::Error> {
166        let modifier = modifier::Subsecond::default();
167        self.items
168            .push(FormatItem::Component(Component::Subsecond(modifier)));
169        Ok(())
170    }
171
172    #[inline]
173    fn tab(&mut self) -> Result<(), Self::Error> {
174        self.spaces()
175    }
176
177    #[inline]
178    fn week_number_of_current_year_start_sunday(&mut self) -> Result<(), Self::Error> {
179        all_paddings!(
180            self.items,
181            {
182                let mut base = modifier::WeekNumber::default();
183                base.repr = modifier::WeekNumberRepr::Sunday;
184                base
185            },
186            Component::WeekNumber
187        );
188        Ok(())
189    }
190
191    #[inline]
192    fn day_of_week_from_sunday_as_0(&mut self) -> Result<(), Self::Error> {
193        let mut modifier = modifier::Weekday::default();
194        modifier.repr = modifier::WeekdayRepr::Sunday;
195        modifier.one_indexed = false;
196        self.items
197            .push(FormatItem::Component(Component::Weekday(modifier)));
198        Ok(())
199    }
200
201    #[inline]
202    fn week_number_of_current_year_start_monday(&mut self) -> Result<(), Self::Error> {
203        all_paddings!(
204            self.items,
205            {
206                let mut base = modifier::WeekNumber::default();
207                base.repr = modifier::WeekNumberRepr::Monday;
208                base
209            },
210            Component::WeekNumber
211        );
212        Ok(())
213    }
214
215    #[inline]
216    fn year_suffix(&mut self) -> Result<(), Self::Error> {
217        all_paddings!(
218            self.items,
219            {
220                let mut base = modifier::Year::default();
221                base.repr = modifier::YearRepr::LastTwo;
222                base
223            },
224            Component::Year
225        );
226        Ok(())
227    }
228
229    #[inline]
230    fn year(&mut self) -> Result<(), Self::Error> {
231        all_paddings!(self.items, modifier::Year::default(), Component::Year);
232        Ok(())
233    }
234
235    #[inline]
236    fn timezone(&mut self) -> Result<(), Self::Error> {
237        let mut modifier = modifier::OffsetHour::default();
238        modifier.sign_is_mandatory = true;
239        self.items
240            .push(FormatItem::Component(Component::OffsetHour(modifier)));
241        self.items
242            .push(FormatItem::Optional(&FormatItem::Literal(b":")));
243        let modifier = modifier::OffsetMinute::default();
244        self.items
245            .push(FormatItem::Component(Component::OffsetMinute(modifier)));
246        Ok(())
247    }
248
249    #[inline]
250    fn timezone_name(&mut self) -> Result<(), Self::Error> {
251        Err(Self::Error::NoCorrespondingFormatItem("timezone name"))
252    }
253
254    #[inline]
255    fn static_str(&mut self, s: &'static str) -> Result<(), Self::Error> {
256        self.items.push(FormatItem::Literal(s.as_bytes()));
257        Ok(())
258    }
259
260    #[inline]
261    fn literal(
262        &mut self,
263        _lit: &str,
264        fmt_span: impl SliceIndex<[u8], Output = [u8]>,
265    ) -> Result<(), Self::Error> {
266        self.items.push(FormatItem::Literal(&self.fmt[fmt_span]));
267        Ok(())
268    }
269
270    #[inline]
271    fn unknown(&mut self, specifier: char) -> Result<(), Self::Error> {
272        Err(Self::Error::UnknownSpecifier(specifier))
273    }
274
275    #[inline]
276    fn unconsumed_input(&self) -> Result<(), Self::Error> {
277        Ok(())
278    }
279
280    #[inline]
281    fn output(self) -> Result<Self::Output, Self::Error> {
282        Ok(self.items)
283    }
284}
285
286pub fn parse_to_format_item(fmt: &str) -> Result<Vec<FormatItem>, Error> {
287    let collector = ToFormatItemCollector::new(fmt.as_bytes());
288    super::desc_parser::parse_format_specifications(fmt, collector, false)
289}
290
291#[cfg(test)]
292mod tests {
293    use time::{macros::datetime, OffsetDateTime, PrimitiveDateTime};
294
295    use super::parse_to_format_item;
296
297    #[test]
298    fn it_works() -> Result<(), super::Error> {
299        assert_eq!(
300            parse_to_format_item("%Y-%m-%d")?,
301            parse_to_format_item("%F")?,
302        );
303        Ok(())
304    }
305
306    #[test]
307    fn parse_primitive_datetime() -> Result<(), Box<dyn std::error::Error>> {
308        let format_items = parse_to_format_item("%Y-%m-%d %H:%M:%S")?;
309        assert_eq!(
310            PrimitiveDateTime::parse("2012-05-21 12:09:14", &format_items)?,
311            datetime!(2012-05-21 12:09:14)
312        );
313        Ok(())
314    }
315
316    #[test]
317    fn parse_offset_datetime() -> Result<(), Box<dyn std::error::Error>> {
318        let format_items = parse_to_format_item("%Y-%m-%d %H:%M:%S %z")?;
319        assert_eq!(
320            OffsetDateTime::parse("2012-05-21 12:09:14 +0900", &format_items)?,
321            datetime!(2012-05-21 12:09:14 +9:00)
322        );
323        assert_eq!(
324            OffsetDateTime::parse("2012-05-21 12:09:14 +09:00", &format_items)?,
325            datetime!(2012-05-21 12:09:14 +9:00)
326        );
327        Ok(())
328    }
329}