use chrono::{FixedOffset, TimeZone}; use regex::Regex; use rocket::http::RawStr; use rocket::request::FromFormValue; use serde::ser::Serializer; use serde::Serialize; use std::convert::TryFrom; /// Represents a very simple Timezoneless date. Considering the timezone will /// always be CEST (aka Belgium's timezone), this is good enough. I use this /// instead of a NaiveDate to avoid E0117. pub struct BasicDate { year: u32, month: u8, day: u8, } impl BasicDate { /// Return the seconds since epoch for this date pub fn epoch(&self) -> i64 { // Timezone of Brussels is UTC + 2 hours in the western hemisphere FixedOffset::west(7_200) .ymd(self.year as i32, self.month as u32, self.day as u32) // For some reason, I couldn't get .timestamp() to work on a Date // without a time component, even though the docs seemed to // indicate this was possible .and_hms(0, 0, 0) .timestamp() } } /// This allows us to use BasicDate as a query parameter in our routes. impl<'v> FromFormValue<'v> for BasicDate { type Error = &'v RawStr; fn from_form_value(form_value: &'v RawStr) -> Result { match BasicDate::try_from(form_value.as_str()) { Err(_) => Err(form_value), Ok(date) => Ok(date), } } } /// We need this when deserializing BasicDate. impl ToString for BasicDate { fn to_string(&self) -> String { format!("{}-{}-{}", self.year, self.month, self.day) } } /// This is used to serialize BasicDate. impl TryFrom<&str> for BasicDate { type Error = (); fn try_from(s: &str) -> Result { let re = Regex::new(r"^(\d{4})-(\d{2})-(\d{2})$").unwrap(); match re.captures(s) { None => Err(()), Some(caps) => Ok(BasicDate { // TODO change this to ? operator if possible year: caps.get(1).unwrap().as_str().parse().unwrap(), month: caps.get(2).unwrap().as_str().parse().unwrap(), day: caps.get(3).unwrap().as_str().parse().unwrap(), }), } } } impl Serialize for BasicDate { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_str(&self.to_string()) } }