Properly handle timezone offsets; Implement conversion to/from Unix
timestamps (i64);
This commit is contained in:
parent
03ac43b821
commit
8c6e15a0c3
5 changed files with 160 additions and 451 deletions
|
@ -16,7 +16,10 @@ pub struct Parser {
|
|||
|
||||
impl Parser {
|
||||
pub fn new(id: &str) -> Self {
|
||||
Self { id: id.to_string(), ..Default::default() }
|
||||
Self {
|
||||
id: id.to_string(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(mut self, content: &str) -> Result<Message, super::Error> {
|
||||
|
|
|
@ -1,114 +0,0 @@
|
|||
use super::{DateTime, TimeZone};
|
||||
|
||||
pub const SECS_PER_DAY: i64 = 86400;
|
||||
|
||||
fn days_since_epoch(ts: i64) -> i64 {
|
||||
ts / SECS_PER_DAY
|
||||
}
|
||||
|
||||
fn is_leap(year: i64) -> bool {
|
||||
year % 4 == 0 && (year % 100 != 0 || year % 16 == 0)
|
||||
}
|
||||
|
||||
fn year_from_ts_with_remainder(ts: i64) -> (i64, i64) {
|
||||
let mut year = 1970;
|
||||
let mut days = days_since_epoch(ts);
|
||||
loop {
|
||||
let days_in_year = if is_leap(year) {
|
||||
366
|
||||
} else {
|
||||
365
|
||||
};
|
||||
if days > days_in_year {
|
||||
days -= days_in_year;
|
||||
year += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
(year, days)
|
||||
}
|
||||
|
||||
fn days_in_month(month: u8, year: i64) -> i64 {
|
||||
assert!(month < 13);
|
||||
match month {
|
||||
1 | 3 | 5 | 7 | 10 | 12 => 31,
|
||||
2 => {
|
||||
if is_leap(year) {
|
||||
29
|
||||
} else {
|
||||
28
|
||||
}
|
||||
},
|
||||
4 | 6 | 9 | 11 => 30,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn month_and_days_from_ordinal(days: i64, year: i64) -> (u8, i64) {
|
||||
assert!(
|
||||
if is_leap(year) {
|
||||
days < 366
|
||||
} else {
|
||||
days < 365
|
||||
}
|
||||
);
|
||||
let mut days = days;
|
||||
let mut month = 1;
|
||||
while days_in_month(month, year) <= days {
|
||||
days -= days_in_month(month, year);
|
||||
month += 1;
|
||||
}
|
||||
(month, days)
|
||||
}
|
||||
|
||||
pub(super) fn date_time_from_ts(ts: i64) -> DateTime {
|
||||
let (year, days) = year_from_ts_with_remainder(ts);
|
||||
let (month, days) = month_and_days_from_ordinal(days, year);
|
||||
let seconds = ts % SECS_PER_DAY;
|
||||
let minutes = seconds / 60;
|
||||
let second = seconds % 60;
|
||||
let hour = minutes / 60;
|
||||
let minute = minutes % 60;
|
||||
DateTime {
|
||||
year: year as u32,
|
||||
month: month.try_into().unwrap(),
|
||||
day: (days + 1).try_into().unwrap(),
|
||||
hour: Some((hour + 1).try_into().unwrap()),
|
||||
minute: Some((minute).try_into().unwrap()),
|
||||
second: Some(second.try_into().unwrap()),
|
||||
tz: Some(TimeZone::UTC),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn get_year() {
|
||||
let ts = 1686459092;
|
||||
let (y, r) = year_from_ts_with_remainder(ts);
|
||||
assert_eq!(y, 2023);
|
||||
assert_eq!(r, 161);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_month_and_days() {
|
||||
let ts = 1686459092;
|
||||
let (year, days) = year_from_ts_with_remainder(ts);
|
||||
let (month, days) = month_and_days_from_ordinal(days, year);
|
||||
assert_eq!(month, 6);
|
||||
assert_eq!(days, 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_datetime() {
|
||||
let ts = 1686462068;
|
||||
let dt = date_time_from_ts(ts);
|
||||
assert_eq!(dt.day, 11);
|
||||
assert_eq!(dt.hour, Some(6));
|
||||
assert_eq!(dt.minute, Some(41));
|
||||
assert_eq!(dt.second, Some(8));
|
||||
}
|
||||
}
|
181
src/time/mod.rs
181
src/time/mod.rs
|
@ -1,7 +1,6 @@
|
|||
//! A container representing a moment in ISO-8601 format Time
|
||||
use std::{fmt, str::FromStr};
|
||||
|
||||
pub mod epoch;
|
||||
mod error;
|
||||
mod parser;
|
||||
pub use {error::Error, parser::Parser};
|
||||
|
@ -9,6 +8,32 @@ pub use {error::Error, parser::Parser};
|
|||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const SECS_PER_DAY: i64 = 86400;
|
||||
|
||||
fn days_since_epoch(ts: i64) -> i64 {
|
||||
ts / SECS_PER_DAY
|
||||
}
|
||||
|
||||
fn is_leap(year: i64) -> bool {
|
||||
year % 4 == 0 && (year % 100 != 0 || year % 16 == 0)
|
||||
}
|
||||
|
||||
pub fn days_in_month(month: u8, year: i64) -> i64 {
|
||||
assert!(month < 13);
|
||||
match month {
|
||||
1 | 3 | 5 | 7 | 10 | 12 => 31,
|
||||
2 => {
|
||||
if is_leap(year) {
|
||||
29
|
||||
} else {
|
||||
28
|
||||
}
|
||||
}
|
||||
4 | 6 | 9 | 11 => 30,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
|
||||
pub enum Sign {
|
||||
|
@ -113,10 +138,9 @@ impl fmt::Display for DateTime {
|
|||
|
||||
impl PartialOrd for DateTime {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
let a = self.normalize()?;
|
||||
let b = other.normalize()?;
|
||||
(a.year, a.month, a.day, a.hour, a.minute, a.second)
|
||||
.partial_cmp(&(b.year, b.month, b.day, b.hour, b.minute, b.second))
|
||||
let a: i64 = (*self).into();
|
||||
let b: i64 = (*other).into();
|
||||
a.partial_cmp(&b)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,38 +152,90 @@ impl FromStr for DateTime {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<i64> for DateTime {
|
||||
fn from(ts: i64) -> Self {
|
||||
// Only allow using this function for positive timestamp values,
|
||||
// ie no dates before 1970-01-01
|
||||
assert!(ts.is_positive());
|
||||
|
||||
let mut year = 1970;
|
||||
let mut days = days_since_epoch(ts);
|
||||
loop {
|
||||
let days_in_year = if is_leap(year) { 366 } else { 365 };
|
||||
if days > days_in_year {
|
||||
days -= days_in_year;
|
||||
year += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let mut month = 1;
|
||||
while days_in_month(month, year) <= days {
|
||||
days -= days_in_month(month, year);
|
||||
month += 1;
|
||||
}
|
||||
let seconds = ts % SECS_PER_DAY;
|
||||
let minutes = seconds / 60;
|
||||
let second = seconds % 60;
|
||||
let hour = minutes / 60 + 1;
|
||||
let minute = minutes % 60;
|
||||
DateTime {
|
||||
year: year as u32,
|
||||
month: month.try_into().unwrap(),
|
||||
day: (days + 1).try_into().unwrap(),
|
||||
hour: Some((hour).try_into().unwrap()),
|
||||
minute: Some((minute).try_into().unwrap()),
|
||||
second: Some(second.try_into().unwrap()),
|
||||
tz: Some(TimeZone::UTC),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DateTime> for i64 {
|
||||
fn from(dt: DateTime) -> Self {
|
||||
let mut seconds = dt.second.unwrap_or(0).into();
|
||||
seconds += dt.minute.unwrap_or(0) as i64 * 60;
|
||||
seconds += (dt.hour.unwrap_or(1) - 1) as i64 * 3600;
|
||||
seconds += SECS_PER_DAY * (dt.day as i64 - 1);
|
||||
let mut m = 1;
|
||||
while m < dt.month {
|
||||
seconds += SECS_PER_DAY * days_in_month(m, dt.year.into());
|
||||
m += 1;
|
||||
}
|
||||
let mut y = 1970;
|
||||
while y < dt.year {
|
||||
if is_leap(y.into()) {
|
||||
seconds += SECS_PER_DAY * 366;
|
||||
} else {
|
||||
seconds += SECS_PER_DAY * 365;
|
||||
}
|
||||
y += 1;
|
||||
}
|
||||
if let Some(TimeZone::Offset(offset)) = dt.tz {
|
||||
let mut offset_seconds = offset.hours as i64 * 3600;
|
||||
if let Some(offset_minutes) = offset.minutes {
|
||||
offset_seconds += offset_minutes as i64 * 60;
|
||||
}
|
||||
match offset.sign {
|
||||
Sign::Positive => seconds += offset_seconds,
|
||||
Sign::Negative => seconds -= offset_seconds,
|
||||
}
|
||||
}
|
||||
seconds
|
||||
}
|
||||
}
|
||||
|
||||
impl DateTime {
|
||||
pub fn normalize(&self) -> Option<Self> {
|
||||
if self.tz == Some(TimeZone::UTC) {
|
||||
Some(*self)
|
||||
} else if let Some(TimeZone::Offset(tz)) = self.tz {
|
||||
let hour = if let Some(hour) = self.hour {
|
||||
match tz.sign {
|
||||
Sign::Positive => Some(hour + tz.hours),
|
||||
Sign::Negative => Some(hour - tz.hours),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let minute = 'blk: {
|
||||
if let Some(minutes) = self.minute {
|
||||
if let Some(o) = tz.minutes {
|
||||
match tz.sign {
|
||||
Sign::Positive => break 'blk Some(minutes + o),
|
||||
Sign::Negative => break 'blk Some(minutes - o),
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
};
|
||||
Some(DateTime {
|
||||
hour,
|
||||
minute,
|
||||
..*self
|
||||
})
|
||||
} else {
|
||||
None
|
||||
/// Expresses this `DateTime` as an equivalent with the timezone
|
||||
/// set to UTC. If the timezone is already UTC, it will be returned
|
||||
/// unchanged. If there is no timezone set, it is assumed to be UTC
|
||||
/// and set accordingly. If the timezone is offset from UTC, the
|
||||
/// offset is applied and the result returned.
|
||||
pub fn normalize(&mut self) -> &Self {
|
||||
if let Some(TimeZone::Offset(_)) = self.tz {
|
||||
*self = i64::from(*self).into();
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -290,4 +366,39 @@ mod test {
|
|||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_timestamp() {
|
||||
let ts = 1686462068;
|
||||
let dt: DateTime = ts.into();
|
||||
assert_eq!(dt.year, 2023);
|
||||
assert_eq!(dt.month, 6);
|
||||
assert_eq!(dt.day, 11);
|
||||
assert_eq!(dt.hour, Some(6));
|
||||
assert_eq!(dt.minute, Some(41));
|
||||
assert_eq!(dt.second, Some(8));
|
||||
assert_eq!(ts, dt.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compare_offset() {
|
||||
let a: DateTime = "2023-06-11T02:07:16-05".parse().unwrap();
|
||||
let b: DateTime = "2023-06-11T02:07:16Z".parse().unwrap();
|
||||
assert!(b > a);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize() {
|
||||
let mut dt: DateTime = "2023-06-11T22:29:48+05".parse().unwrap();
|
||||
assert_eq!(
|
||||
dt.tz.unwrap(),
|
||||
TimeZone::Offset(Offset {
|
||||
sign: Sign::Positive,
|
||||
hours: 5,
|
||||
minutes: None
|
||||
})
|
||||
);
|
||||
assert_eq!(dt.normalize().tz, Some(TimeZone::UTC));
|
||||
assert_eq!(dt.to_string(), "2023-06-12T03:29:48Z");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//! Implements a parser for ISO-8601 format Time
|
||||
use super::{DateTime, Error, Offset, Sign, TimeZone};
|
||||
use super::{days_in_month, DateTime, Error, Offset, Sign, TimeZone};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum Format {
|
||||
|
@ -53,20 +53,9 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::missing_panics_doc)]
|
||||
fn validate_day(&self, day: u8) -> Result<(), Error> {
|
||||
let max = match self.month {
|
||||
Some(1 | 3 | 5 | 7 | 10 | 12) => 31,
|
||||
Some(2) => {
|
||||
let year = self.year.unwrap();
|
||||
if year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) {
|
||||
29
|
||||
} else {
|
||||
28
|
||||
}
|
||||
}
|
||||
Some(4 | 6 | 9 | 11) => 30,
|
||||
_ => return Err(Error::InvalidMonth),
|
||||
};
|
||||
let max = days_in_month(self.month.unwrap(), self.year.unwrap() as i64) as u8;
|
||||
if day > max {
|
||||
Err(Error::InvalidDay)
|
||||
} else {
|
||||
|
@ -267,12 +256,12 @@ impl<'a> Parser<'a> {
|
|||
let sign = match self.buffer.chars().next() {
|
||||
Some('+') => Sign::Positive,
|
||||
Some('-') => Sign::Negative,
|
||||
None => return Ok(()),
|
||||
_ => return Err(Error::InvalidOffset),
|
||||
};
|
||||
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..5) {
|
||||
Some(n.parse()?)
|
||||
|
@ -510,4 +499,10 @@ mod tests {
|
|||
}))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_no_tz() {
|
||||
let dt: Result<DateTime, Error> = Parser::new("2023-06-11T22:41:31").parse();
|
||||
assert!(dt.unwrap().tz.is_none());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,286 +0,0 @@
|
|||
//! Implements a parser for ISO-8601 format Time
|
||||
use super::{DateTime, Error, TimeZone};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum Format {
|
||||
Basic,
|
||||
Extended,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum Mode {
|
||||
Year,
|
||||
Month,
|
||||
Day,
|
||||
Hour,
|
||||
Minute,
|
||||
Second,
|
||||
TimeZone,
|
||||
Finish,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Parser<'a> {
|
||||
text: &'a str,
|
||||
format: Format,
|
||||
mode: Mode,
|
||||
year: Option<u32>,
|
||||
month: Option<u8>,
|
||||
day: Option<u8>,
|
||||
hour: Option<u8>,
|
||||
minute: Option<u8>,
|
||||
second: Option<u8>,
|
||||
tz: Option<TimeZone>,
|
||||
}
|
||||
|
||||
impl<'a> Parser<'a> {
|
||||
pub fn new(text: &'a str) -> Self {
|
||||
Self {
|
||||
text,
|
||||
format: Format::Extended,
|
||||
mode: Mode::Year,
|
||||
year: None,
|
||||
month: None,
|
||||
day: None,
|
||||
hour: None,
|
||||
minute: None,
|
||||
second: None,
|
||||
tz: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_year(&mut self) -> Result<(), Error> {
|
||||
assert_eq!(self.mode, Mode::Year);
|
||||
let year = self.text.get(..3).ok_or(Error::Truncated)?.parse()?;
|
||||
self.year = Some(year);
|
||||
if let Some(c) = self.text.chars().nth(5) {
|
||||
match c {
|
||||
'-' => self.format = Format::Extended,
|
||||
_ => self.format = Format::Basic,
|
||||
}
|
||||
}
|
||||
self.mode = Mode::Month;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_month(&mut self) -> Result<(), Error> {
|
||||
assert_eq!(self.mode, Mode::Month);
|
||||
let month = match self.format {
|
||||
Format::Basic => self.text.get(4..6),
|
||||
Format::Extended => self.text.get(5..7),
|
||||
};
|
||||
if let Some(m) = month {
|
||||
let month = match m.parse() {
|
||||
Ok(m) => m,
|
||||
Err(_) => {
|
||||
self.mode = Mode::TimeZone;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
if month > 12 {
|
||||
return Err(Error::InvalidMonth);
|
||||
}
|
||||
self.month = Some(month);
|
||||
self.mode = Mode::Day;
|
||||
} else {
|
||||
self.mode = Mode::TimeZone;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_day(&mut self) -> Result<(), Error> {
|
||||
assert_eq!(self.mode, Mode::Day);
|
||||
let day = match self.format {
|
||||
Format::Basic => self.text.get(6..8),
|
||||
Format::Extended => self.text.get(8..10),
|
||||
};
|
||||
if let Some(d) = day {
|
||||
let day = match d.parse() {
|
||||
Ok(d) => d,
|
||||
Err(_) => {
|
||||
self.mode = Mode::TimeZone;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
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 {
|
||||
return Err(Error::InvalidDay);
|
||||
}
|
||||
self.day = Some(day);
|
||||
let tidx = match self.format {
|
||||
Format::Basic => 8,
|
||||
Format::Extended => 10,
|
||||
};
|
||||
if let Some(c) = self.text.chars().nth(tidx) {
|
||||
match c {
|
||||
'T' => self.mode = Mode::Hour,
|
||||
'Z' | '-' | '+' => self.mode = Mode::TimeZone,
|
||||
_ => return Err(Error::InvalidHour),
|
||||
}
|
||||
} else {
|
||||
self.mode = Mode::Finish;
|
||||
}
|
||||
} else {
|
||||
self.mode = Mode::TimeZone;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_hour(&mut self) -> Result<(), Error> {
|
||||
assert_eq!(self.mode, Mode::Hour);
|
||||
let hour = match self.format {
|
||||
Format::Basic => self.text.get(9..11),
|
||||
Format::Extended => self.text.get(11..13),
|
||||
};
|
||||
if let Some(h) = hour {
|
||||
let hour = match h.parse() {
|
||||
Ok(h) => h,
|
||||
Err(_) => {
|
||||
self.mode = Mode::TimeZone;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
if hour > 24 {
|
||||
return Err(Error::InvalidHour);
|
||||
}
|
||||
self.hour = Some(hour);
|
||||
self.mode = Mode::Minute;
|
||||
} else {
|
||||
self.mode = Mode::TimeZone;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_minute(&mut self) -> Result<(), Error> {
|
||||
assert_eq!(self.mode, Mode::Minute);
|
||||
let minute = match self.format {
|
||||
Format::Basic => self.text.get(11..13),
|
||||
Format::Extended => self.text.get(14..16),
|
||||
};
|
||||
if let Some(m) = minute {
|
||||
let minute = match m.parse() {
|
||||
Ok(m) => m,
|
||||
Err(_) => {
|
||||
self.mode = Mode::TimeZone;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
if minute > 60 {
|
||||
return Err(Error::InvalidMinute);
|
||||
}
|
||||
self.minute = Some(minute);
|
||||
self.mode = Mode::Second;
|
||||
} else {
|
||||
self.mode = Mode::TimeZone;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_second(&mut self) -> Result<(), Error> {
|
||||
assert_eq!(self.mode, Mode::Second);
|
||||
let second = match self.format {
|
||||
Format::Basic => self.text.get(13..15),
|
||||
Format::Extended => self.text.get(17..19),
|
||||
};
|
||||
if let Some(s) = second {
|
||||
let second = match s.parse() {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
self.mode = Mode::TimeZone;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
if second > 60 {
|
||||
return Err(Error::InvalidSecond);
|
||||
}
|
||||
self.second = Some(second);
|
||||
}
|
||||
self.mode = Mode::TimeZone;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_timezone(&mut self) -> Result<(), Error> {
|
||||
assert_eq!(self.mode, Mode::TimeZone);
|
||||
let idx = if self.second.is_some() {
|
||||
match self.format {
|
||||
Format::Basic => 17,
|
||||
Format::Extended => 21,
|
||||
}
|
||||
} else if self.minute.is_some() {
|
||||
match self.format {
|
||||
Format::Basic => 15,
|
||||
Format::Extended => 19,
|
||||
}
|
||||
} else if self.hour.is_some() {
|
||||
match self.format {
|
||||
Format::Basic => 13,
|
||||
Format::Extended => 16,
|
||||
}
|
||||
} else if self.day.is_some() {
|
||||
match self.format {
|
||||
Format::Basic => 9,
|
||||
Format::Extended => 11,
|
||||
}
|
||||
} else if self.month.is_some() {
|
||||
match self.format {
|
||||
Format::Basic => 6,
|
||||
Format::Extended => 8,
|
||||
}
|
||||
} else {
|
||||
4
|
||||
};
|
||||
match self.text.chars().nth(idx) {
|
||||
Some('Z') => {
|
||||
if self.text.len() > idx + 2 {
|
||||
return Err(Error::TrailingGarbage);
|
||||
} else {
|
||||
self.tz = Some(TimeZone::UTC);
|
||||
return Ok(());
|
||||
}
|
||||
},
|
||||
Some('-') => todo!(),
|
||||
Some('+') => todo!(),
|
||||
None => self.mode = Mode::Finish,
|
||||
_ => return Err(Error::InvalidTimezone),
|
||||
}
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn parse(mut self) -> Result<DateTime, Error> {
|
||||
loop {
|
||||
match self.mode {
|
||||
Mode::Year => self.parse_year()?,
|
||||
Mode::Month => self.parse_month()?,
|
||||
Mode::Day => self.parse_day()?,
|
||||
Mode::Hour => self.parse_hour()?,
|
||||
Mode::Minute => self.parse_minute()?,
|
||||
Mode::Second => self.parse_second()?,
|
||||
Mode::TimeZone => {
|
||||
self.parse_timezone()?;
|
||||
break;
|
||||
}
|
||||
Mode::Finish => break,
|
||||
}
|
||||
}
|
||||
Ok(DateTime {
|
||||
year: self.year.unwrap(),
|
||||
month: self.month,
|
||||
day: self.day,
|
||||
hour: self.hour,
|
||||
minute: self.minute,
|
||||
second: self.second,
|
||||
tz: self.tz,
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue