time_fmt/
format.rs

1use std::fmt::Write;
2
3use thiserror::Error;
4use time::{Date, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset};
5
6use crate::{format::spec_parser::Collector, util};
7
8mod spec_parser;
9pub mod time_format_item;
10
11#[derive(Error, Debug, PartialEq, Eq)]
12#[non_exhaustive]
13pub enum FormatError {
14    #[error("Unknown specifier `%{0}`")]
15    UnknownSpecifier(char),
16    #[error(transparent)]
17    Format(#[from] std::fmt::Error),
18}
19
20struct FormatCollector<'a, W: Write> {
21    date: Date,
22    time: Time,
23    offset: Option<UtcOffset>,
24    zone_name: Option<&'a str>,
25    write: &'a mut W,
26}
27impl<'a, W: Write> FormatCollector<'a, W> {
28    fn from_date_time(date_time: PrimitiveDateTime, write: &'a mut W) -> Self {
29        Self {
30            date: date_time.date(),
31            time: date_time.time(),
32            offset: None,
33            zone_name: None,
34            write,
35        }
36    }
37    fn from_offset_date_time(date_time: OffsetDateTime, write: &'a mut W) -> Self {
38        Self {
39            date: date_time.date(),
40            time: date_time.time(),
41            offset: Some(date_time.offset()),
42            zone_name: None,
43            write,
44        }
45    }
46
47    fn from_zoned_date_time(
48        date_time: PrimitiveDateTime,
49        offset: UtcOffset,
50        zone_name: &'a str,
51        write: &'a mut W,
52    ) -> Self {
53        Self {
54            date: date_time.date(),
55            time: date_time.time(),
56            offset: Some(offset),
57            zone_name: Some(zone_name),
58            write,
59        }
60    }
61
62    fn from_zoned_offset_date_time(
63        date_time: OffsetDateTime,
64        zone_name: &'a str,
65        write: &'a mut W,
66    ) -> Self {
67        Self {
68            date: date_time.date(),
69            time: date_time.time(),
70            offset: Some(date_time.offset()),
71            zone_name: Some(zone_name),
72            write,
73        }
74    }
75}
76
77impl<'a, W: Write> Collector for FormatCollector<'a, W> {
78    type Output = ();
79    type Error = FormatError;
80
81    #[inline]
82    fn day_of_week_name_short(&mut self) -> Result<(), Self::Error> {
83        self.write
84            .write_str(util::weekday_short_str(self.date.weekday()))?;
85        Ok(())
86    }
87
88    #[inline]
89    fn day_of_week_name_long(&mut self) -> Result<(), Self::Error> {
90        self.write
91            .write_str(util::weekday_long_str(self.date.weekday()))?;
92        Ok(())
93    }
94
95    #[inline]
96    fn month_name_short(&mut self) -> Result<(), Self::Error> {
97        self.write
98            .write_str(util::month_short_str(self.date.month()))?;
99        Ok(())
100    }
101
102    #[inline]
103    fn month_name_long(&mut self) -> Result<(), Self::Error> {
104        self.write
105            .write_str(util::month_long_str(self.date.month()))?;
106        Ok(())
107    }
108
109    #[inline]
110    fn year_prefix(&mut self) -> Result<(), Self::Error> {
111        self.write
112            .write_fmt(format_args!("{:02}", self.date.year().div_euclid(100)))?;
113        Ok(())
114    }
115
116    #[inline]
117    fn day_of_month(&mut self) -> Result<(), Self::Error> {
118        self.write
119            .write_fmt(format_args!("{:02}", self.date.day()))?;
120        Ok(())
121    }
122
123    #[inline]
124    fn day_of_month_blank(&mut self) -> Result<(), Self::Error> {
125        self.write
126            .write_fmt(format_args!("{:2}", self.date.day()))?;
127        Ok(())
128    }
129
130    #[inline]
131    fn iso8601_week_based_year_suffix(&mut self) -> Result<(), Self::Error> {
132        let (year, _, _) = self.date.to_iso_week_date();
133        self.write
134            .write_fmt(format_args!("{:02}", year.rem_euclid(100)))?;
135        Ok(())
136    }
137
138    #[inline]
139    fn iso8601_week_based_year(&mut self) -> Result<(), Self::Error> {
140        let (year, _, _) = self.date.to_iso_week_date();
141        self.write.write_fmt(format_args!("{:4}", year))?;
142        Ok(())
143    }
144
145    #[inline]
146    fn hour_of_day(&mut self) -> Result<(), Self::Error> {
147        self.write
148            .write_fmt(format_args!("{:02}", self.time.hour()))?;
149        Ok(())
150    }
151
152    #[inline]
153    fn hour_of_day_12(&mut self) -> Result<(), Self::Error> {
154        self.write
155            .write_fmt(format_args!("{:02}", (self.time.hour() + 11) % 12 + 1))?;
156        Ok(())
157    }
158
159    #[inline]
160    fn day_of_year(&mut self) -> Result<(), Self::Error> {
161        self.write
162            .write_fmt(format_args!("{:03}", self.date.ordinal()))?;
163        Ok(())
164    }
165
166    #[inline]
167    fn hour_of_day_blank(&mut self) -> Result<(), Self::Error> {
168        self.write
169            .write_fmt(format_args!("{:2}", self.time.hour()))?;
170        Ok(())
171    }
172
173    #[inline]
174    fn hour_of_day_12_blank(&mut self) -> Result<(), Self::Error> {
175        self.write
176            .write_fmt(format_args!("{:2}", (self.time.hour() + 11) % 12 + 1))?;
177        Ok(())
178    }
179
180    #[inline]
181    fn month_of_year(&mut self) -> Result<(), Self::Error> {
182        self.write
183            .write_fmt(format_args!("{:02}", self.date.month() as u8))?;
184        Ok(())
185    }
186
187    #[inline]
188    fn minute_of_hour(&mut self) -> Result<(), Self::Error> {
189        self.write
190            .write_fmt(format_args!("{:02}", self.time.minute()))?;
191        Ok(())
192    }
193
194    #[inline]
195    fn ampm(&mut self) -> Result<(), Self::Error> {
196        self.write.write_str(util::ampm_upper(self.time.hour()))?;
197        Ok(())
198    }
199
200    #[inline]
201    fn ampm_lower(&mut self) -> Result<(), Self::Error> {
202        self.write.write_str(util::ampm_lower(self.time.hour()))?;
203        Ok(())
204    }
205
206    #[inline]
207    fn second_of_minute(&mut self) -> Result<(), Self::Error> {
208        self.write
209            .write_fmt(format_args!("{:02}", self.time.second()))?;
210        Ok(())
211    }
212
213    #[inline]
214    fn nanosecond_of_second(&mut self) -> Result<(), Self::Error> {
215        self.write
216            .write_fmt(format_args!("{:0>9}", self.time.nanosecond()))?;
217        Ok(())
218    }
219
220    #[inline]
221    fn day_of_week_from_monday_as_1(&mut self) -> Result<(), Self::Error> {
222        self.write
223            .write_fmt(format_args!("{}", self.date.weekday().number_from_monday()))?;
224        Ok(())
225    }
226
227    #[inline]
228    fn week_number_of_current_year_start_sunday(&mut self) -> Result<(), Self::Error> {
229        self.write
230            .write_fmt(format_args!("{:02}", self.date.sunday_based_week()))?;
231        Ok(())
232    }
233
234    #[inline]
235    fn iso8601_week_number(&mut self) -> Result<(), Self::Error> {
236        self.write
237            .write_fmt(format_args!("{:02}", self.date.iso_week()))?;
238        Ok(())
239    }
240
241    #[inline]
242    fn day_of_week_from_sunday_as_0(&mut self) -> Result<(), Self::Error> {
243        self.write.write_fmt(format_args!(
244            "{}",
245            self.date.weekday().number_days_from_sunday()
246        ))?;
247        Ok(())
248    }
249
250    #[inline]
251    fn week_number_of_current_year_start_monday(&mut self) -> Result<(), Self::Error> {
252        self.write
253            .write_fmt(format_args!("{:02}", self.date.monday_based_week()))?;
254        Ok(())
255    }
256
257    #[inline]
258    fn year_suffix(&mut self) -> Result<(), Self::Error> {
259        let year = self.date.year();
260        self.write
261            .write_fmt(format_args!("{:02}", year.abs() % 100))?;
262        Ok(())
263    }
264
265    #[inline]
266    fn year(&mut self) -> Result<(), Self::Error> {
267        self.write
268            .write_fmt(format_args!("{:04}", self.date.year()))?;
269        Ok(())
270    }
271
272    #[inline]
273    fn timezone(&mut self) -> Result<(), Self::Error> {
274        if let Some(offset) = self.offset {
275            let (h, m, _) = offset.as_hms();
276            if offset.is_negative() {
277                self.write.write_fmt(format_args!("-{:02}{:02}", -h, -m))?;
278            } else {
279                self.write.write_fmt(format_args!("+{:02}{:02}", h, m))?;
280            }
281        }
282        // No bytes if no timezone is determinable.
283        Ok(())
284    }
285
286    #[inline]
287    fn timezone_name(&mut self) -> Result<(), Self::Error> {
288        if let Some(zone_name) = &self.zone_name {
289            self.write.write_str(zone_name)?;
290        }
291        // No bytes if no timezone information exists.
292        Ok(())
293    }
294
295    #[inline]
296    fn static_str(&mut self, s: &'static str) -> Result<(), Self::Error> {
297        self.write.write_str(s)?;
298        Ok(())
299    }
300
301    #[inline]
302    fn literal(
303        &mut self,
304        lit: &str,
305        _fmt_span: impl std::slice::SliceIndex<[u8], Output = [u8]>,
306    ) -> Result<(), Self::Error> {
307        self.write.write_str(lit)?;
308        Ok(())
309    }
310
311    #[inline]
312    fn unknown(&mut self, specifier: char) -> Result<(), Self::Error> {
313        Err(Self::Error::UnknownSpecifier(specifier))
314    }
315
316    #[inline]
317    fn output(self) -> Result<Self::Output, Self::Error> {
318        Ok(())
319    }
320}
321
322pub fn format_date_time(fmt: &str, date_time: PrimitiveDateTime) -> Result<String, FormatError> {
323    let mut ret = String::new();
324    let collector = FormatCollector::from_date_time(date_time, &mut ret);
325    spec_parser::parse_conversion_specifications(fmt, collector)?;
326    Ok(ret)
327}
328
329pub fn format_offset_date_time(
330    fmt: &str,
331    date_time: OffsetDateTime,
332) -> Result<String, FormatError> {
333    let mut ret = String::new();
334    let collector = FormatCollector::from_offset_date_time(date_time, &mut ret);
335    spec_parser::parse_conversion_specifications(fmt, collector)?;
336    Ok(ret)
337}
338
339pub fn format_zoned_date_time(
340    fmt: &str,
341    date_time: PrimitiveDateTime,
342    offset: UtcOffset,
343    zone_name: &str,
344) -> Result<String, FormatError> {
345    let mut ret = String::new();
346    let collector = FormatCollector::from_zoned_date_time(date_time, offset, zone_name, &mut ret);
347    spec_parser::parse_conversion_specifications(fmt, collector)?;
348    Ok(ret)
349}
350
351pub fn format_zoned_offset_date_time(
352    fmt: &str,
353    date_time: OffsetDateTime,
354    zone_name: &str,
355) -> Result<String, FormatError> {
356    let mut ret = String::new();
357    let collector = FormatCollector::from_zoned_offset_date_time(date_time, zone_name, &mut ret);
358    spec_parser::parse_conversion_specifications(fmt, collector)?;
359    Ok(ret)
360}
361
362#[cfg(test)]
363mod tests {
364    use super::{format_date_time, format_offset_date_time};
365    use time::{
366        macros::{datetime, offset},
367        PrimitiveDateTime,
368    };
369
370    #[test]
371    fn test_simple() -> Result<(), super::FormatError> {
372        fn test_datetime(
373            fmt: &str,
374            dt: PrimitiveDateTime,
375            expected: &str,
376        ) -> Result<(), super::FormatError> {
377            assert_eq!(format_date_time(fmt, dt)?, expected);
378            assert_eq!(
379                format_offset_date_time(fmt, dt.assume_offset(offset!(+9:00)))?,
380                expected
381            );
382            assert_eq!(
383                super::format_zoned_date_time(fmt, dt, offset!(+9:00), "JST")?,
384                expected
385            );
386            assert_eq!(
387                super::format_zoned_offset_date_time(fmt, dt.assume_offset(offset!(+9:00)), "JST")?,
388                expected
389            );
390            Ok(())
391        }
392
393        let datetime = datetime!(2022-03-06 12:34:56);
394        let datetime2 = datetime!(2022-03-06 02:04:06);
395        test_datetime("%a %A", datetime, "Sun Sunday")?;
396        test_datetime("%b %h %B", datetime, "Mar Mar March")?;
397        test_datetime("%c", datetime, "Sun Mar  6 12:34:56 2022")?;
398        test_datetime("%C", datetime, "20")?;
399        test_datetime("%d", datetime, "06")?;
400        test_datetime("%D", datetime, "03/06/22")?;
401        test_datetime("%e", datetime, " 6")?;
402        test_datetime("%F", datetime, "2022-03-06")?;
403        test_datetime("%g", datetime, "22")?;
404        test_datetime("%G", datetime, "2022")?;
405        test_datetime("%H", datetime, "12")?;
406        test_datetime("%H", datetime2, "02")?;
407        test_datetime("%I", datetime, "12")?;
408        test_datetime("%I", datetime2, "02")?;
409        test_datetime("%j", datetime, "065")?;
410        test_datetime("%k", datetime2, " 2")?;
411        test_datetime("%l", datetime, "12")?;
412        test_datetime("%l", datetime2, " 2")?;
413        test_datetime("%m", datetime, "03")?;
414        test_datetime("%M", datetime, "34")?;
415        test_datetime("%n", datetime, "\n")?;
416        test_datetime("%p", datetime, "PM")?;
417        test_datetime("%P", datetime, "pm")?;
418        test_datetime("%r", datetime, "12:34:56 PM")?;
419        test_datetime("%r", datetime2, "02:04:06 AM")?;
420        test_datetime("%R", datetime, "12:34")?;
421        test_datetime("%R", datetime2, "02:04")?;
422        test_datetime("%S", datetime, "56")?;
423        test_datetime("%t", datetime, "\t")?;
424        test_datetime("%T", datetime, "12:34:56")?;
425        test_datetime("%u", datetime, "7")?;
426        test_datetime("%U", datetime, "10")?;
427        test_datetime("%V", datetime, "09")?;
428        test_datetime("%w", datetime, "0")?;
429        test_datetime("%W", datetime, "09")?;
430        test_datetime("%x", datetime, "03/06/22")?;
431        test_datetime("%X", datetime, "12:34:56")?;
432        test_datetime("%y", datetime, "22")?;
433        test_datetime("%Y", datetime, "2022")?;
434        test_datetime("%%", datetime, "%")?;
435
436        let datetime_ms0 = datetime!(2022-03-06 02:04:06);
437        let datetime_ms1 = datetime!(2022-03-06 02:04:06.1);
438        let datetime_ms2 = datetime!(2022-03-06 02:04:06.12);
439        let datetime_ms3 = datetime!(2022-03-06 02:04:06.123);
440        let datetime_ms4 = datetime!(2022-03-06 02:04:06.1234);
441        let datetime_ms5 = datetime!(2022-03-06 02:04:06.12345);
442        let datetime_ms6 = datetime!(2022-03-06 02:04:06.123456);
443        let datetime_ms7 = datetime!(2022-03-06 02:04:06.1234567);
444        let datetime_ms8 = datetime!(2022-03-06 02:04:06.12345678);
445        let datetime_ms9 = datetime!(2022-03-06 02:04:06.123456789);
446
447        test_datetime("%f", datetime_ms0, "000000000")?;
448        test_datetime("%f", datetime_ms1, "100000000")?;
449        test_datetime("%f", datetime_ms2, "120000000")?;
450        test_datetime("%f", datetime_ms3, "123000000")?;
451        test_datetime("%f", datetime_ms4, "123400000")?;
452        test_datetime("%f", datetime_ms5, "123450000")?;
453        test_datetime("%f", datetime_ms6, "123456000")?;
454        test_datetime("%f", datetime_ms7, "123456700")?;
455        test_datetime("%f", datetime_ms8, "123456780")?;
456        test_datetime("%f", datetime_ms9, "123456789")?;
457
458        let datetime_ms1 = datetime!(2022-03-06 02:04:06.900000000);
459        let datetime_ms2 = datetime!(2022-03-06 02:04:06.980000000);
460        let datetime_ms3 = datetime!(2022-03-06 02:04:06.987000000);
461        let datetime_ms4 = datetime!(2022-03-06 02:04:06.987600000);
462        let datetime_ms5 = datetime!(2022-03-06 02:04:06.987650000);
463        let datetime_ms6 = datetime!(2022-03-06 02:04:06.987654000);
464        let datetime_ms7 = datetime!(2022-03-06 02:04:06.987654300);
465        let datetime_ms8 = datetime!(2022-03-06 02:04:06.987654320);
466
467        test_datetime("%f", datetime_ms1, "900000000")?;
468        test_datetime("%f", datetime_ms2, "980000000")?;
469        test_datetime("%f", datetime_ms3, "987000000")?;
470        test_datetime("%f", datetime_ms4, "987600000")?;
471        test_datetime("%f", datetime_ms5, "987650000")?;
472        test_datetime("%f", datetime_ms6, "987654000")?;
473        test_datetime("%f", datetime_ms7, "987654300")?;
474        test_datetime("%f", datetime_ms8, "987654320")?;
475
476        let datetime_ms1 = datetime!(2022-03-06 02:04:06.000000002);
477        let datetime_ms2 = datetime!(2022-03-06 02:04:06.000000022);
478        let datetime_ms3 = datetime!(2022-03-06 02:04:06.000000222);
479        let datetime_ms4 = datetime!(2022-03-06 02:04:06.000002222);
480        let datetime_ms5 = datetime!(2022-03-06 02:04:06.000022222);
481        let datetime_ms6 = datetime!(2022-03-06 02:04:06.000222222);
482        let datetime_ms7 = datetime!(2022-03-06 02:04:06.002222222);
483        let datetime_ms8 = datetime!(2022-03-06 02:04:06.022222222);
484
485        test_datetime("%f", datetime_ms1, "000000002")?;
486        test_datetime("%f", datetime_ms2, "000000022")?;
487        test_datetime("%f", datetime_ms3, "000000222")?;
488        test_datetime("%f", datetime_ms4, "000002222")?;
489        test_datetime("%f", datetime_ms5, "000022222")?;
490        test_datetime("%f", datetime_ms6, "000222222")?;
491        test_datetime("%f", datetime_ms7, "002222222")?;
492        test_datetime("%f", datetime_ms8, "022222222")?;
493
494        Ok(())
495    }
496
497    #[test]
498    fn test_year_prefix() -> Result<(), super::FormatError> {
499        let fmt = "%C";
500        assert_eq!(
501            format_offset_date_time(fmt, datetime!(410-01-01 01:01:01 UTC))?,
502            "04".to_string()
503        );
504        assert_eq!(
505            format_offset_date_time(fmt, datetime!(2021-01-01 01:01:01 UTC))?,
506            "20".to_string()
507        );
508        assert_eq!(
509            format_offset_date_time(fmt, datetime!(+99999-01-01 01:01:01 UTC))?,
510            "999".to_string()
511        );
512        assert_eq!(
513            format_offset_date_time(fmt, datetime!(-1-01-01 01:01:01 UTC))?,
514            "-1".to_string()
515        );
516        assert_eq!(
517            format_offset_date_time(fmt, datetime!(-1000-01-01 01:01:01 UTC))?,
518            "-10".to_string()
519        );
520        Ok(())
521    }
522
523    #[test]
524    fn test_offset() -> Result<(), super::FormatError> {
525        let fmt = "%z";
526        assert_eq!(
527            format_offset_date_time(fmt, datetime!(410-01-01 01:01:01 UTC))?,
528            "+0000".to_string()
529        );
530        assert_eq!(
531            format_offset_date_time(fmt, datetime!(2022-02-02 01:01:01 -1:23))?,
532            "-0123".to_string()
533        );
534        Ok(())
535    }
536
537    #[test]
538    fn test_timezone_name() -> Result<(), super::FormatError> {
539        use super::{format_zoned_date_time, format_zoned_offset_date_time};
540
541        assert_eq!(
542            format_zoned_date_time(
543                "%z %Z",
544                datetime!(2022-02-02 02:02:02),
545                offset!(+9:00),
546                "JST"
547            )?,
548            "+0900 JST".to_string()
549        );
550
551        assert_eq!(
552            format_zoned_offset_date_time(
553                "%T %z %Z",
554                datetime!(2022-02-02 02:02:02 UTC).to_offset(offset!(+9:00)),
555                "JST"
556            )?,
557            "11:02:02 +0900 JST".to_string()
558        );
559        Ok(())
560    }
561}