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 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 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}