use std::borrow::Borrow;
use std::ops::Deref;
use std::marker::PhantomData;
use std::fmt::{Display, Formatter, Result as FmtResult};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde::de::{Error, Visitor};
use super::format::{DateFormat, DateValue, FormattableDateValue, FormattedDate, ParseError};
use super::formats::ChronoFormat;
use super::mapping::{DateFieldType, DateMapping, DefaultDateMapping};
use private::field::StdField;
pub use chrono::{Datelike, Timelike};
pub type ChronoDateTime = DateTime<Utc>;
impl DateFieldType<DefaultDateMapping<ChronoFormat>> for ChronoDateTime {}
impl From<ChronoDateTime> for FormattableDateValue<ChronoFormat> {
fn from(date: ChronoDateTime) -> Self {
FormattableDateValue::from(DateValue::from(date))
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Date<TMapping>
where
TMapping: DateMapping,
{
value: FormattableDateValue<TMapping::Format>,
}
impl<TMapping> Date<TMapping>
where
TMapping: DateMapping,
{
pub fn new<I>(date: I) -> Self
where
I: Into<FormattableDateValue<TMapping::Format>>,
{
Date { value: date.into() }
}
pub fn build(year: i32, month: u32, day: u32, hour: u32, minute: u32, second: u32, milli: u32) -> Self {
Date::new(DateValue::build(
year,
month,
day,
hour,
minute,
second,
milli,
))
}
pub fn now() -> Self {
Date::new(DateValue::now())
}
pub fn remap<TNewMapping>(date: Date<TMapping>) -> Date<TNewMapping>
where
TNewMapping: DateMapping,
{
Date::new(DateValue::from(date.value))
}
}
impl<TMapping> DateFieldType<TMapping> for Date<TMapping>
where
TMapping: DateMapping,
{
}
impl<TMapping> From<Date<TMapping>> for FormattableDateValue<TMapping::Format>
where
TMapping: DateMapping,
{
fn from(date: Date<TMapping>) -> Self {
date.value
}
}
impl<TMapping> From<FormattableDateValue<TMapping::Format>> for Date<TMapping>
where
TMapping: DateMapping,
{
fn from(date: FormattableDateValue<TMapping::Format>) -> Self {
Date::new(date)
}
}
impl<TMapping> From<Date<TMapping>> for DateValue
where
TMapping: DateMapping,
{
fn from(date: Date<TMapping>) -> Self {
date.value.into()
}
}
impl<TMapping> From<DateValue> for Date<TMapping>
where
TMapping: DateMapping,
{
fn from(value: DateValue) -> Self {
Date::new(value)
}
}
impl<TMapping> StdField<ChronoDateTime> for Date<TMapping>
where
TMapping: DateMapping,
{
}
impl<TMapping> PartialEq<ChronoDateTime> for Date<TMapping>
where
TMapping: DateMapping,
{
fn eq(&self, other: &ChronoDateTime) -> bool {
PartialEq::eq(&self.value, other)
}
fn ne(&self, other: &ChronoDateTime) -> bool {
PartialEq::ne(&self.value, other)
}
}
impl<TMapping> PartialEq<Date<TMapping>> for ChronoDateTime
where
TMapping: DateMapping,
{
fn eq(&self, other: &Date<TMapping>) -> bool {
PartialEq::eq(self, &other.value)
}
fn ne(&self, other: &Date<TMapping>) -> bool {
PartialEq::ne(self, &other.value)
}
}
impl<TMapping> Deref for Date<TMapping>
where
TMapping: DateMapping,
{
type Target = ChronoDateTime;
fn deref(&self) -> &ChronoDateTime {
self.value.borrow()
}
}
impl<TMapping> Borrow<ChronoDateTime> for Date<TMapping>
where
TMapping: DateMapping,
{
fn borrow(&self) -> &ChronoDateTime {
self.value.borrow()
}
}
impl<TMapping> Default for Date<TMapping>
where
TMapping: DateMapping,
{
fn default() -> Self {
Date::now()
}
}
impl<TMapping> Display for Date<TMapping>
where
TMapping: DateMapping,
{
fn fmt(&self, f: &mut Formatter) -> FmtResult {
write!(f, "{}", format(self))
}
}
impl<TMapping> Serialize for Date<TMapping>
where
TMapping: DateMapping,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_str(&self)
}
}
impl<'de, TMapping> Deserialize<'de> for Date<TMapping>
where
TMapping: DateMapping,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Default)]
struct DateTimeVisitor<TMapping> {
_t: PhantomData<TMapping>,
}
impl<'de, TMapping> Visitor<'de> for DateTimeVisitor<TMapping>
where
TMapping: DateMapping,
{
type Value = Date<TMapping>;
fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
write!(
formatter,
"a json string or number containing a formatted date"
)
}
fn visit_str<E>(self, v: &str) -> Result<Date<TMapping>, E>
where
E: Error,
{
parse(v).map_err(|err| Error::custom(format!("{}", err)))
}
fn visit_i64<E>(self, v: i64) -> Result<Date<TMapping>, E>
where
E: Error,
{
parse(&v.to_string()).map_err(|err| Error::custom(format!("{}", err)))
}
fn visit_u64<E>(self, v: u64) -> Result<Date<TMapping>, E>
where
E: Error,
{
parse(&v.to_string()).map_err(|err| Error::custom(format!("{}", err)))
}
}
deserializer.deserialize_any(DateTimeVisitor::<TMapping>::default())
}
}
pub(crate) fn format<'a, TMapping>(date: &'a Date<TMapping>) -> FormattedDate<'a>
where
TMapping: DateMapping,
{
date.value.format()
}
pub(crate) fn parse<TMapping>(date: &str) -> Result<Date<TMapping>, ParseError>
where
TMapping: DateMapping,
{
let parsed = FormattableDateValue::parse(date)?;
Ok(Date::new(parsed))
}
#[derive(Debug, Clone, PartialEq)]
pub struct DateExpr<TFormat> {
anchor: DateExprAnchor<TFormat>,
ops: Vec<DateExprOp>,
}
impl<TFormat> Display for DateExpr<TFormat>
where
TFormat: DateFormat,
{
fn fmt(&self, f: &mut Formatter) -> FmtResult {
self.anchor.fmt(f)?;
for op in &self.ops {
op.fmt(f)?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
enum DateExprAnchor<TFormat> {
Now,
Value(FormattableDateValue<TFormat>),
}
impl<TFormat> Display for DateExprAnchor<TFormat>
where
TFormat: DateFormat,
{
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match *self {
DateExprAnchor::Now => "now".fmt(f),
DateExprAnchor::Value(ref date) => write!(f, "{}||", date.format()),
}
}
}
#[derive(Debug, Clone, PartialEq)]
enum DateExprOp {
Add(usize, DateExprOpUnit),
Sub(usize, DateExprOpUnit),
Round(DateExprOpUnit),
}
impl Display for DateExprOp {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match *self {
DateExprOp::Add(size, unit) => write!(f, "+{}{}", size, unit),
DateExprOp::Sub(size, unit) => write!(f, "-{}{}", size, unit),
DateExprOp::Round(unit) => write!(f, "/{}", unit),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum DateExprOpUnit {
Year,
Month,
Week,
Day,
Hour,
Minute,
Second,
}
impl Display for DateExprOpUnit {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
let fmtd = match *self {
DateExprOpUnit::Year => "y",
DateExprOpUnit::Month => "M",
DateExprOpUnit::Week => "w",
DateExprOpUnit::Day => "d",
DateExprOpUnit::Hour => "h",
DateExprOpUnit::Minute => "m",
DateExprOpUnit::Second => "s",
};
fmtd.fmt(f)
}
}
macro_rules! impl_expr_ops {
($op:path, $add:ident, $sub:ident, $round:ident) => (
pub fn $add(mut self, value: usize) -> Self {
self.ops.push(DateExprOp::Add(value, $op));
self
}
pub fn $sub(mut self, value: usize) -> Self {
self.ops.push(DateExprOp::Sub(value, $op));
self
}
pub fn $round(mut self) -> Self {
self.ops.push(DateExprOp::Round($op));
self
}
)
}
impl<TFormat> DateExpr<TFormat>
where
TFormat: DateFormat,
{
pub fn now() -> Self {
DateExpr {
anchor: DateExprAnchor::Now,
ops: Vec::new(),
}
}
pub fn value<TDate>(date: TDate) -> Self
where
TDate: Into<FormattableDateValue<TFormat>>,
{
let date = date.into();
DateExpr {
anchor: DateExprAnchor::Value(date),
ops: Vec::new(),
}
}
impl_expr_ops!(DateExprOpUnit::Year, add_years, sub_years, round_year);
impl_expr_ops!(DateExprOpUnit::Month, add_months, sub_months, round_month);
impl_expr_ops!(DateExprOpUnit::Week, add_weeks, sub_weeks, round_week);
impl_expr_ops!(DateExprOpUnit::Day, add_days, sub_days, round_day);
impl_expr_ops!(DateExprOpUnit::Hour, add_hours, sub_hours, round_hour);
impl_expr_ops!(
DateExprOpUnit::Minute,
add_minutes,
sub_minutes,
round_minute
);
impl_expr_ops!(
DateExprOpUnit::Second,
add_seconds,
sub_seconds,
round_second
);
}
impl<TFormat> Serialize for DateExpr<TFormat>
where
TFormat: DateFormat,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_str(&self)
}
}
#[cfg(test)]
mod tests {
use serde_json;
use chrono;
use chrono::offset::TimeZone;
use prelude::*;
#[derive(ElasticDateFormat, Default, Clone)]
#[elastic(date_format = "yyyy/MM/dd HH:mm:ss", date_format_name = "test_date_1")]
pub struct NamedDateFormat;
#[derive(ElasticDateFormat, Default, Clone, Copy)]
#[elastic(date_format = "yyyyMMdd")]
pub struct UnNamedDateFormat;
#[test]
fn date_format_uses_name_if_supplied() {
assert_eq!("test_date_1", NamedDateFormat::name());
}
#[test]
fn date_format_uses_format_if_name_not_supplied() {
assert_eq!("yyyyMMdd", UnNamedDateFormat::name());
}
#[test]
fn dates_should_use_chrono_format() {
let dt = DateValue::build(2015, 05, 13, 0, 0, 0, 0);
let dt = Date::<DefaultDateMapping<NamedDateFormat>>::new(dt.clone());
let actual = format(&dt).to_string();
assert_eq!("2015/05/13 00:00:00", actual);
}
#[test]
fn dates_should_use_es_format() {
let dt = DateValue::build(2015, 05, 13, 0, 0, 0, 0);
let dt = Date::<DefaultDateMapping<UnNamedDateFormat>>::new(dt.clone());
let actual = format(&dt).to_string();
assert_eq!("20150513", actual);
}
#[test]
fn can_change_date_mapping() {
fn takes_epoch_millis(_: Date<DefaultDateMapping<EpochMillis>>) -> bool {
true
}
let date: Date<DefaultDateMapping<BasicDateTime>> = Date::now();
assert!(takes_epoch_millis(Date::remap(date)));
}
#[test]
fn can_build_date_from_value() {
let date: Date<DefaultDateMapping> = Date::new(DateValue::build(2015, 05, 13, 0, 0, 0, 0));
assert_eq!(
(2015, 5, 13, 0, 0, 0),
(
date.year(),
date.month(),
date.day(),
date.hour(),
date.minute(),
date.second()
)
);
}
#[test]
fn can_build_date_from_chrono() {
let date = chrono::Utc
.datetime_from_str("13/05/2015 00:00:00", "%d/%m/%Y %H:%M:%S")
.unwrap();
let date: Date<DefaultDateMapping<ChronoFormat>> = Date::new(date);
assert_eq!(
(2015, 5, 13, 0, 0, 0),
(
date.year(),
date.month(),
date.day(),
date.hour(),
date.minute(),
date.second()
)
);
}
#[test]
fn can_build_date_from_prim() {
let date: Date<DefaultDateMapping> = Date::build(2015, 5, 13, 0, 0, 0, 0);
assert_eq!(
(2015, 5, 13, 0, 0, 0),
(
date.year(),
date.month(),
date.day(),
date.hour(),
date.minute(),
date.second()
)
);
}
#[test]
fn serialise_elastic_date() {
let date = Date::<DefaultDateMapping<BasicDateTime>>::new(DateValue::build(2015, 05, 13, 0, 0, 0, 0));
let ser = serde_json::to_string(&date).unwrap();
assert_eq!(r#""20150513T000000.000Z""#, ser);
}
#[test]
fn deserialise_elastic_date() {
let date: Date<DefaultDateMapping<BasicDateTime>> = serde_json::from_str(r#""20150513T000000.000Z""#).unwrap();
assert_eq!((2015, 5, 13), (date.year(), date.month(), date.day()));
}
#[test]
fn serialise_date_expr_now() {
let expr = DateExpr::<DefaultDateFormat>::now();
let ser = serde_json::to_string(&expr).unwrap();
assert_eq!(r#""now""#, ser);
}
#[test]
fn serialise_date_expr_chrono() {
let date = chrono::Utc
.datetime_from_str("13/05/2015 00:00:00", "%d/%m/%Y %H:%M:%S")
.unwrap();
let expr = DateExpr::value(date);
let ser = serde_json::to_string(&expr).unwrap();
assert_eq!(r#""2015-05-13T00:00:00Z||""#, ser);
}
#[test]
fn serialise_date_expr_date() {
let expr = DateExpr::value(Date::<DefaultDateMapping<BasicDateTime>>::build(
2015,
5,
13,
0,
0,
0,
0,
));
let ser = serde_json::to_string(&expr).unwrap();
assert_eq!(r#""20150513T000000.000Z||""#, ser);
}
#[test]
fn serialise_date_expr_value_with_ops() {
let expr = DateExpr::value(Date::<DefaultDateMapping<BasicDateTime>>::build(
2015,
5,
13,
0,
0,
0,
0,
)).add_days(2)
.round_week();
let ser = serde_json::to_string(&expr).unwrap();
assert_eq!(r#""20150513T000000.000Z||+2d/w""#, ser);
}
#[test]
fn serialise_date_expr_add() {
let expr = DateExpr::<DefaultDateFormat>::now()
.add_years(1)
.add_months(2)
.add_weeks(3)
.add_days(4)
.add_hours(5)
.add_minutes(6)
.add_seconds(7);
let ser = serde_json::to_string(&expr).unwrap();
assert_eq!(r#""now+1y+2M+3w+4d+5h+6m+7s""#, ser);
}
#[test]
fn serialise_date_expr_sub() {
let expr = DateExpr::<DefaultDateFormat>::now()
.sub_years(1)
.sub_months(2)
.sub_weeks(3)
.sub_days(4)
.sub_hours(5)
.sub_minutes(6)
.sub_seconds(7);
let ser = serde_json::to_string(&expr).unwrap();
assert_eq!(r#""now-1y-2M-3w-4d-5h-6m-7s""#, ser);
}
#[test]
fn serialise_date_expr_round() {
let expr = DateExpr::<DefaultDateFormat>::now()
.round_year()
.round_month()
.round_week()
.round_day()
.round_hour()
.round_minute()
.round_second();
let ser = serde_json::to_string(&expr).unwrap();
assert_eq!(r#""now/y/M/w/d/h/m/s""#, ser);
}
}