use std::cmp::Ord;
use std::cmp::Ordering;
use std::cmp::PartialOrd;
use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Result as FmtResult;
use std::fmt::Write;
use std::str::FromStr;
use ordered_float::OrderedFloat;
use pest::iterators::Pair;
use crate::error::Error;
use crate::error::SyntaxError;
use crate::parser::FromPair;
use crate::parser::Rule;
pub trait DateTime {
fn to_xsd_datetime(&self) -> String;
}
#[derive(Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
pub struct NaiveDateTime {
year: u16,
month: u8,
day: u8,
hour: u8,
minute: u8,
}
impl NaiveDateTime {
pub fn new(day: u8, month: u8, year: u16, hour: u8, minute: u8) -> Self {
NaiveDateTime {
day,
month,
year,
hour,
minute,
}
}
pub fn with_date(mut self, day: u8, month: u8, year: u16) -> Self {
self.day = day;
self.month = month;
self.year = year;
self
}
pub fn with_time(mut self, hour: u8, minute: u8) -> Self {
self.hour = hour;
self.minute = minute;
self
}
pub fn day(&self) -> u8 {
self.day
}
pub fn month(&self) -> u8 {
self.month
}
pub fn year(&self) -> u16 {
self.year
}
pub fn hour(&self) -> u8 {
self.hour
}
pub fn minute(&self) -> u8 {
self.minute
}
}
impl DateTime for NaiveDateTime {
fn to_xsd_datetime(&self) -> String {
format!(
"{:04}-{:02}-{:02}T{:02}:{:02}:00",
self.year, self.month, self.day, self.hour, self.minute
)
}
}
impl Display for NaiveDateTime {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
write!(
f,
"{:02}:{:02}:{:04} {:02}:{:02}",
self.day, self.month, self.year, self.hour, self.minute
)
}
}
impl<'i> FromPair<'i> for NaiveDateTime {
const RULE: Rule = Rule::NaiveDateTime;
unsafe fn from_pair_unchecked(pair: Pair<'i, Rule>) -> Result<Self, SyntaxError> {
let mut inner = pair.into_inner();
let date = inner.next().unwrap();
let time = inner.next().unwrap();
let datestr = date.as_str();
let timestr = time.as_str();
Ok(NaiveDateTime {
day: u8::from_str_radix(&datestr[..2], 10).unwrap(),
month: u8::from_str_radix(&datestr[3..5], 10).unwrap(),
year: u16::from_str_radix(&datestr[6..10], 10).unwrap(),
hour: u8::from_str_radix(×tr[..2], 10).unwrap(),
minute: u8::from_str_radix(×tr[3..5], 10).unwrap(),
})
}
}
impl_fromstr!(NaiveDateTime);
#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub struct IsoDateTime {
year: u16,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
fraction: Option<OrderedFloat<f32>>,
timezone: Option<IsoTimezone>,
}
impl IsoDateTime {
pub fn new(day: u8, month: u8, year: u16, hour: u8, minute: u8, second: u8) -> Self {
IsoDateTime {
day,
month,
year,
hour,
minute,
second,
fraction: None,
timezone: None,
}
}
pub fn with_timezone<I>(mut self, tz: I) -> Self
where
I: Into<Option<IsoTimezone>>,
{
self.timezone = tz.into();
self
}
pub fn with_date(mut self, day: u8, month: u8, year: u16) -> Self {
self.day = day;
self.month = month;
self.year = year;
self
}
pub fn with_time(mut self, hour: u8, minute: u8, second: u8) -> Self {
self.hour = hour;
self.minute = minute;
self.second = second;
self
}
pub fn day(&self) -> u8 {
self.day
}
pub fn month(&self) -> u8 {
self.month
}
pub fn year(&self) -> u16 {
self.year
}
pub fn hour(&self) -> u8 {
self.hour
}
pub fn minute(&self) -> u8 {
self.minute
}
pub fn second(&self) -> u8 {
self.second
}
pub fn fraction(&self) -> Option<f32> {
self.fraction.as_ref().map(|f| f.0)
}
pub fn timezone(&self) -> Option<&IsoTimezone> {
self.timezone.as_ref()
}
}
impl Display for IsoDateTime {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
write!(
f,
"{:02}-{:02}-{:04}T{:02}:{:02}:{:02}",
self.day, self.month, self.year, self.hour, self.minute, self.second,
)?;
match self.timezone {
Some(ref tz) => tz.fmt(f),
None => Ok(()),
}
}
}
impl<'i> FromPair<'i> for IsoDateTime {
const RULE: Rule = Rule::Iso8601DateTime;
unsafe fn from_pair_unchecked(pair: Pair<'i, Rule>) -> Result<Self, SyntaxError> {
let mut inner = pair.into_inner();
let mut date = inner.next().unwrap().into_inner();
let mut time = inner.next().unwrap().into_inner();
let year = u16::from_str_radix(date.next().unwrap().as_str(), 10).unwrap();
let month = u8::from_str_radix(date.next().unwrap().as_str(), 10).unwrap();
let day = u8::from_str_radix(date.next().unwrap().as_str(), 10).unwrap();
let hour = u8::from_str_radix(time.next().unwrap().as_str(), 10).unwrap();
let minute = u8::from_str_radix(time.next().unwrap().as_str(), 10).unwrap();
let second = u8::from_str_radix(time.next().unwrap().as_str(), 10).unwrap();
let fraction = time.next().map(|p| f32::from_str(p.as_str()).unwrap().into());
let timezone = match inner.next() {
Some(pair) => Some(IsoTimezone::from_pair_unchecked(pair)?),
None => None,
};
Ok(IsoDateTime {
day,
month,
year,
hour,
minute,
second,
fraction,
timezone,
})
}
}
impl_fromstr!(IsoDateTime);
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum IsoTimezone {
Minus(u8, Option<u8>),
Utc,
Plus(u8, Option<u8>),
}
impl DateTime for IsoDateTime {
fn to_xsd_datetime(&self) -> String {
let tz = match self.timezone {
None => String::new(),
Some(ref dt) => dt.to_string(),
};
format!(
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}{}",
self.year, self.month, self.day, self.hour, self.minute, self.second, tz,
)
}
}
impl Display for IsoTimezone {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
use self::IsoTimezone::*;
match self {
Utc => f.write_char('Z'),
Plus(hh, Some(mm)) => write!(f, "+{:02}:{:02}", hh, mm),
Minus(hh, Some(mm)) => write!(f, "-{:02}:{:02}", hh, mm),
Plus(hh, None) => write!(f, "-{:02}", hh),
Minus(hh, None) => write!(f, "-{:02}", hh),
}
}
}
impl<'i> FromPair<'i> for IsoTimezone {
const RULE: Rule = Rule::Iso8601TimeZone;
unsafe fn from_pair_unchecked(pair: Pair<'i, Rule>) -> Result<Self, SyntaxError> {
use self::IsoTimezone::*;
let tag = pair.as_str().chars().next().unwrap();
if tag == 'Z' {
return Ok(Utc);
}
let mut inner = pair.into_inner();
let hh = u8::from_str_radix(inner.next().unwrap().as_str(), 10).unwrap();
let mm = inner.next().map(|p| u8::from_str_radix(p.as_str(), 10).unwrap());
match tag {
'+' => Ok(Plus(hh, mm)),
'-' => Ok(Minus(hh, mm)),
_ => unreachable!(),
}
}
}
impl_fromstr!(IsoTimezone);
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use std::str::FromStr;
mod naive {
use super::*;
#[test]
fn from_str() {
let naive = NaiveDateTime::from_str("12:06:2018 17:13").unwrap();
self::assert_eq!(naive, NaiveDateTime::new(12, 6, 2018, 17, 13));
}
}
mod iso {
use super::*;
#[test]
fn from_str() {
match IsoDateTime::from_str("2017-1-24T14:41:36Z") {
Ok(_) => (),
Err(e) => panic!("{}", e),
}
match IsoDateTime::from_str("2015-08-11T15:05:12Z") {
Ok(_) => (),
Err(e) => panic!("{}", e),
}
match IsoDateTime::from_str("2016-10-26T10:51:48Z") {
Ok(_) => (),
Err(e) => panic!("{}", e),
}
match IsoDateTime::from_str("2017-1-24T14:41:36Z") {
Ok(_) => (),
Err(e) => panic!("{}", e),
}
match IsoDateTime::from_str("2017-1-24T14:41:36.05Z") {
Ok(_) => (),
Err(e) => panic!("{}", e),
}
match IsoDateTime::from_str("2017-1-24T14:41:36+01:30") {
Ok(_) => (),
Err(e) => panic!("{}", e),
}
}
}
}