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