Add Time module for storing ISO-8601 time and converting to and from

a string
This commit is contained in:
Nathan Fisher 2023-06-09 01:23:35 -04:00
parent 5ab89f74db
commit 0fcfb6db47
7 changed files with 470 additions and 8 deletions

View file

@ -1,4 +1,4 @@
use super::{Verifier, FingerPrintStore};
use super::{FingerPrintStore, Verifier};
use rustls::ServerConfig;
use std::{net::TcpStream, sync::Arc};

View file

@ -2,7 +2,11 @@ pub mod builder;
pub mod error;
pub mod verifier;
pub use self::{builder::Builder, error::Error, verifier::{FingerPrintStore, Verifier}};
pub use self::{
builder::Builder,
error::Error,
verifier::{FingerPrintStore, Verifier},
};
#[derive(Debug)]
pub struct Connection {

View file

@ -1,6 +1,6 @@
use rustls::server::ClientCertVerifier;
use crate::{prelude::CertificateStore, mailuser::Mailuser};
use crate::{mailuser::Mailuser, prelude::CertificateStore};
use std::sync::Mutex;
#[derive(Debug)]

View file

@ -13,5 +13,6 @@ pub mod request;
pub mod response;
pub mod sender;
pub mod status;
pub mod time;
pub use sender::{Error as SenderError, Sender};

29
src/time/error.rs Normal file
View file

@ -0,0 +1,29 @@
use std::{fmt, num::ParseIntError};
#[derive(Debug)]
pub enum Error {
ParseInt,
InvalidMonth,
InvalidDay,
InvalidHour,
InvalidMinute,
InvalidSecond,
InvalidTimezone,
InvalidOffset,
TrailingGarbage,
Truncated,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
impl std::error::Error for Error {}
impl From<ParseIntError> for Error {
fn from(_value: ParseIntError) -> Self {
Self::ParseInt
}
}

254
src/time/mod.rs Normal file
View file

@ -0,0 +1,254 @@
//! A container representing a moment in ISO-8601 format Time
use std::{fmt, str::FromStr};
mod error;
mod parser;
pub use {error::Error, parser::Parser};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum Sign {
Positive,
Negative,
}
impl fmt::Display for Sign {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Positive => write!(f, "+"),
Self::Negative => write!(f, "-"),
}
}
}
impl TryFrom<char> for Sign {
type Error = Error;
fn try_from(value: char) -> Result<Self, Self::Error> {
match value {
'+' => Ok(Self::Positive),
'-' => Ok(Self::Negative),
_ => Err(Error::InvalidOffset),
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct Offset {
pub sign: Sign,
pub hours: u8,
pub minutes: Option<u8>,
}
impl fmt::Display for Offset {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(m) = self.minutes {
write!(f, "{}{:02}:{m:02}", self.sign, self.hours)
} else {
write!(f, "{}{:02}", self.sign, self.hours)
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum TimeZone {
UTC,
Offset(Offset),
}
impl fmt::Display for TimeZone {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UTC => write!(f, "Z"),
Self::Offset(o) => write!(f, "{o}"),
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct DateTime {
pub year: u32,
pub month: Option<u8>,
pub day: Option<u8>,
pub hour: Option<u8>,
pub minute: Option<u8>,
pub second: Option<u8>,
pub tz: Option<TimeZone>,
}
impl fmt::Display for DateTime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
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}")?;
} else {
break 'blk;
}
for field in &[self.minute, self.second] {
if let Some(field) = field {
write!(f, ":{field:02}")?;
} else {
break 'blk;
}
}
}
if let Some(ref tz) = self.tz {
write!(f, "{tz}")
} else {
Ok(())
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn fmt_full() {
let dt = DateTime {
year: 2023,
month: Some(5),
day: Some(7),
hour: Some(9),
minute: Some(3),
second: Some(8),
tz: Some(TimeZone::Offset(Offset {
sign: Sign::Positive,
hours: 5,
minutes: Some(15),
})),
};
assert_eq!(dt.to_string(), "2023-05-07T09:03:08+05:15");
}
#[test]
fn fmt_partial_offset() {
let dt = DateTime {
year: 2023,
month: Some(5),
day: Some(7),
hour: Some(9),
minute: Some(3),
second: Some(8),
tz: Some(TimeZone::Offset(Offset {
sign: Sign::Negative,
hours: 5,
minutes: None,
})),
};
assert_eq!(dt.to_string(), "2023-05-07T09:03:08-05");
}
#[test]
fn fmt_utc() {
let dt = DateTime {
year: 2023,
month: Some(5),
day: Some(7),
hour: Some(9),
minute: Some(3),
second: Some(8),
tz: Some(TimeZone::UTC),
};
assert_eq!(dt.to_string(), "2023-05-07T09:03:08Z");
}
#[test]
fn fmt_no_tz() {
let dt = DateTime {
year: 2023,
month: Some(5),
day: Some(7),
hour: Some(9),
minute: Some(3),
second: Some(8),
tz: None,
};
assert_eq!(dt.to_string(), "2023-05-07T09:03:08");
}
#[test]
fn fmt_no_seconds() {
let dt = DateTime {
year: 2023,
month: Some(5),
day: Some(7),
hour: Some(9),
minute: Some(3),
second: None,
tz: Some(TimeZone::UTC),
};
assert_eq!(dt.to_string(), "2023-05-07T09:03Z");
}
#[test]
fn fmt_no_minutes() {
let dt = DateTime {
year: 2023,
month: Some(5),
day: Some(7),
hour: Some(9),
minute: None,
second: Some(50),
tz: Some(TimeZone::UTC),
};
assert_eq!(dt.to_string(), "2023-05-07T09Z");
}
#[test]
fn fmt_no_time() {
let dt = DateTime {
year: 2023,
month: Some(5),
day: Some(7),
hour: None,
minute: None,
second: Some(50),
tz: Some(TimeZone::UTC),
};
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");
}
}

174
src/time/parser.rs Normal file
View file

@ -0,0 +1,174 @@
//! 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 = m.parse()?;
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 = d.parse()?;
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);
todo!()
}
fn parse_minute(&mut self) -> Result<(), Error> {
assert_eq!(self.mode, Mode::Minute);
todo!()
}
fn parse_second(&mut self) -> Result<(), Error> {
assert_eq!(self.mode, Mode::Second);
todo!()
}
fn parse_timezone(&mut self) -> Result<(), Error> {
assert_eq!(self.mode, Mode::TimeZone);
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,
})
}
}