1use thiserror::Error;
2use time::{Date, Month, PrimitiveDateTime, Time, UtcOffset, Weekday};
3
4use crate::{parse::desc_parser::Collector, util};
5
6mod desc_parser;
7pub mod time_format_item;
8
9#[derive(Error, Debug, PartialEq, Eq)]
10#[non_exhaustive]
11pub enum ParseError {
12 #[error("Unknown specifier `%{0}`")]
13 UnknownSpecifier(char),
14 #[error("Expected {0} but got a byte {1}")]
15 UnexpectedByte(&'static str, u8),
16 #[error("Expected {0} but reached to the end")]
17 UnexpectedEnd(&'static str),
18 #[error("Expected {0} but doesn't have a match")]
19 NotMatch(&'static str),
20 #[error("Out-of-range for {0} component")]
21 ComponentOutOfRange(&'static str),
22 #[error("Unconverted data remains: {0}")]
23 UnconvertedDataRemains(String),
24 #[error(transparent)]
25 ComponentRange(#[from] time::error::ComponentRange),
26}
27
28trait Nat: std::ops::Add<Output = Self> + std::ops::Mul<Output = Self>
29where
30 Self: Sized,
31{
32 const ZERO: Self;
33 const TEN: Self;
34 fn from_u8(_: u8) -> Self;
35}
36
37trait Int: Nat + std::ops::Neg<Output = Self> {}
38
39impl Nat for u8 {
40 const ZERO: Self = 0;
41 const TEN: Self = 10;
42 fn from_u8(v: u8) -> Self {
43 v
44 }
45}
46impl Nat for u16 {
47 const ZERO: Self = 0;
48 const TEN: Self = 10;
49 fn from_u8(v: u8) -> Self {
50 v as u16
51 }
52}
53impl Nat for u32 {
54 const ZERO: Self = 0;
55 const TEN: Self = 10;
56 fn from_u8(v: u8) -> Self {
57 v as u32
58 }
59}
60impl Nat for i16 {
61 const ZERO: Self = 0;
62 const TEN: Self = 10;
63 fn from_u8(v: u8) -> Self {
64 v as i16
65 }
66}
67impl Int for i16 {}
68impl Nat for i32 {
69 const ZERO: Self = 0;
70 const TEN: Self = 10;
71 fn from_u8(v: u8) -> Self {
72 v as i32
73 }
74}
75impl Int for i32 {}
76
77#[derive(Debug)]
78enum ParsingYear {
79 Unspecified,
80 Year(i32),
81 PrefixSuffix(i32, u8),
82}
83#[derive(Debug)]
84enum ParsingDayOfYear {
85 Unspecified,
86 MonthDay(Month, u8),
87 DayOfYear(u16),
88}
89#[derive(Debug)]
90enum ParsingHour {
91 Unspecified,
92 FullDay(u8),
93 HalfDay(u8, bool),
94}
95
96#[derive(Debug, PartialEq, Eq)]
97pub enum TimeZoneSpecifier<'a> {
98 Offset(UtcOffset),
99 Name(&'a str),
100}
101
102struct ParseCollector<'a> {
103 s: &'a str,
104 year: ParsingYear,
105 day: ParsingDayOfYear,
106 hour: ParsingHour,
107 minute: u8,
108 second: u8,
109 nanosecond: u32,
110 zone: Option<TimeZoneSpecifier<'a>>,
111}
112impl<'a> ParseCollector<'a> {
113 fn new(s: &'a str) -> Self {
114 Self {
115 s,
116 year: ParsingYear::Unspecified,
117 day: ParsingDayOfYear::Unspecified,
118 hour: ParsingHour::Unspecified,
119 minute: 0,
120 second: 0,
121 nanosecond: 0,
122 zone: None,
123 }
124 }
125
126 #[inline]
127 fn skip_whitespaces(&mut self) {
128 self.s = self.s.trim_start();
129 }
130
131 #[inline]
132 fn get_until_whitespace(&mut self) -> &'a str {
133 let pos = self.s.find(char::is_whitespace).unwrap_or(self.s.len());
134 let (res, rest) = self.s.split_at(pos);
135 self.s = rest;
136 res
137 }
138
139 #[inline]
140 fn peek_byte(&self) -> Option<u8> {
141 self.s.bytes().next()
142 }
143
144 #[inline]
146 fn parse_nat<N: Nat>(&mut self, min_len: usize, max_len: usize) -> Result<N, ParseError> {
147 if self.s.len() < min_len {
148 return Err(ParseError::UnexpectedEnd("digits"));
149 }
150 let bytes = self.s.as_bytes();
151 let max_len = max_len.min(bytes.len());
152 let mut res = N::ZERO;
153 let mut bytes_read = 0;
154 for &c in &bytes[..max_len] {
155 if (b'0'..=b'9').contains(&c) {
156 res = (res * N::TEN) + N::from_u8(c - b'0');
157 } else if bytes_read < min_len {
158 return Err(ParseError::UnexpectedByte("digits", c));
159 } else {
160 break;
161 }
162 bytes_read += 1;
163 }
164 self.s = &self.s[bytes_read..];
165 Ok(res)
166 }
167
168 #[inline]
171 fn parse_int<Z: Int>(&mut self, max_len: usize) -> Result<Z, ParseError> {
172 if self.s.is_empty() {
173 return Err(ParseError::UnexpectedEnd("digits"));
174 }
175 let max_len = max_len.min(self.s.len());
176 let mut res = Z::ZERO;
177 let mut bytes_read = 0;
178 let mut negate = false;
179 let mut had_digit = false;
180 for &c in &self.s.as_bytes()[..max_len] {
181 if (b'0'..=b'9').contains(&c) {
182 res = (res * Z::TEN) + Z::from_u8(c - b'0');
183 had_digit = true;
184 } else if bytes_read == 0 {
185 if c == b'+' {
186 } else if c == b'-' {
188 negate = true;
189 } else {
190 return Err(ParseError::UnexpectedByte("digits or sign", c));
191 }
192 } else if had_digit {
193 break;
194 } else {
195 return Err(ParseError::UnexpectedByte("digits", c));
196 }
197 bytes_read += 1;
198 }
199 self.s = &self.s[bytes_read..];
200 Ok(if negate { -res } else { res })
201 }
202
203 #[inline]
204 fn starts_with_ignore_ascii_case(&self, prefix: &str) -> bool {
205 self.s.len() >= prefix.len()
206 && self.s.is_char_boundary(prefix.len())
207 && self.s[..prefix.len()].eq_ignore_ascii_case(prefix)
208 }
209}
210
211impl<'a> Collector for ParseCollector<'a> {
212 type Output = (PrimitiveDateTime, Option<TimeZoneSpecifier<'a>>);
213 type Error = ParseError;
214
215 #[inline]
216 fn spaces(&mut self) -> Result<(), Self::Error> {
217 self.skip_whitespaces();
218 Ok(())
219 }
220
221 #[inline]
222 fn day_of_week_name(&mut self) -> Result<(), Self::Error> {
223 let mut weekday = Weekday::Monday;
224 for _i in 0..7 {
225 let short = util::weekday_short_str(weekday);
226 if self.starts_with_ignore_ascii_case(short) {
227 let long = util::weekday_long_str(weekday);
228 if self.starts_with_ignore_ascii_case(long) {
229 self.s = &self.s[long.len()..];
230 } else {
231 self.s = &self.s[short.len()..];
232 }
233 return Ok(());
235 }
236 weekday = weekday.next();
237 }
238 Err(Self::Error::NotMatch("day of week name"))
239 }
240
241 #[inline]
242 fn month_name(&mut self) -> Result<(), Self::Error> {
243 let mut month = Month::January;
244 for _i in 0..12 {
245 let short = util::month_short_str(month);
246 if self.starts_with_ignore_ascii_case(short) {
247 let long = util::month_long_str(month);
248 if self.starts_with_ignore_ascii_case(long) {
249 self.s = &self.s[long.len()..];
250 } else {
251 self.s = &self.s[short.len()..];
252 }
253 match &mut self.day {
254 ParsingDayOfYear::Unspecified => {
255 self.day = ParsingDayOfYear::MonthDay(month, 1)
256 }
257 ParsingDayOfYear::MonthDay(current, _) => *current = month,
258 ParsingDayOfYear::DayOfYear(_) => {}
260 }
261 return Ok(());
262 }
263 month = month.next();
264 }
265 Err(Self::Error::NotMatch("month name"))
266 }
267
268 #[inline]
269 fn year_prefix(&mut self) -> Result<(), Self::Error> {
270 let prefix = self.parse_int(2)?;
271 match &mut self.year {
272 ParsingYear::Unspecified => self.year = ParsingYear::PrefixSuffix(prefix, 0),
273 ParsingYear::Year(_) => {}
275 ParsingYear::PrefixSuffix(v, _) => *v = prefix,
276 }
277 Ok(())
278 }
279
280 #[inline]
281 fn day_of_month(&mut self) -> Result<(), Self::Error> {
282 let day = self.parse_nat(1, 2)?;
283 if (1..=31).contains(&day) {
284 match &mut self.day {
285 ParsingDayOfYear::Unspecified => {
286 self.day = ParsingDayOfYear::MonthDay(Month::January, day)
287 }
288 ParsingDayOfYear::MonthDay(_, current) => *current = day,
289 ParsingDayOfYear::DayOfYear(_) => {}
291 }
292 Ok(())
293 } else {
294 Err(Self::Error::ComponentOutOfRange("day-of-month"))
295 }
296 }
297
298 #[inline]
299 fn hour_of_day(&mut self) -> Result<(), Self::Error> {
300 let hour = self.parse_nat(1, 2)?;
301 if (0..24).contains(&hour) {
302 match &mut self.hour {
303 ParsingHour::Unspecified => self.hour = ParsingHour::FullDay(hour),
304 ParsingHour::FullDay(current) => *current = hour,
305 ParsingHour::HalfDay(_, _) => {}
307 }
308 Ok(())
309 } else {
310 Err(Self::Error::ComponentOutOfRange("hour-of-day"))
311 }
312 }
313
314 #[inline]
315 fn hour_of_day_12(&mut self) -> Result<(), Self::Error> {
316 let hour: u8 = self.parse_nat(1, 2)?;
317 if (1..=12).contains(&hour) {
318 let hour = hour % 12;
319 match &mut self.hour {
320 ParsingHour::Unspecified => self.hour = ParsingHour::HalfDay(hour, false),
321 ParsingHour::FullDay(_) => {}
323 ParsingHour::HalfDay(current, _) => *current = hour,
324 }
325 Ok(())
326 } else {
327 Err(Self::Error::ComponentOutOfRange("hour-of-half-day"))
328 }
329 }
330
331 #[inline]
332 fn day_of_year(&mut self) -> Result<(), Self::Error> {
333 let day = self.parse_nat(1, 3)?;
334 if (1..=366).contains(&day) {
335 self.day = ParsingDayOfYear::DayOfYear(day);
337 Ok(())
338 } else {
339 Err(Self::Error::ComponentOutOfRange("day-of-year"))
340 }
341 }
342
343 #[inline]
344 fn month_of_year(&mut self) -> Result<(), Self::Error> {
345 let month = self.parse_nat(1, 2)?;
346 if (1..=12).contains(&month) {
347 let month = util::get_month(month).unwrap();
348 match &mut self.day {
349 ParsingDayOfYear::Unspecified => self.day = ParsingDayOfYear::MonthDay(month, 1),
350 ParsingDayOfYear::MonthDay(current, _) => *current = month,
351 ParsingDayOfYear::DayOfYear(_) => {}
353 }
354 Ok(())
355 } else {
356 Err(Self::Error::ComponentOutOfRange("month"))
357 }
358 }
359
360 #[inline]
361 fn minute_of_hour(&mut self) -> Result<(), Self::Error> {
362 let minute = self.parse_nat(1, 2)?;
363 if (0..60).contains(&minute) {
364 self.minute = minute;
365 Ok(())
366 } else {
367 Err(Self::Error::ComponentOutOfRange("munute"))
368 }
369 }
370
371 #[inline]
372 fn ampm(&mut self) -> Result<(), Self::Error> {
373 for h in [0, 12] {
374 let s = util::ampm_lower(h);
375 if self.starts_with_ignore_ascii_case(s) {
376 match &mut self.hour {
377 ParsingHour::Unspecified => self.hour = ParsingHour::HalfDay(0, h != 0),
378 ParsingHour::FullDay(_) => {}
380 ParsingHour::HalfDay(_, current) => *current = h != 0,
381 }
382 self.s = &self.s[2..];
384 return Ok(());
385 }
386 }
387 Err(Self::Error::NotMatch("am/pm"))
388 }
389
390 #[inline]
391 fn second_of_minute(&mut self) -> Result<(), Self::Error> {
392 let second = self.parse_nat(1, 2)?;
393 if (0..61).contains(&second) {
394 self.second = second;
395 Ok(())
396 } else {
397 Err(Self::Error::ComponentOutOfRange("second"))
398 }
399 }
400
401 #[inline]
402 fn nanosecond_of_second(&mut self) -> Result<(), Self::Error> {
403 let input_length = self.s.len();
404 let nanosecond: u32 = self.parse_nat(1, 9)?;
405
406 let digits_consumed = input_length - self.s.len();
407 static SCALE: [u32; 10] = [
408 0,
409 100_000_000,
410 10_000_000,
411 1_000_000,
412 100_000,
413 10_000,
414 1_000,
415 100,
416 10,
417 1,
418 ];
419 self.nanosecond = nanosecond * SCALE[digits_consumed];
420
421 Ok(())
422 }
423
424 #[inline]
425 fn week_number_of_current_year_start_sunday(&mut self) -> Result<(), Self::Error> {
426 let w: u8 = self.parse_nat(1, 2)?;
427 if (0..=53).contains(&w) {
428 Ok(())
430 } else {
431 Err(Self::Error::ComponentOutOfRange("week-number"))
432 }
433 }
434
435 #[inline]
436 fn day_of_week_from_sunday_as_0(&mut self) -> Result<(), Self::Error> {
437 let w: u8 = self.parse_nat(1, 1)?;
438 if (0..7).contains(&w) {
439 Ok(())
441 } else {
442 Err(Self::Error::ComponentOutOfRange("day-of-week"))
443 }
444 }
445
446 #[inline]
447 fn week_number_of_current_year_start_monday(&mut self) -> Result<(), Self::Error> {
448 let w: u8 = self.parse_nat(1, 2)?;
449 if (0..=53).contains(&w) {
450 Ok(())
451 } else {
452 Err(Self::Error::ComponentOutOfRange("week-number"))
453 }
454 }
455
456 #[inline]
457 fn year_suffix(&mut self) -> Result<(), Self::Error> {
458 let y = self.parse_nat(1, 2)?;
459 if (0..100).contains(&y) {
460 match &mut self.year {
461 ParsingYear::Unspecified => {
462 self.year = ParsingYear::PrefixSuffix(if y < 69 { 20 } else { 19 }, y)
463 }
464 ParsingYear::Year(_) => {}
466 ParsingYear::PrefixSuffix(_, current) => *current = y,
467 }
468 Ok(())
469 } else {
470 Err(Self::Error::ComponentOutOfRange("year-suffix"))
471 }
472 }
473
474 #[inline]
475 fn year(&mut self) -> Result<(), Self::Error> {
476 let y = self.parse_int(4)?;
477 self.year = ParsingYear::Year(y);
479 Ok(())
480 }
481
482 #[inline]
483 fn timezone(&mut self) -> Result<(), Self::Error> {
484 let negate = match self.peek_byte() {
485 Some(b'Z') => {
486 self.s = &self.s[1..]; self.zone = Some(TimeZoneSpecifier::Offset(UtcOffset::UTC));
488 return Ok(());
489 }
490 Some(c @ (b'+' | b'-')) => {
491 self.s = &self.s[1..]; c == b'-'
493 }
494 Some(b) => return Err(Self::Error::UnexpectedByte("+ or -", b)),
495 None => return Err(Self::Error::UnexpectedEnd("+ or -")),
496 };
497 let h: u8 = self.parse_nat(2, 2)?;
498 if self.peek_byte() == Some(b':') {
499 self.s = &self.s[1..]; }
501 let m: u8 = self.parse_nat(2, 2)?;
502 let h: i8 = h
503 .try_into()
504 .map_err(|_| Self::Error::ComponentOutOfRange("offset-hour"))?;
505 let m: i8 = m
506 .try_into()
507 .map_err(|_| Self::Error::ComponentOutOfRange("offset-minute"))?;
508 let (h, m) = if negate { (-h, -m) } else { (h, m) };
509 self.zone = Some(TimeZoneSpecifier::Offset(UtcOffset::from_hms(h, m, 0)?));
510 Ok(())
511 }
512
513 #[inline]
514 fn timezone_name(&mut self) -> Result<(), Self::Error> {
515 let s = self.get_until_whitespace();
516 self.zone = Some(TimeZoneSpecifier::Name(s));
517 Ok(())
518 }
519
520 #[inline]
521 fn static_str(&mut self, s: &'static str) -> Result<(), Self::Error> {
522 if let Some(rest) = self.s.strip_prefix(s) {
523 self.s = rest;
524 Ok(())
525 } else {
526 Err(Self::Error::NotMatch(s))
527 }
528 }
529
530 #[inline]
531 fn literal(
532 &mut self,
533 lit: &str,
534 _fmt_span: impl std::slice::SliceIndex<[u8], Output = [u8]>,
535 ) -> Result<(), Self::Error> {
536 if let Some(rest) = self.s.strip_prefix(lit) {
537 self.s = rest;
538 Ok(())
539 } else {
540 Err(Self::Error::NotMatch("string literal"))
541 }
542 }
543
544 #[inline]
545 fn unknown(&mut self, specifier: char) -> Result<(), Self::Error> {
546 Err(Self::Error::UnknownSpecifier(specifier))
547 }
548
549 #[inline]
550 fn unconsumed_input(&self) -> Result<(), Self::Error> {
551 let unconsumed_input = self.s.to_string();
552 if unconsumed_input.len() > 0 {
553 Err(Self::Error::UnconvertedDataRemains(unconsumed_input))
554 } else {
555 Ok(())
556 }
557 }
558
559 #[inline]
560 fn output(self) -> Result<Self::Output, Self::Error> {
561 let year = match self.year {
562 ParsingYear::Unspecified => 1900,
563 ParsingYear::Year(y) => y,
564 ParsingYear::PrefixSuffix(p, s) => p
565 .checked_mul(100)
566 .and_then(|p| p.checked_add(s as i32))
567 .ok_or(Self::Error::ComponentOutOfRange("year"))?,
568 };
569 let date = match self.day {
570 ParsingDayOfYear::Unspecified => Date::from_ordinal_date(year, 1)?,
571 ParsingDayOfYear::MonthDay(month, day) => Date::from_calendar_date(year, month, day)?,
572 ParsingDayOfYear::DayOfYear(day) => Date::from_ordinal_date(year, day)?,
573 };
574 let hour = match self.hour {
575 ParsingHour::Unspecified => 0,
576 ParsingHour::FullDay(h) => h,
577 ParsingHour::HalfDay(h, ampm) => {
578 if ampm {
579 h + 12
580 } else {
581 h
582 }
583 }
584 };
585 let time = Time::from_hms_nano(hour, self.minute, self.second, self.nanosecond)?;
586 let zone = self.zone;
587 Ok((PrimitiveDateTime::new(date, time), zone))
588 }
589}
590
591pub fn parse_date_time_maybe_with_zone<'a>(
592 fmt: &str,
593 s: &'a str,
594) -> Result<(PrimitiveDateTime, Option<TimeZoneSpecifier<'a>>), ParseError> {
595 let collector = ParseCollector::new(s);
596 desc_parser::parse_format_specifications(fmt, collector, false)
597}
598
599pub fn parse_strict_date_time_maybe_with_zone<'a>(
600 fmt: &str,
601 s: &'a str,
602) -> Result<(PrimitiveDateTime, Option<TimeZoneSpecifier<'a>>), ParseError> {
603 let collector = ParseCollector::new(s);
604 desc_parser::parse_format_specifications(fmt, collector, true)
605}
606
607#[cfg(test)]
608mod tests {
609 use super::{
610 parse_date_time_maybe_with_zone, parse_strict_date_time_maybe_with_zone, ParseError,
611 TimeZoneSpecifier,
612 };
613 use time::macros::{datetime, offset};
614
615 #[test]
616 fn test_simple_parse() -> Result<(), super::ParseError> {
617 assert_eq!(
618 parse_date_time_maybe_with_zone("%a %A %a", "wED Wed weDnesDay")?,
619 (datetime!(1900-01-01 00:00:00), None)
620 );
621 assert_eq!(
622 parse_date_time_maybe_with_zone("%b %B %b", "feB FEb feburaRy")?,
623 (datetime!(1900-02-01 00:00:00), None)
624 );
625 assert_eq!(
626 parse_date_time_maybe_with_zone("%c", "Sun Mar 6 12:34:56 2022")?,
627 (datetime!(2022-03-06 12:34:56), None)
628 );
629 assert_eq!(
630 parse_date_time_maybe_with_zone("%C", "20")?,
631 (datetime!(2000-01-01 00:00:00), None)
632 );
633 assert_eq!(
634 parse_date_time_maybe_with_zone("%d", "5")?,
635 (datetime!(1900-01-05 00:00:00), None)
636 );
637 assert_eq!(
638 parse_date_time_maybe_with_zone("%e", "5")?,
639 (datetime!(1900-01-05 00:00:00), None)
640 );
641 assert_eq!(
642 parse_date_time_maybe_with_zone("%D", "3 /6/22")?,
643 (datetime!(2022-03-06 00:00:00), None)
644 );
645 assert_eq!(
646 parse_date_time_maybe_with_zone("%F", "2022-03-06")?,
647 (datetime!(2022-03-06 00:00:00), None)
648 );
649 assert_eq!(
650 parse_date_time_maybe_with_zone("%H", "2")?,
651 (datetime!(1900-01-01 2:00:00), None)
652 );
653 assert_eq!(
654 parse_date_time_maybe_with_zone("%k", "2")?,
655 (datetime!(1900-01-01 2:00:00), None)
656 );
657 assert_eq!(
658 parse_date_time_maybe_with_zone("%I", "2")?,
659 (datetime!(1900-01-01 2:00:00), None)
660 );
661 assert_eq!(
662 parse_date_time_maybe_with_zone("%l", "12")?,
663 (datetime!(1900-01-01 00:00:00), None)
664 );
665 assert_eq!(
666 parse_date_time_maybe_with_zone("%j", "38")?,
667 (datetime!(1900-02-07 00:00:00), None)
668 );
669 assert_eq!(
670 parse_date_time_maybe_with_zone("%m", "8")?,
671 (datetime!(1900-08-01 00:00:00), None)
672 );
673 assert_eq!(
674 parse_date_time_maybe_with_zone("%M", "8")?,
675 (datetime!(1900-01-01 00:08:00), None)
676 );
677 assert_eq!(
678 parse_date_time_maybe_with_zone("%n%t ", " ")?,
679 (datetime!(1900-01-01 00:00:00), None)
680 );
681 assert_eq!(
682 parse_date_time_maybe_with_zone("%I %p", "12 AM")?,
683 (datetime!(1900-01-01 00:00:00), None)
684 );
685 assert_eq!(
686 parse_date_time_maybe_with_zone("%I %p", "1 AM")?,
687 (datetime!(1900-01-01 01:00:00), None)
688 );
689 assert_eq!(
690 parse_date_time_maybe_with_zone("%I %p", "1 pm")?,
691 (datetime!(1900-01-01 13:00:00), None)
692 );
693 assert_eq!(
694 parse_date_time_maybe_with_zone("%I %p", "12 pm")?,
695 (datetime!(1900-01-01 12:00:00), None)
696 );
697 assert_eq!(
698 parse_date_time_maybe_with_zone("%r", "12:34:56 PM")?,
699 (datetime!(1900-01-01 12:34:56), None)
700 );
701 assert_eq!(
702 parse_date_time_maybe_with_zone("%r", "12:34:56 AM")?,
703 (datetime!(1900-01-01 00:34:56), None)
704 );
705 assert_eq!(
706 parse_date_time_maybe_with_zone("%r %F", "12:34:56 AM 2022-03-06")?,
707 (datetime!(2022-03-06 00:34:56), None)
708 );
709 assert_eq!(
710 parse_date_time_maybe_with_zone("%R", "12: 4")?,
711 (datetime!(1900-01-01 12:04:00), None)
712 );
713 assert_eq!(
714 parse_date_time_maybe_with_zone("%S", "01")?,
715 (datetime!(1900-01-01 00:00:01), None)
716 );
717 assert_eq!(
718 parse_date_time_maybe_with_zone("%T", "01:23:45")?,
719 (datetime!(1900-01-01 01:23:45), None)
720 );
721 assert_eq!(
722 parse_date_time_maybe_with_zone("%T.%f", "01:23:45.123")?,
723 (datetime!(1900-01-01 01:23:45.123), None)
724 );
725 assert_eq!(
726 parse_date_time_maybe_with_zone("%T.%f", "01:23:45.12300")?,
727 (datetime!(1900-01-01 01:23:45.123), None)
728 );
729 assert_eq!(
730 parse_date_time_maybe_with_zone("%T.%f", "01:23:45.000001234")?,
731 (datetime!(1900-01-01 01:23:45.000001234), None)
732 );
733 assert_eq!(
734 parse_date_time_maybe_with_zone("%U", "1")?,
735 (datetime!(1900-01-01 00:00:00), None)
736 );
737 assert_eq!(
738 parse_date_time_maybe_with_zone("%w", "1")?,
739 (datetime!(1900-01-01 00:00:00), None)
740 );
741 assert_eq!(
742 parse_date_time_maybe_with_zone("%W", "1")?,
743 (datetime!(1900-01-01 00:00:00), None)
744 );
745 assert_eq!(
746 parse_date_time_maybe_with_zone("%x", "3/6/22")?,
747 (datetime!(2022-03-06 00:00:00), None)
748 );
749 assert_eq!(
750 parse_date_time_maybe_with_zone("%X", "12:34:5")?,
751 (datetime!(1900-01-01 12:34:05), None)
752 );
753 assert_eq!(
754 parse_date_time_maybe_with_zone("%y", "12")?,
755 (datetime!(2012-01-01 00:00:00), None)
756 );
757 assert_eq!(
758 parse_date_time_maybe_with_zone("%y", "70")?,
759 (datetime!(1970-01-01 00:00:00), None)
760 );
761 assert_eq!(
762 parse_date_time_maybe_with_zone("%Y", "70")?,
763 (datetime!(0070-01-01 00:00:00), None)
764 );
765 assert_eq!(
766 parse_date_time_maybe_with_zone("%C%y", "2022")?,
767 (datetime!(2022-01-01 00:00:00), None)
768 );
769 Ok(())
770 }
771
772 #[test]
773 fn test_strict() {
774 assert!(matches!(
775 parse_strict_date_time_maybe_with_zone("%F", "2022-03-06T12:34:56Z"),
776 Err(ParseError::UnconvertedDataRemains(_)),
777 ));
778
779 assert_eq!(
780 parse_strict_date_time_maybe_with_zone("%FT%TZ", "2022-03-06T12:34:56Z"),
781 Ok((datetime!(2022-03-06 12:34:56), None))
782 );
783 }
784
785 #[test]
786 fn test_zone() -> Result<(), super::ParseError> {
787 assert_eq!(
788 parse_date_time_maybe_with_zone("%FT%TZ", "2022-03-06T12:34:56Z")?,
789 (datetime!(2022-03-06 12:34:56), None)
790 );
791 assert_eq!(
792 parse_date_time_maybe_with_zone("%FT%T%z", "2022-03-06T12:34:56Z")?,
793 (
794 datetime!(2022-03-06 12:34:56),
795 Some(TimeZoneSpecifier::Offset(offset!(+00:00)))
796 )
797 );
798 assert_eq!(
799 parse_date_time_maybe_with_zone("%FT%T%Z", "2022-03-06T12:34:56Z")?,
800 (
801 datetime!(2022-03-06 12:34:56),
802 Some(TimeZoneSpecifier::Name("Z"))
803 )
804 );
805 assert_eq!(
806 parse_date_time_maybe_with_zone("%FT%T %z", "2022-03-06T12:34:56 -1234")?,
807 (
808 datetime!(2022-03-06 12:34:56),
809 Some(TimeZoneSpecifier::Offset(offset!(-12:34)))
810 )
811 );
812 assert_eq!(
813 parse_date_time_maybe_with_zone("%FT%T %Z", "2022-03-06T12:34:56 JST")?,
814 (
815 datetime!(2022-03-06 12:34:56),
816 Some(TimeZoneSpecifier::Name("JST"))
817 )
818 );
819 assert_eq!(
820 parse_date_time_maybe_with_zone("%FT%T %z", "2022-03-06T12:34:56 -12:34")?,
821 (
822 datetime!(2022-03-06 12:34:56),
823 Some(TimeZoneSpecifier::Offset(offset!(-12:34)))
824 )
825 );
826 assert!(parse_date_time_maybe_with_zone("%FT%T %z", "2022-03-06T12:34:56 12:34").is_err());
827 assert!(parse_date_time_maybe_with_zone("%FT%T %z", "2022-03-06T12:34:56 +2:34").is_err());
828 assert!(parse_date_time_maybe_with_zone("%FT%T %z", "2022-03-06T12:34:56 +234").is_err());
829 Ok(())
830 }
831}