Added some tests for time::Parser

This commit is contained in:
Nathan Fisher 2023-06-10 00:08:33 -04:00
parent 0bbd11aa03
commit de27339b73
3 changed files with 148 additions and 73 deletions

View file

@ -1,5 +1,5 @@
use std::{fmt, num::ParseIntError};
use super::parser::Mode;
use std::{fmt, num::ParseIntError};
#[derive(Debug)]
pub enum Error {

View file

@ -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;
}
write!(f, "-{field:02}")?;
}
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");
}
}

View file

@ -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 {
@ -55,15 +55,15 @@ impl<'a> Parser<'a> {
fn validate_day(&self, day: u8) -> Result<(), Error> {
let max = match self.month {
Some(1|3|5|7|10|12) => 31,
Some(1 | 3 | 5 | 7 | 10 | 12) => 31,
Some(2) => {
if self.year.unwrap() % 4 == 0 {
29
} else {
28
}
},
Some(4|6|9|11) => 30,
}
Some(4 | 6 | 9 | 11) => 30,
_ => return Err(Error::InvalidMonth),
};
if day > max {
@ -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());
}
}