time_fmt/parse/
desc_parser.rs

1use std::slice::SliceIndex;
2
3/// E and O are not implemented.
4/// Those require `nl-langinfo` lookup is default-implemented as if it were a POSIX locale.
5/// If you'd want to implement it properly, it's your responsibility to recursively parse
6/// the format you get from `nl-langinfo`, and prevent infinite recursion.
7pub(crate) trait Collector {
8    type Output;
9    type Error;
10    /// Skips sequence of whitespaces.
11    fn spaces(&mut self) -> Result<(), Self::Error>;
12    /// `%a` or `%A`. `nl_langinfo`-dependent.
13    fn day_of_week_name(&mut self) -> Result<(), Self::Error>;
14    /// `%b`, `%B` or `%h`. `nl_langinfo`-dependent.
15    fn month_name(&mut self) -> Result<(), Self::Error>;
16    /// `%c`. Same as `%a %b %e %T %Y` in POSIX locale. `nl_langinfo`-dependent.
17    #[inline]
18    fn preferred_date_time(&mut self) -> Result<(), Self::Error> {
19        self.day_of_week_name()?;
20        self.spaces()?;
21        self.month_name()?;
22        self.spaces()?;
23        self.day_of_month()?;
24        self.spaces()?;
25        self.time_of_day()?;
26        self.spaces()?;
27        self.year()
28    }
29    /// `%C`. `0` to `99`.
30    fn year_prefix(&mut self) -> Result<(), Self::Error>;
31    /// `%d`, `%e`. `01` to `31`.
32    fn day_of_month(&mut self) -> Result<(), Self::Error>;
33    /// `%D`. `%m / %d / %y` (American......).
34    #[inline]
35    fn date_mmddyy_slash(&mut self) -> Result<(), Self::Error> {
36        self.month_of_year()?;
37        self.spaces()?;
38        self.static_str("/")?;
39        self.spaces()?;
40        self.day_of_month()?;
41        self.spaces()?;
42        self.static_str("/")?;
43        self.spaces()?;
44        self.year_suffix()
45    }
46    /// `%F`. `%Y-%m-%d`.
47    #[inline]
48    fn date_yyyymmdd_hyphen(&mut self) -> Result<(), Self::Error> {
49        self.year()?;
50        self.static_str("-")?;
51        self.month_of_year()?;
52        self.static_str("-")?;
53        self.day_of_month()
54    }
55    /// `%H`, `%k`. `00` to `23`.
56    fn hour_of_day(&mut self) -> Result<(), Self::Error>;
57    /// `%I`, `%l`. `01` to `12`.
58    fn hour_of_day_12(&mut self) -> Result<(), Self::Error>;
59    /// `%j`. `001` to `336`.
60    fn day_of_year(&mut self) -> Result<(), Self::Error>;
61    /// `%m`. `01` to `12`.
62    fn month_of_year(&mut self) -> Result<(), Self::Error>;
63    /// `%M`. `00` to `59`.
64    fn minute_of_hour(&mut self) -> Result<(), Self::Error>;
65    /// `%n`.
66    #[inline]
67    fn new_line(&mut self) -> Result<(), Self::Error> {
68        self.spaces()
69    }
70    /// `%p`, `%P`. `AM` or `PM`. `nl_langinfo`-dependent.
71    fn ampm(&mut self) -> Result<(), Self::Error>;
72    /// `%r`. Same as `%I : %M : %S %p` in POSIX locale. `nl_langinfo`-dependent.
73    #[inline]
74    fn time_ampm(&mut self) -> Result<(), Self::Error> {
75        self.hour_of_day_12()?;
76        self.spaces()?;
77        self.static_str(":")?;
78        self.spaces()?;
79        self.minute_of_hour()?;
80        self.spaces()?;
81        self.static_str(":")?;
82        self.spaces()?;
83        self.second_of_minute()?;
84        self.spaces()?;
85        self.ampm()
86    }
87    /// `%R`. Same as `%H : %M`.
88    #[inline]
89    fn hour_minute_of_day(&mut self) -> Result<(), Self::Error> {
90        self.hour_of_day()?;
91        self.spaces()?;
92        self.static_str(":")?;
93        self.spaces()?;
94        self.minute_of_hour()
95    }
96    /// `%S`. `00` to `60`.
97    fn second_of_minute(&mut self) -> Result<(), Self::Error>;
98    /// `%f`. `000000000` to `999999999`.
99    fn nanosecond_of_second(&mut self) -> Result<(), Self::Error>;
100    /// `%t`.
101    #[inline]
102    fn tab(&mut self) -> Result<(), Self::Error> {
103        self.spaces()
104    }
105    /// `%T`. Same as `%H : %M : %S`.
106    #[inline]
107    fn time_of_day(&mut self) -> Result<(), Self::Error> {
108        self.hour_of_day()?;
109        self.spaces()?;
110        self.static_str(":")?;
111        self.spaces()?;
112        self.minute_of_hour()?;
113        self.spaces()?;
114        self.static_str(":")?;
115        self.spaces()?;
116        self.second_of_minute()
117    }
118    /// `%U`. `00` to `53`.
119    fn week_number_of_current_year_start_sunday(&mut self) -> Result<(), Self::Error>;
120    /// `%w`.
121    fn day_of_week_from_sunday_as_0(&mut self) -> Result<(), Self::Error>;
122    /// `%W`. `00` to `53`.
123    fn week_number_of_current_year_start_monday(&mut self) -> Result<(), Self::Error>;
124    /// `%x`. `%m/%d/%y` in POSIX locale. `nl_langinfo`-dependent.
125    #[inline]
126    fn preferred_date(&mut self) -> Result<(), Self::Error> {
127        self.month_of_year()?;
128        self.static_str("/")?;
129        self.day_of_month()?;
130        self.static_str("/")?;
131        self.year_suffix()
132    }
133    /// `%X`. `%H:%M:%S` in POSIX locale. `nl_langinfo`-dependent.
134    #[inline]
135    fn preferred_time_of_day(&mut self) -> Result<(), Self::Error> {
136        self.hour_of_day()?;
137        self.static_str(":")?;
138        self.minute_of_hour()?;
139        self.static_str(":")?;
140        self.second_of_minute()
141    }
142    /// `%y`. `00` to `99`.
143    fn year_suffix(&mut self) -> Result<(), Self::Error>;
144    /// `%Y`.
145    fn year(&mut self) -> Result<(), Self::Error>;
146    /// `%z`. `+hhmm` or `-hhmm`.
147    fn timezone(&mut self) -> Result<(), Self::Error>;
148    /// `%Z`. Timezone name or abbreviation.
149    fn timezone_name(&mut self) -> Result<(), Self::Error>;
150    /// `%%`.
151    #[inline]
152    fn percent(&mut self) -> Result<(), Self::Error> {
153        self.static_str("%")
154    }
155    /// Escaped character or seprators in formatted string like `:` or `/`.
156    /// It's just a character but we'd want a &'static str.
157    fn static_str(&mut self, s: &'static str) -> Result<(), Self::Error>;
158    /// Other literals.
159    /// The byte range of the original format `fmt_span` is passed so that you can internally store
160    /// the original format and index that to get the same content with `lit` with your favorite
161    /// lifetime.
162    fn literal(
163        &mut self,
164        lit: &str,
165        fmt_span: impl SliceIndex<[u8], Output = [u8]>,
166    ) -> Result<(), Self::Error>;
167    /// `%(something else)`.
168    fn unknown(&mut self, specifier: char) -> Result<(), Self::Error>;
169
170    /// Check for remaining unconsumed input.
171    fn unconsumed_input(&self) -> Result<(), Self::Error>;
172
173    /// Construct the final result from what you've collected.
174    fn output(self) -> Result<Self::Output, Self::Error>;
175}
176
177pub(crate) fn parse_format_specifications<C: Collector>(
178    mut format: &str,
179    mut collector: C,
180    strict: bool,
181) -> Result<C::Output, C::Error> {
182    let original_len = format.len();
183    while !format.is_empty() {
184        let i = format
185            .find(|c: char| c == '%' || c.is_whitespace())
186            .unwrap_or(format.len());
187        if i > 0 {
188            let start = original_len - format.len();
189            let (lit, rest) = format.split_at(i);
190            collector.literal(lit, start..(start + i))?;
191            format = rest;
192            if format.is_empty() {
193                break;
194            }
195        }
196        if format.starts_with(char::is_whitespace) {
197            collector.spaces()?;
198            format = format.trim_start();
199            continue;
200        }
201        assert_eq!(format.as_bytes()[0], b'%');
202        format = &format[1..];
203        if let Some(b) = format.bytes().next() {
204            match b {
205                b'a' | b'A' => collector.day_of_week_name()?,
206                b'b' | b'B' | b'h' => collector.month_name()?,
207                b'c' => collector.preferred_date_time()?,
208                b'C' => collector.year_prefix()?,
209                b'd' | b'e' => collector.day_of_month()?,
210                b'D' => collector.date_mmddyy_slash()?,
211                b'F' => collector.date_yyyymmdd_hyphen()?,
212                b'H' | b'k' => collector.hour_of_day()?,
213                b'I' | b'l' => collector.hour_of_day_12()?,
214                b'j' => collector.day_of_year()?,
215                b'm' => collector.month_of_year()?,
216                b'M' => collector.minute_of_hour()?,
217                b'n' => collector.new_line()?,
218                b'p' | b'P' => collector.ampm()?,
219                b'r' => collector.time_ampm()?,
220                b'R' => collector.hour_minute_of_day()?,
221                b'S' => collector.second_of_minute()?,
222                b'f' => collector.nanosecond_of_second()?,
223                b't' => collector.tab()?,
224                b'T' => collector.time_of_day()?,
225                b'U' => collector.week_number_of_current_year_start_sunday()?,
226                b'w' => collector.day_of_week_from_sunday_as_0()?,
227                b'W' => collector.week_number_of_current_year_start_monday()?,
228                b'x' => collector.preferred_date()?,
229                b'X' => collector.preferred_time_of_day()?,
230                b'y' => collector.year_suffix()?,
231                b'Y' => collector.year()?,
232                b'z' => collector.timezone()?,
233                b'Z' => collector.timezone_name()?,
234                b'%' => collector.percent()?,
235                _ => {
236                    let c = format.chars().next().unwrap();
237                    collector.unknown(c)?;
238                    format = &format[c.len_utf8()..];
239                    continue;
240                }
241            }
242            format = &format[1..];
243        } else {
244            collector.percent()?;
245        }
246    }
247
248    if strict {
249        collector.unconsumed_input()?;
250    };
251
252    collector.output()
253}