Added some tests for time::Parser
This commit is contained in:
parent
0bbd11aa03
commit
de27339b73
3 changed files with 148 additions and 73 deletions
|
@ -1,5 +1,5 @@
|
|||
use std::{fmt, num::ParseIntError};
|
||||
use super::parser::Mode;
|
||||
use std::{fmt, num::ParseIntError};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
|
|
|
@ -74,8 +74,8 @@ impl fmt::Display for TimeZone {
|
|||
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
|
||||
pub struct DateTime {
|
||||
pub year: u32,
|
||||
pub month: Option<u8>,
|
||||
pub day: Option<u8>,
|
||||
pub month: u8,
|
||||
pub day: u8,
|
||||
pub hour: Option<u8>,
|
||||
pub minute: Option<u8>,
|
||||
pub second: Option<u8>,
|
||||
|
@ -87,11 +87,7 @@ impl fmt::Display for DateTime {
|
|||
write!(f, "{:04}", self.year)?;
|
||||
'blk: {
|
||||
for field in &[self.month, self.day] {
|
||||
if let Some(field) = field {
|
||||
write!(f, "-{field:02}")?;
|
||||
} else {
|
||||
break 'blk;
|
||||
}
|
||||
}
|
||||
if let Some(h) = self.hour {
|
||||
write!(f, "T{h:02}")?;
|
||||
|
@ -122,8 +118,8 @@ mod test {
|
|||
fn fmt_full() {
|
||||
let dt = DateTime {
|
||||
year: 2023,
|
||||
month: Some(5),
|
||||
day: Some(7),
|
||||
month: 5,
|
||||
day: 7,
|
||||
hour: Some(9),
|
||||
minute: Some(3),
|
||||
second: Some(8),
|
||||
|
@ -140,8 +136,8 @@ mod test {
|
|||
fn fmt_partial_offset() {
|
||||
let dt = DateTime {
|
||||
year: 2023,
|
||||
month: Some(5),
|
||||
day: Some(7),
|
||||
month: 5,
|
||||
day: 7,
|
||||
hour: Some(9),
|
||||
minute: Some(3),
|
||||
second: Some(8),
|
||||
|
@ -158,8 +154,8 @@ mod test {
|
|||
fn fmt_utc() {
|
||||
let dt = DateTime {
|
||||
year: 2023,
|
||||
month: Some(5),
|
||||
day: Some(7),
|
||||
month: 5,
|
||||
day: 7,
|
||||
hour: Some(9),
|
||||
minute: Some(3),
|
||||
second: Some(8),
|
||||
|
@ -172,8 +168,8 @@ mod test {
|
|||
fn fmt_no_tz() {
|
||||
let dt = DateTime {
|
||||
year: 2023,
|
||||
month: Some(5),
|
||||
day: Some(7),
|
||||
month: 5,
|
||||
day: 7,
|
||||
hour: Some(9),
|
||||
minute: Some(3),
|
||||
second: Some(8),
|
||||
|
@ -186,8 +182,8 @@ mod test {
|
|||
fn fmt_no_seconds() {
|
||||
let dt = DateTime {
|
||||
year: 2023,
|
||||
month: Some(5),
|
||||
day: Some(7),
|
||||
month: 5,
|
||||
day: 7,
|
||||
hour: Some(9),
|
||||
minute: Some(3),
|
||||
second: None,
|
||||
|
@ -200,8 +196,8 @@ mod test {
|
|||
fn fmt_no_minutes() {
|
||||
let dt = DateTime {
|
||||
year: 2023,
|
||||
month: Some(5),
|
||||
day: Some(7),
|
||||
month: 5,
|
||||
day: 7,
|
||||
hour: Some(9),
|
||||
minute: None,
|
||||
second: Some(50),
|
||||
|
@ -214,8 +210,8 @@ mod test {
|
|||
fn fmt_no_time() {
|
||||
let dt = DateTime {
|
||||
year: 2023,
|
||||
month: Some(5),
|
||||
day: Some(7),
|
||||
month: 5,
|
||||
day: 7,
|
||||
hour: None,
|
||||
minute: None,
|
||||
second: Some(50),
|
||||
|
@ -223,32 +219,4 @@ mod test {
|
|||
};
|
||||
assert_eq!(dt.to_string(), "2023-05-07Z");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fmt_no_day() {
|
||||
let dt = DateTime {
|
||||
year: 2023,
|
||||
month: Some(5),
|
||||
day: None,
|
||||
hour: Some(20),
|
||||
minute: None,
|
||||
second: Some(50),
|
||||
tz: None,
|
||||
};
|
||||
assert_eq!(dt.to_string(), "2023-05");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fmt_no_month() {
|
||||
let dt = DateTime {
|
||||
year: 2023,
|
||||
month: None,
|
||||
day: None,
|
||||
hour: Some(20),
|
||||
minute: None,
|
||||
second: Some(50),
|
||||
tz: None,
|
||||
};
|
||||
assert_eq!(dt.to_string(), "2023");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//! Implements a parser for ISO-8601 format Time
|
||||
use super::{DateTime, Error, Sign, TimeZone, Offset};
|
||||
use super::{DateTime, Error, Offset, Sign, TimeZone};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum Format {
|
||||
|
@ -62,7 +62,7 @@ impl<'a> Parser<'a> {
|
|||
} else {
|
||||
28
|
||||
}
|
||||
},
|
||||
}
|
||||
Some(4 | 6 | 9 | 11) => 30,
|
||||
_ => return Err(Error::InvalidMonth),
|
||||
};
|
||||
|
@ -155,14 +155,14 @@ impl<'a> Parser<'a> {
|
|||
if self.buffer.len() == 5 {
|
||||
self.mode = Mode::Finish;
|
||||
}
|
||||
},
|
||||
}
|
||||
Format::Extended => {
|
||||
if self.buffer.len() > 3 && !self.sep {
|
||||
return Err(Error::MissingSeparator);
|
||||
} else if self.buffer.len() == 5 {
|
||||
self.mode = Mode::Finish;
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
Mode::Finish => return Err(Error::TrailingGarbage),
|
||||
}
|
||||
|
@ -179,14 +179,14 @@ impl<'a> Parser<'a> {
|
|||
self.format = Format::Extended;
|
||||
self.sep = true;
|
||||
}
|
||||
},
|
||||
}
|
||||
Mode::Day => {
|
||||
if self.day.is_some() || self.format == Format::Basic {
|
||||
return Err(Error::UnexpectedChar(self.mode, '-'));
|
||||
} else {
|
||||
self.sep = true;
|
||||
}
|
||||
},
|
||||
}
|
||||
Mode::Hour | Mode::Minute | Mode::Second => {
|
||||
if !self.buffer.is_empty() {
|
||||
return Err(Error::UnexpectedChar(self.mode, '-'));
|
||||
|
@ -194,14 +194,14 @@ impl<'a> Parser<'a> {
|
|||
self.buffer.push('-');
|
||||
self.mode = Mode::TimeZone;
|
||||
}
|
||||
},
|
||||
}
|
||||
Mode::TimeZone => {
|
||||
if self.buffer.is_empty() {
|
||||
self.buffer.push('-');
|
||||
} else {
|
||||
return Err(Error::UnexpectedChar(self.mode, '-'));
|
||||
}
|
||||
},
|
||||
}
|
||||
Mode::Finish => return Err(Error::TrailingGarbage),
|
||||
}
|
||||
Ok(())
|
||||
|
@ -209,21 +209,23 @@ impl<'a> Parser<'a> {
|
|||
|
||||
fn colon(&mut self) -> Result<(), Error> {
|
||||
match self.mode {
|
||||
Mode::Year | Mode::Month | Mode::Day => return Err(Error::UnexpectedChar(self.mode, ':')),
|
||||
Mode::Year | Mode::Month | Mode::Day => {
|
||||
return Err(Error::UnexpectedChar(self.mode, ':'))
|
||||
}
|
||||
Mode::Hour | Mode::Minute | Mode::Second => {
|
||||
if !self.buffer.is_empty() || self.format == Format::Basic {
|
||||
return Err(Error::UnexpectedChar(self.mode, ':'));
|
||||
} else {
|
||||
self.sep = true;
|
||||
}
|
||||
},
|
||||
}
|
||||
Mode::TimeZone => {
|
||||
if !self.buffer.len() == 2 || self.format == Format::Basic {
|
||||
return Err(Error::UnexpectedChar(self.mode, ':'));
|
||||
} else {
|
||||
self.sep = true;
|
||||
}
|
||||
},
|
||||
}
|
||||
Mode::Finish => return Err(Error::TrailingGarbage),
|
||||
}
|
||||
Ok(())
|
||||
|
@ -239,7 +241,11 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
|
||||
fn zed(&mut self) -> Result<(), Error> {
|
||||
if self.mode == Mode::Year || !self.buffer.is_empty() {
|
||||
if self.mode == Mode::Year
|
||||
|| self.mode == Mode::Month
|
||||
|| self.mode == Mode::Day
|
||||
|| !self.buffer.is_empty()
|
||||
{
|
||||
return Err(Error::UnexpectedChar(self.mode, 'Z'));
|
||||
} else {
|
||||
self.tz = Some(TimeZone::UTC);
|
||||
|
@ -248,6 +254,17 @@ impl<'a> Parser<'a> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn plus(&mut self) -> Result<(), Error> {
|
||||
if self.mode != Mode::TimeZone && self.mode != Mode::Finish || !self.buffer.is_empty() {
|
||||
return Err(Error::UnexpectedChar(self.mode, '+'));
|
||||
} else if self.mode == Mode::Year || self.mode == Mode::Month || self.mode == Mode::Day {
|
||||
return Err(Error::UnexpectedChar(self.mode, '+'));
|
||||
} else {
|
||||
self.buffer.push('+');
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_tz_basic(&mut self) -> Result<(), Error> {
|
||||
let sign = match self.buffer.chars().next() {
|
||||
Some('+') => Sign::Positive,
|
||||
|
@ -257,8 +274,9 @@ impl<'a> Parser<'a> {
|
|||
let Some(n) = self.buffer.get(1..3) else {
|
||||
return Err(Error::InvalidOffset);
|
||||
};
|
||||
println!("Hours: {n}");
|
||||
let hours: u8 = n.parse()?;
|
||||
let minutes: Option<u8> = if let Some(n) = self.buffer.get(3..) {
|
||||
let minutes: Option<u8> = if let Some(n) = self.buffer.get(3..5) {
|
||||
Some(n.parse()?)
|
||||
} else {
|
||||
None
|
||||
|
@ -266,13 +284,19 @@ impl<'a> Parser<'a> {
|
|||
match (hours, minutes) {
|
||||
(h, None) if h > 12 => return Err(Error::InvalidOffset),
|
||||
(h, Some(m)) if h == 12 && m > 0 || m > 59 => return Err(Error::InvalidOffset),
|
||||
_ => self.tz = Some(TimeZone::Offset(Offset { sign, hours, minutes })),
|
||||
_ => {
|
||||
self.tz = Some(TimeZone::Offset(Offset {
|
||||
sign,
|
||||
hours,
|
||||
minutes,
|
||||
}))
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_tz_extended(&mut self) -> Result<(), Error> {
|
||||
if !self.sep {
|
||||
if self.buffer.len() > 3 && !self.sep {
|
||||
Err(Error::MissingSeparator)
|
||||
} else {
|
||||
self.parse_tz_basic()
|
||||
|
@ -287,6 +311,7 @@ impl<'a> Parser<'a> {
|
|||
':' => self.colon()?,
|
||||
'T' => self.tee()?,
|
||||
'Z' => self.zed()?,
|
||||
'+' => self.plus()?,
|
||||
_ => return Err(Error::UnexpectedChar(self.mode, c)),
|
||||
}
|
||||
}
|
||||
|
@ -301,13 +326,16 @@ impl<'a> Parser<'a> {
|
|||
Format::Extended => self.parse_tz_extended()?,
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
_ => return Err(Error::Truncated),
|
||||
}
|
||||
if self.year.is_none() || self.month.is_none() || self.day.is_none() {
|
||||
return Err(Error::Truncated);
|
||||
}
|
||||
Ok(DateTime {
|
||||
year: self.year.unwrap(),
|
||||
month: self.month,
|
||||
day: self.day,
|
||||
month: self.month.unwrap(),
|
||||
day: self.day.unwrap(),
|
||||
hour: self.hour,
|
||||
minute: self.minute,
|
||||
second: self.second,
|
||||
|
@ -325,12 +353,91 @@ mod tests {
|
|||
let parser = Parser::new("2023-05-09T19:39:15Z");
|
||||
let dt = parser.parse().unwrap();
|
||||
assert_eq!(dt.year, 2023);
|
||||
assert_eq!(dt.month, Some(5));
|
||||
assert_eq!(dt.day, Some(9));
|
||||
assert_eq!(dt.month, 5);
|
||||
assert_eq!(dt.day, 9);
|
||||
assert_eq!(dt.hour, Some(19));
|
||||
assert_eq!(dt.minute, Some(39));
|
||||
assert_eq!(dt.second, Some(15));
|
||||
assert_eq!(dt.tz, Some(TimeZone::UTC));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_full_positive() {
|
||||
let parser = Parser::new("2023-05-09T19:39:15+05:15");
|
||||
let dt = parser.parse().unwrap();
|
||||
assert_eq!(dt.year, 2023);
|
||||
assert_eq!(dt.month, 5);
|
||||
assert_eq!(dt.day, 9);
|
||||
assert_eq!(dt.hour, Some(19));
|
||||
assert_eq!(dt.minute, Some(39));
|
||||
assert_eq!(dt.second, Some(15));
|
||||
assert_eq!(
|
||||
dt.tz,
|
||||
Some(TimeZone::Offset(Offset {
|
||||
sign: Sign::Positive,
|
||||
hours: 5,
|
||||
minutes: Some(15)
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_full_negative() {
|
||||
let parser = Parser::new("2023-05-09T19:39:15-05");
|
||||
let dt = parser.parse().unwrap();
|
||||
assert_eq!(dt.year, 2023);
|
||||
assert_eq!(dt.month, 5);
|
||||
assert_eq!(dt.day, 9);
|
||||
assert_eq!(dt.hour, Some(19));
|
||||
assert_eq!(dt.minute, Some(39));
|
||||
assert_eq!(dt.second, Some(15));
|
||||
assert_eq!(
|
||||
dt.tz,
|
||||
Some(TimeZone::Offset(Offset {
|
||||
sign: Sign::Negative,
|
||||
hours: 5,
|
||||
minutes: None
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_full_no_seconds() {
|
||||
let parser = Parser::new("2023-05-09T19:39-05");
|
||||
let dt = parser.parse().unwrap();
|
||||
assert!(dt.second.is_none());
|
||||
assert_eq!(
|
||||
dt.tz,
|
||||
Some(TimeZone::Offset(Offset {
|
||||
sign: Sign::Negative,
|
||||
hours: 5,
|
||||
minutes: None
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_full_no_minutes() {
|
||||
let parser = Parser::new("2023-05-09T19-05");
|
||||
let dt = parser.parse().unwrap();
|
||||
assert!(dt.second.is_none());
|
||||
assert!(dt.minute.is_none());
|
||||
assert_eq!(
|
||||
dt.tz,
|
||||
Some(TimeZone::Offset(Offset {
|
||||
sign: Sign::Negative,
|
||||
hours: 5,
|
||||
minutes: None
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_full_no_time() {
|
||||
let parser = Parser::new("2023-05-09");
|
||||
let dt = parser.parse().unwrap();
|
||||
assert!(dt.second.is_none());
|
||||
assert!(dt.minute.is_none());
|
||||
assert!(dt.tz.is_none());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue