imag/libimagentrydatetime/src/datetime.rs

260 lines
8.8 KiB
Rust
Raw Normal View History

2017-05-19 15:11:36 +00:00
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
use chrono::naive::datetime::NaiveDateTime;
use toml_query::delete::TomlValueDeleteExt;
use toml_query::insert::TomlValueInsertExt;
use toml_query::read::TomlValueReadExt;
use toml::Value;
use libimagstore::store::Entry;
use libimagerror::into::IntoError;
use error::DateErrorKind as DEK;
use error::*;
use result::Result;
2017-06-06 17:28:00 +00:00
use range::DateTimeRange;
2017-05-19 15:43:42 +00:00
2017-05-19 15:11:36 +00:00
pub trait EntryDate {
fn delete_date(&mut self) -> Result<()>;
fn read_date(&self) -> Result<NaiveDateTime>;
fn set_date(&mut self, d: NaiveDateTime) -> Result<Option<Result<NaiveDateTime>>>;
2017-05-19 15:43:42 +00:00
fn delete_date_range(&mut self) -> Result<()>;
2017-06-06 17:28:00 +00:00
fn read_date_range(&self) -> Result<DateTimeRange>;
fn set_date_range(&mut self, start: NaiveDateTime, end: NaiveDateTime) -> Result<Option<Result<DateTimeRange>>>;
2017-05-19 15:43:42 +00:00
2017-05-19 15:11:36 +00:00
}
lazy_static! {
2017-06-07 10:01:39 +00:00
static ref DATE_HEADER_LOCATION : &'static str = "date.value";
static ref DATE_RANGE_START_HEADER_LOCATION : &'static str = "date.range.start";
static ref DATE_RANGE_END_HEADER_LOCATION : &'static str = "date.range.end";
static ref DATE_FMT : &'static str = "%Y-%m-%dT%H:%M:%S";
2017-05-19 15:11:36 +00:00
}
impl EntryDate for Entry {
fn delete_date(&mut self) -> Result<()> {
self.get_header_mut()
.delete(&DATE_HEADER_LOCATION)
.map(|_| ())
2017-05-20 11:55:19 +00:00
.map_err_into(DEK::DeleteDateError)
2017-05-19 15:11:36 +00:00
}
fn read_date(&self) -> Result<NaiveDateTime> {
self.get_header()
.read(&DATE_HEADER_LOCATION)
2017-05-20 11:55:19 +00:00
.map_err_into(DEK::ReadDateError)
2017-05-19 15:11:36 +00:00
.and_then(|v| {
match v {
&Value::String(ref s) => s.parse::<NaiveDateTime>()
.map_err_into(DEK::DateTimeParsingError),
_ => Err(DEK::DateHeaderFieldTypeError.into_error()),
}
})
}
/// Set a Date for this entry
///
/// # Return value
///
/// This function returns funny things, I know. But I find it more attractive to be explicit
/// what failed when here, instead of beeing nice to the user here.
///
/// So here's a list how things are returned:
///
/// - Err(_) if the inserting failed
/// - Ok(None) if the inserting succeeded and _did not replace an existing value_.
/// - Ok(Some(Ok(_))) if the inserting succeeded, but replaced an existing value which then got
/// parsed into a NaiveDateTime object
/// - Ok(Some(Err(_))) if the inserting succeeded, but replaced an existing value which then
/// got parsed into a NaiveDateTime object, where the parsing failed for some reason.
///
fn set_date(&mut self, d: NaiveDateTime) -> Result<Option<Result<NaiveDateTime>>> {
let date = d.format(&DATE_FMT).to_string();
self.get_header_mut()
.insert(&DATE_HEADER_LOCATION, Value::String(date))
.map(|opt| opt.map(|stri| {
match stri {
Value::String(ref s) => s.parse::<NaiveDateTime>()
.map_err_into(DEK::DateTimeParsingError),
_ => Err(DEK::DateHeaderFieldTypeError.into_error()),
}
}))
2017-05-20 11:55:19 +00:00
.map_err_into(DEK::SetDateError)
2017-05-19 15:11:36 +00:00
}
2017-05-19 15:43:42 +00:00
/// Deletes the date range
///
/// # Warning
///
/// First deletes the start, then the end. If the first operation fails, this might leave the
/// header in an inconsistent state.
///
fn delete_date_range(&mut self) -> Result<()> {
let _ = try!(self
.get_header_mut()
.delete(&DATE_RANGE_START_HEADER_LOCATION)
.map(|_| ())
2017-06-06 17:28:00 +00:00
.map_err_into(DEK::DeleteDateTimeRangeError));
2017-05-19 15:43:42 +00:00
self.get_header_mut()
.delete(&DATE_RANGE_END_HEADER_LOCATION)
.map(|_| ())
2017-06-06 17:28:00 +00:00
.map_err_into(DEK::DeleteDateTimeRangeError)
2017-05-19 15:43:42 +00:00
}
2017-06-06 17:28:00 +00:00
fn read_date_range(&self) -> Result<DateTimeRange> {
2017-05-19 15:43:42 +00:00
let start = try!(self
.get_header()
.read(&DATE_RANGE_START_HEADER_LOCATION)
2017-06-06 17:28:00 +00:00
.map_err_into(DEK::ReadDateTimeRangeError)
2017-05-19 15:43:42 +00:00
.and_then(|v| {
match v {
&Value::String(ref s) => s.parse::<NaiveDateTime>()
.map_err_into(DEK::DateTimeParsingError),
_ => Err(DEK::DateHeaderFieldTypeError.into_error()),
}
}));
let end = try!(self
.get_header()
.read(&DATE_RANGE_START_HEADER_LOCATION)
2017-06-06 17:28:00 +00:00
.map_err_into(DEK::ReadDateTimeRangeError)
2017-05-19 15:43:42 +00:00
.and_then(|v| {
match v {
&Value::String(ref s) => s.parse::<NaiveDateTime>()
.map_err_into(DEK::DateTimeParsingError),
_ => Err(DEK::DateHeaderFieldTypeError.into_error()),
}
}));
2017-06-06 17:28:00 +00:00
DateTimeRange::new(start, end)
.map_err_into(DEK::DateTimeRangeError)
2017-05-19 15:43:42 +00:00
}
/// Set the date range
///
/// # Warning
///
/// This first sets the start, then the end. If the first operation fails, this might leave the
/// header in an inconsistent state.
///
fn set_date_range(&mut self, start: NaiveDateTime, end: NaiveDateTime)
2017-06-06 17:28:00 +00:00
-> Result<Option<Result<DateTimeRange>>>
2017-05-19 15:43:42 +00:00
{
let start = start.format(&DATE_FMT).to_string();
let end = end.format(&DATE_FMT).to_string();
let opt_old_start = try!(self
.get_header_mut()
.insert(&DATE_RANGE_START_HEADER_LOCATION, Value::String(start))
.map(|opt| opt.map(|stri| {
match stri {
Value::String(ref s) => s.parse::<NaiveDateTime>()
.map_err_into(DEK::DateTimeParsingError),
_ => Err(DEK::DateHeaderFieldTypeError.into_error()),
}
}))
2017-06-06 17:28:00 +00:00
.map_err_into(DEK::SetDateTimeRangeError));
2017-05-19 15:43:42 +00:00
let opt_old_end = try!(self
.get_header_mut()
.insert(&DATE_RANGE_END_HEADER_LOCATION, Value::String(end))
.map(|opt| opt.map(|stri| {
match stri {
Value::String(ref s) => s.parse::<NaiveDateTime>()
.map_err_into(DEK::DateTimeParsingError),
_ => Err(DEK::DateHeaderFieldTypeError.into_error()),
}
}))
2017-06-06 17:28:00 +00:00
.map_err_into(DEK::SetDateTimeRangeError));
2017-05-19 15:43:42 +00:00
match (opt_old_start, opt_old_end) {
2017-06-06 17:28:00 +00:00
(Some(Ok(old_start)), Some(Ok(old_end))) => {
let dr = DateTimeRange::new(old_start, old_end)
.map_err_into(DEK::DateTimeRangeError);
Ok(Some(dr))
},
2017-05-19 15:43:42 +00:00
(Some(Err(e)), _) => Err(e),
(_, Some(Err(e))) => Err(e),
_ => {
Ok(None)
},
}
}
2017-05-19 15:11:36 +00:00
}
2017-06-07 10:01:39 +00:00
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::*;
use libimagstore::store::Store;
use chrono::naive::datetime::NaiveDateTime;
use chrono::naive::date::NaiveDate;
use chrono::naive::time::NaiveTime;
pub fn get_store() -> Store {
Store::new(PathBuf::from("/"), None).unwrap()
}
#[test]
fn test_set_date() {
let store = get_store();
let date = {
let date = NaiveDate::from_ymd(2000, 01, 02);
let time = NaiveTime::from_hms(03, 04, 05);
NaiveDateTime::new(date, time)
};
let mut entry = store.create(PathBuf::from("test")).unwrap();
let res = entry.set_date(date);
assert!(res.is_ok(), format!("Error: {:?}", res));
let res = res.unwrap();
assert!(res.is_none()); // There shouldn't be an existing value
// Check whether the header is set correctly
let hdr_field = entry.get_header().read(&DATE_HEADER_LOCATION);
assert!(hdr_field.is_ok());
let hdr_field = hdr_field.unwrap();
match *hdr_field {
Value::String(ref s) => assert_eq!("2000-01-02T03:04:05", s),
_ => assert!(false, "Wrong header type"),
}
}
}