Add Time
module for storing ISO-8601 time and converting to and from
a string
This commit is contained in:
parent
5ab89f74db
commit
0fcfb6db47
7 changed files with 470 additions and 8 deletions
|
@ -1,4 +1,4 @@
|
||||||
use super::{Verifier, FingerPrintStore};
|
use super::{FingerPrintStore, Verifier};
|
||||||
use rustls::ServerConfig;
|
use rustls::ServerConfig;
|
||||||
use std::{net::TcpStream, sync::Arc};
|
use std::{net::TcpStream, sync::Arc};
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,11 @@ pub mod builder;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod verifier;
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct Connection {
|
pub struct Connection {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use rustls::server::ClientCertVerifier;
|
use rustls::server::ClientCertVerifier;
|
||||||
|
|
||||||
use crate::{prelude::CertificateStore, mailuser::Mailuser};
|
use crate::{mailuser::Mailuser, prelude::CertificateStore};
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
@ -13,5 +13,6 @@ pub mod request;
|
||||||
pub mod response;
|
pub mod response;
|
||||||
pub mod sender;
|
pub mod sender;
|
||||||
pub mod status;
|
pub mod status;
|
||||||
|
pub mod time;
|
||||||
|
|
||||||
pub use sender::{Error as SenderError, Sender};
|
pub use sender::{Error as SenderError, Sender};
|
||||||
|
|
29
src/time/error.rs
Normal file
29
src/time/error.rs
Normal 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
254
src/time/mod.rs
Normal 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
174
src/time/parser.rs
Normal 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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue