diff --git a/src/time/parser.rs b/src/time/parser.rs index 96f6125..b2aa60f 100644 --- a/src/time/parser.rs +++ b/src/time/parser.rs @@ -1,5 +1,5 @@ //! Implements a parser for ISO-8601 format Time -use super::{DateTime, Error, TimeZone}; +use super::{DateTime, Error, Sign, TimeZone, Offset}; #[derive(Debug, PartialEq)] 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> { self.buffer.push(c); match self.mode { @@ -67,6 +87,9 @@ impl<'a> Parser<'a> { Mode::Month => { if self.buffer.len() == 2 { let m: u8 = self.buffer.parse()?; + if m > 12 { + return Err(Error::InvalidMonth); + } self.buffer.clear(); self.month = Some(m); self.mode = Mode::Day; @@ -76,6 +99,7 @@ impl<'a> Parser<'a> { Mode::Day => { if self.buffer.len() == 2 { let d: u8 = self.buffer.parse()?; + self.validate_day(d)?; self.buffer.clear(); self.day = Some(d); self.mode = Mode::Hour; @@ -87,6 +111,9 @@ impl<'a> Parser<'a> { Mode::Hour => { if self.buffer.len() == 2 { let h: u8 = self.buffer.parse()?; + if h > 23 { + return Err(Error::InvalidHour); + } self.buffer.clear(); self.hour = Some(h); self.mode = Mode::Minute; @@ -98,6 +125,9 @@ impl<'a> Parser<'a> { Mode::Minute => { if self.buffer.len() == 2 { let m = self.buffer.parse()?; + if m > 59 { + return Err(Error::InvalidMinute); + } self.buffer.clear(); self.minute = Some(m); self.mode = Mode::Second; @@ -109,6 +139,9 @@ impl<'a> Parser<'a> { Mode::Second => { if self.buffer.len() == 2 { let s = self.buffer.parse()?; + if s > 59 { + return Err(Error::InvalidSecond); + } self.buffer.clear(); self.second = Some(s); self.mode = Mode::TimeZone; @@ -136,7 +169,7 @@ impl<'a> Parser<'a> { Ok(()) } - fn dash(&mut self, c: char) -> Result<(), Error> { + fn dash(&mut self) -> Result<(), Error> { match self.mode { Mode::Year => return Err(Error::UnexpectedChar(Mode::Year, '-')), Mode::Month => { @@ -174,7 +207,7 @@ impl<'a> Parser<'a> { Ok(()) } - fn colon(&mut self, c: char) -> Result<(), Error> { + fn colon(&mut self) -> Result<(), Error> { match self.mode { Mode::Year | Mode::Month | Mode::Day => return Err(Error::UnexpectedChar(self.mode, ':')), Mode::Hour | Mode::Minute | Mode::Second => { @@ -196,7 +229,7 @@ impl<'a> Parser<'a> { Ok(()) } - fn tee(&mut self, c: char) -> Result<(), Error> { + fn tee(&mut self) -> Result<(), Error> { if self.mode != Mode::Hour || !self.buffer.is_empty() { Err(Error::UnexpectedChar(self.mode, 'T')) } 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() { return Err(Error::UnexpectedChar(self.mode, 'Z')); } else { @@ -216,7 +249,26 @@ impl<'a> Parser<'a> { } 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 = 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> { @@ -231,10 +283,10 @@ impl<'a> Parser<'a> { for c in self.text.chars() { match c { x if x.is_numeric() => self.number(c)?, - '-' => self.dash(c)?, - ':' => self.colon(c)?, - 'T' => self.tee(c)?, - 'Z' => self.zed(c)?, + '-' => self.dash()?, + ':' => self.colon()?, + 'T' => self.tee()?, + 'Z' => self.zed()?, _ => 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() { return Err(Error::TrailingGarbage); } - match self.format { - Format::Basic => self.parse_tz_basic()?, - Format::Extended => self.parse_tz_extended()?, + if self.tz.is_none() { + match self.format { + Format::Basic => self.parse_tz_basic()?, + Format::Extended => self.parse_tz_extended()?, + } } }, _ => 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)); + } +} +