Finish time::parser and begin adding tests
This commit is contained in:
parent
db1680d46f
commit
0bbd11aa03
1 changed files with 86 additions and 13 deletions
|
@ -1,5 +1,5 @@
|
||||||
//! Implements a parser for ISO-8601 format Time
|
//! Implements a parser for ISO-8601 format Time
|
||||||
use super::{DateTime, Error, TimeZone};
|
use super::{DateTime, Error, Sign, TimeZone, Offset};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
enum Format {
|
enum Format {
|
||||||
|
@ -53,6 +53,26 @@ 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(2) => {
|
||||||
|
if self.year.unwrap() % 4 == 0 {
|
||||||
|
29
|
||||||
|
} else {
|
||||||
|
28
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Some(4|6|9|11) => 30,
|
||||||
|
_ => return Err(Error::InvalidMonth),
|
||||||
|
};
|
||||||
|
if day > max {
|
||||||
|
Err(Error::InvalidDay)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn number(&mut self, c: char) -> Result<(), Error> {
|
fn number(&mut self, c: char) -> Result<(), Error> {
|
||||||
self.buffer.push(c);
|
self.buffer.push(c);
|
||||||
match self.mode {
|
match self.mode {
|
||||||
|
@ -67,6 +87,9 @@ impl<'a> Parser<'a> {
|
||||||
Mode::Month => {
|
Mode::Month => {
|
||||||
if self.buffer.len() == 2 {
|
if self.buffer.len() == 2 {
|
||||||
let m: u8 = self.buffer.parse()?;
|
let m: u8 = self.buffer.parse()?;
|
||||||
|
if m > 12 {
|
||||||
|
return Err(Error::InvalidMonth);
|
||||||
|
}
|
||||||
self.buffer.clear();
|
self.buffer.clear();
|
||||||
self.month = Some(m);
|
self.month = Some(m);
|
||||||
self.mode = Mode::Day;
|
self.mode = Mode::Day;
|
||||||
|
@ -76,6 +99,7 @@ impl<'a> Parser<'a> {
|
||||||
Mode::Day => {
|
Mode::Day => {
|
||||||
if self.buffer.len() == 2 {
|
if self.buffer.len() == 2 {
|
||||||
let d: u8 = self.buffer.parse()?;
|
let d: u8 = self.buffer.parse()?;
|
||||||
|
self.validate_day(d)?;
|
||||||
self.buffer.clear();
|
self.buffer.clear();
|
||||||
self.day = Some(d);
|
self.day = Some(d);
|
||||||
self.mode = Mode::Hour;
|
self.mode = Mode::Hour;
|
||||||
|
@ -87,6 +111,9 @@ impl<'a> Parser<'a> {
|
||||||
Mode::Hour => {
|
Mode::Hour => {
|
||||||
if self.buffer.len() == 2 {
|
if self.buffer.len() == 2 {
|
||||||
let h: u8 = self.buffer.parse()?;
|
let h: u8 = self.buffer.parse()?;
|
||||||
|
if h > 23 {
|
||||||
|
return Err(Error::InvalidHour);
|
||||||
|
}
|
||||||
self.buffer.clear();
|
self.buffer.clear();
|
||||||
self.hour = Some(h);
|
self.hour = Some(h);
|
||||||
self.mode = Mode::Minute;
|
self.mode = Mode::Minute;
|
||||||
|
@ -98,6 +125,9 @@ impl<'a> Parser<'a> {
|
||||||
Mode::Minute => {
|
Mode::Minute => {
|
||||||
if self.buffer.len() == 2 {
|
if self.buffer.len() == 2 {
|
||||||
let m = self.buffer.parse()?;
|
let m = self.buffer.parse()?;
|
||||||
|
if m > 59 {
|
||||||
|
return Err(Error::InvalidMinute);
|
||||||
|
}
|
||||||
self.buffer.clear();
|
self.buffer.clear();
|
||||||
self.minute = Some(m);
|
self.minute = Some(m);
|
||||||
self.mode = Mode::Second;
|
self.mode = Mode::Second;
|
||||||
|
@ -109,6 +139,9 @@ impl<'a> Parser<'a> {
|
||||||
Mode::Second => {
|
Mode::Second => {
|
||||||
if self.buffer.len() == 2 {
|
if self.buffer.len() == 2 {
|
||||||
let s = self.buffer.parse()?;
|
let s = self.buffer.parse()?;
|
||||||
|
if s > 59 {
|
||||||
|
return Err(Error::InvalidSecond);
|
||||||
|
}
|
||||||
self.buffer.clear();
|
self.buffer.clear();
|
||||||
self.second = Some(s);
|
self.second = Some(s);
|
||||||
self.mode = Mode::TimeZone;
|
self.mode = Mode::TimeZone;
|
||||||
|
@ -136,7 +169,7 @@ impl<'a> Parser<'a> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dash(&mut self, c: char) -> Result<(), Error> {
|
fn dash(&mut self) -> Result<(), Error> {
|
||||||
match self.mode {
|
match self.mode {
|
||||||
Mode::Year => return Err(Error::UnexpectedChar(Mode::Year, '-')),
|
Mode::Year => return Err(Error::UnexpectedChar(Mode::Year, '-')),
|
||||||
Mode::Month => {
|
Mode::Month => {
|
||||||
|
@ -174,7 +207,7 @@ impl<'a> Parser<'a> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn colon(&mut self, c: char) -> Result<(), Error> {
|
fn colon(&mut self) -> Result<(), Error> {
|
||||||
match self.mode {
|
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 => {
|
Mode::Hour | Mode::Minute | Mode::Second => {
|
||||||
|
@ -196,7 +229,7 @@ impl<'a> Parser<'a> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tee(&mut self, c: char) -> Result<(), Error> {
|
fn tee(&mut self) -> Result<(), Error> {
|
||||||
if self.mode != Mode::Hour || !self.buffer.is_empty() {
|
if self.mode != Mode::Hour || !self.buffer.is_empty() {
|
||||||
Err(Error::UnexpectedChar(self.mode, 'T'))
|
Err(Error::UnexpectedChar(self.mode, 'T'))
|
||||||
} else {
|
} else {
|
||||||
|
@ -205,7 +238,7 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn zed(&mut self, c: char) -> Result<(), Error> {
|
fn zed(&mut self) -> Result<(), Error> {
|
||||||
if self.mode == Mode::Year || !self.buffer.is_empty() {
|
if self.mode == Mode::Year || !self.buffer.is_empty() {
|
||||||
return Err(Error::UnexpectedChar(self.mode, 'Z'));
|
return Err(Error::UnexpectedChar(self.mode, 'Z'));
|
||||||
} else {
|
} else {
|
||||||
|
@ -216,7 +249,26 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_tz_basic(&mut self) -> Result<(), Error> {
|
fn parse_tz_basic(&mut self) -> Result<(), Error> {
|
||||||
todo!()
|
let sign = match self.buffer.chars().next() {
|
||||||
|
Some('+') => Sign::Positive,
|
||||||
|
Some('-') => Sign::Negative,
|
||||||
|
_ => return Err(Error::InvalidOffset),
|
||||||
|
};
|
||||||
|
let Some(n) = self.buffer.get(1..3) else {
|
||||||
|
return Err(Error::InvalidOffset);
|
||||||
|
};
|
||||||
|
let hours: u8 = n.parse()?;
|
||||||
|
let minutes: Option<u8> = if let Some(n) = self.buffer.get(3..) {
|
||||||
|
Some(n.parse()?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
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 })),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_tz_extended(&mut self) -> Result<(), Error> {
|
fn parse_tz_extended(&mut self) -> Result<(), Error> {
|
||||||
|
@ -231,10 +283,10 @@ impl<'a> Parser<'a> {
|
||||||
for c in self.text.chars() {
|
for c in self.text.chars() {
|
||||||
match c {
|
match c {
|
||||||
x if x.is_numeric() => self.number(c)?,
|
x if x.is_numeric() => self.number(c)?,
|
||||||
'-' => self.dash(c)?,
|
'-' => self.dash()?,
|
||||||
':' => self.colon(c)?,
|
':' => self.colon()?,
|
||||||
'T' => self.tee(c)?,
|
'T' => self.tee()?,
|
||||||
'Z' => self.zed(c)?,
|
'Z' => self.zed()?,
|
||||||
_ => return Err(Error::UnexpectedChar(self.mode, c)),
|
_ => return Err(Error::UnexpectedChar(self.mode, c)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -243,9 +295,11 @@ impl<'a> Parser<'a> {
|
||||||
if self.tz == Some(TimeZone::UTC) && !self.buffer.is_empty() {
|
if self.tz == Some(TimeZone::UTC) && !self.buffer.is_empty() {
|
||||||
return Err(Error::TrailingGarbage);
|
return Err(Error::TrailingGarbage);
|
||||||
}
|
}
|
||||||
match self.format {
|
if self.tz.is_none() {
|
||||||
Format::Basic => self.parse_tz_basic()?,
|
match self.format {
|
||||||
Format::Extended => self.parse_tz_extended()?,
|
Format::Basic => self.parse_tz_basic()?,
|
||||||
|
Format::Extended => self.parse_tz_extended()?,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => return Err(Error::Truncated),
|
_ => return Err(Error::Truncated),
|
||||||
|
@ -261,3 +315,22 @@ impl<'a> Parser<'a> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_full_utc() {
|
||||||
|
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.hour, Some(19));
|
||||||
|
assert_eq!(dt.minute, Some(39));
|
||||||
|
assert_eq!(dt.second, Some(15));
|
||||||
|
assert_eq!(dt.tz, Some(TimeZone::UTC));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue