use std::str::FromStr;
use chrono::offset::Utc;
use chrono::{DateTime, Datelike, Timelike};
use chrono_tz::Tz;
use failure::{Fail, ResultExt};
use error::{FrontendError, FrontendErrorKind, MissingField};
#[derive(Clone, Debug)]
pub struct Event {
title: String,
description: String,
start_date: DateTime<Tz>,
end_date: DateTime<Tz>,
}
impl Event {
pub fn from_parts(
title: String,
description: String,
start_date: DateTime<Tz>,
end_date: DateTime<Tz>,
) -> Self {
Event {
title,
description,
start_date,
end_date,
}
}
pub fn from_option(option_event: OptionEvent) -> Result<Self, FrontendError> {
CreateEvent::from_option(option_event)?.try_to_event()
}
pub fn title(&self) -> &str {
&self.title
}
pub fn description(&self) -> &str {
&self.description
}
pub fn start_date(&self) -> DateTime<Tz> {
self.start_date
}
pub fn end_date(&self) -> DateTime<Tz> {
self.end_date
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct OptionEvent {
title: Option<String>,
description: Option<String>,
start_year: Option<i32>,
start_month: Option<u32>,
start_day: Option<u32>,
start_hour: Option<u32>,
start_minute: Option<u32>,
end_year: Option<i32>,
end_month: Option<u32>,
end_day: Option<u32>,
end_hour: Option<u32>,
end_minute: Option<u32>,
timezone: Option<String>,
}
impl OptionEvent {
pub fn missing_keys(&self) -> Vec<&'static str> {
let mut v = Vec::new();
if self.title.is_none() {
v.push("title");
} else if let Some(ref title) = self.title {
if title.trim().len() == 0 {
v.push("title");
}
}
if self.description.is_none() {
v.push("description");
} else if let Some(ref description) = self.description {
if description.trim().len() == 0 {
v.push("description");
}
}
if self.start_year.is_none() {
v.push("start year");
}
if self.start_month.is_none() {
v.push("start month");
}
if self.start_day.is_none() {
v.push("start day");
}
if self.start_hour.is_none() {
v.push("start hour");
}
if self.start_minute.is_none() {
v.push("start minute");
}
if self.end_year.is_none() {
v.push("end year");
}
if self.end_month.is_none() {
v.push("end month");
}
if self.end_day.is_none() {
v.push("end day");
}
if self.end_hour.is_none() {
v.push("end hour");
}
if self.end_minute.is_none() {
v.push("end minute");
}
if self.timezone.is_none() {
v.push("timezone");
}
v
}
}
pub struct CreateEvent {
pub title: String,
pub description: String,
pub start_year: i32,
pub start_month: u32,
pub start_day: u32,
pub start_hour: u32,
pub start_minute: u32,
pub end_year: i32,
pub end_month: u32,
pub end_day: u32,
pub end_hour: u32,
pub end_minute: u32,
pub timezone: String,
}
impl CreateEvent {
pub fn default_from(date: DateTime<Tz>) -> Self {
CreateEvent {
title: "".to_owned(),
description: "".to_owned(),
start_year: date.year(),
start_month: date.month() - 1,
start_day: date.day() as u32,
start_hour: date.hour() as u32,
start_minute: date.minute() as u32,
end_year: date.year(),
end_month: date.month() - 1,
end_day: date.day() as u32,
end_hour: date.hour() as u32,
end_minute: date.minute() as u32,
timezone: date.timezone().name().to_owned(),
}
}
pub fn merge(&mut self, option_event: &OptionEvent) {
if let Some(ref title) = option_event.title {
self.title = title.to_owned();
}
if let Some(ref description) = option_event.description {
self.description = description.to_owned();
}
if let Some(start_year) = option_event.start_year {
self.start_year = start_year;
}
if let Some(start_month) = option_event.start_month {
self.start_month = start_month;
}
if let Some(start_day) = option_event.start_day {
self.start_day = start_day;
}
if let Some(start_hour) = option_event.start_hour {
self.start_hour = start_hour;
}
if let Some(start_minute) = option_event.start_minute {
self.start_minute = start_minute;
}
if let Some(end_year) = option_event.end_year {
self.end_year = end_year;
}
if let Some(end_month) = option_event.end_month {
self.end_month = end_month;
}
if let Some(end_day) = option_event.end_day {
self.end_day = end_day;
}
if let Some(end_hour) = option_event.end_hour {
self.end_hour = end_hour;
}
if let Some(end_minute) = option_event.end_minute {
self.end_minute = end_minute;
}
if let Some(ref timezone) = option_event.timezone {
self.timezone = timezone.to_owned();
}
}
fn from_option(option_event: OptionEvent) -> Result<Self, FrontendError> {
let title = maybe_empty_string(maybe_field(option_event.title, "title")?, "title")?;
let description = maybe_empty_string(
maybe_field(option_event.description, "description")?,
"description",
)?;
let start_year = maybe_field(option_event.start_year, "start_year")?;
let start_month = maybe_field(option_event.start_month, "start_month")?;
let start_day = maybe_field(option_event.start_day, "start_day")?;
let start_hour = maybe_field(option_event.start_hour, "start_hour")?;
let start_minute = maybe_field(option_event.start_minute, "start_minute")?;
let end_year = maybe_field(option_event.end_year, "end_year")?;
let end_month = maybe_field(option_event.end_month, "end_month")?;
let end_day = maybe_field(option_event.end_day, "end_day")?;
let end_hour = maybe_field(option_event.end_hour, "end_hour")?;
let end_minute = maybe_field(option_event.end_minute, "end_minute")?;
let timezone = maybe_field(option_event.timezone, "timezone")?;
Ok(CreateEvent {
title,
description,
start_year,
start_month,
start_day,
start_hour,
start_minute,
end_year,
end_month,
end_day,
end_hour,
end_minute,
timezone,
})
}
fn try_to_event(self) -> Result<Event, FrontendError> {
let timezone = Tz::from_str(&self.timezone).map_err(|_| FrontendErrorKind::BadTimeZone)?;
let now = Utc::now();
let datetime = now.with_timezone(&timezone);
let start_datetime = datetime
.with_year(self.start_year)
.ok_or(FrontendErrorKind::BadYear)?
.with_month0(self.start_month)
.ok_or(FrontendErrorKind::BadMonth)?
.with_day(self.start_day)
.ok_or(FrontendErrorKind::BadDay)?
.with_hour(self.start_hour)
.ok_or(FrontendErrorKind::BadHour)?
.with_minute(self.start_minute)
.ok_or(FrontendErrorKind::BadMinute)?
.with_second(0)
.ok_or(FrontendErrorKind::BadSecond)?;
let end_datetime = datetime
.with_year(self.end_year)
.ok_or(FrontendErrorKind::BadYear)?
.with_month0(self.end_month)
.ok_or(FrontendErrorKind::BadMonth)?
.with_day(self.end_day)
.ok_or(FrontendErrorKind::BadDay)?
.with_hour(self.end_hour)
.ok_or(FrontendErrorKind::BadHour)?
.with_minute(self.end_minute)
.ok_or(FrontendErrorKind::BadMinute)?
.with_second(0)
.ok_or(FrontendErrorKind::BadSecond)?;
Ok(Event {
title: self.title,
description: self.description,
start_date: start_datetime,
end_date: end_datetime,
})
}
}
impl From<Event> for CreateEvent {
fn from(e: Event) -> Self {
CreateEvent {
title: e.title,
description: e.description,
start_year: e.start_date.year(),
start_month: e.start_date.month(),
start_day: e.start_date.day(),
start_hour: e.start_date.hour(),
start_minute: e.start_date.minute(),
end_year: e.end_date.year(),
end_month: e.end_date.month(),
end_day: e.end_date.day(),
end_hour: e.end_date.hour(),
end_minute: e.end_date.minute(),
timezone: e.end_date.timezone().name().to_owned(),
}
}
}
fn maybe_field<T>(maybe: Option<T>, field: &'static str) -> Result<T, FrontendError> {
Ok(maybe
.ok_or(MissingField { field })
.context(FrontendErrorKind::MissingField)?)
}
fn maybe_empty_string(s: String, field: &'static str) -> Result<String, FrontendError> {
let s = s.trim().to_owned();
if s.len() == 0 {
Err(MissingField { field }
.context(FrontendErrorKind::MissingField)
.into())
} else {
Ok(s)
}
}