elastic_types/date/
formats.rs

1use std::error::Error;
2use chrono::{DateTime, NaiveDateTime, Timelike, Utc};
3use super::{DateFormat, DateValue, FormattedDate, ParseError};
4
5/** The default `date` format (`BasicDateTime`). */
6pub type DefaultDateFormat = BasicDateTime;
7
8/** Format for default `chrono::DateTime`. */
9#[derive(ElasticDateFormat, PartialEq, Debug, Default, Clone, Copy)]
10#[elastic(date_format = "yyyy-MM-dd'T'HH:mm:ssZ")]
11pub struct ChronoFormat;
12
13/**
14Format for `basic_date_time_no_millis`.
15
16# Links
17- [Elasticsearch Doc](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#built-in-date-formats)
18*/
19#[derive(ElasticDateFormat, PartialEq, Debug, Default, Clone, Copy)]
20#[elastic(date_format = "yyyyMMdd'T'HHmmssZ", date_format_name = "basic_date_time_no_millis")]
21pub struct BasicDateTimeNoMillis;
22
23/**
24Format for `basic_date_time`.
25
26# Links
27- [Elasticsearch Doc](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#built-in-date-formats)
28*/
29#[derive(ElasticDateFormat, PartialEq, Debug, Default, Clone, Copy)]
30#[elastic(date_format = "yyyyMMdd'T'HHmmss.SSSZ", date_format_name = "basic_date_time")]
31pub struct BasicDateTime;
32
33/**
34Format for `epoch_millis`.
35
36Takes up to a 13 digit string of millis since the epoch and converts to a `DateTime`.
37This is an efficient formatter, so is a good choice for storing timestamps.
38
39# Links
40- [Elasticsearch Doc](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#built-in-date-formats)
41*/
42#[derive(PartialEq, Debug, Default, Clone, Copy)]
43pub struct EpochMillis;
44
45impl DateFormat for EpochMillis {
46    fn name() -> &'static str {
47        "epoch_millis"
48    }
49
50    fn parse(date: &str) -> Result<DateValue, ParseError> {
51        let millis = date.parse::<i64>()
52            .map_err(|e| e.description().to_string())?;
53
54        let (s, m) = {
55            // For positive timestamps:
56            // Extract the millis straight off the timestamp (how many millis since the last second?)
57            if millis >= 0 {
58                ((millis / 1000), (millis % 1000))
59            }
60            // For negative timestamps:
61            // The millis need to be inverted (how many millis before the next second?)
62            else {
63                ((millis / 1000) - 1, (1000 + (millis % 1000)))
64            }
65        };
66
67        let date = DateTime::from_utc(NaiveDateTime::from_timestamp(s, m as u32 * 1000000), Utc);
68
69        Ok(date.into())
70    }
71
72    fn format<'a>(date: &'a DateValue) -> FormattedDate<'a> {
73        let msec = (date.timestamp() * 1000) + (date.nanosecond() as i64 / 1000000);
74        msec.into()
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use chrono::{DateTime, Utc};
81    use date::{format, parse};
82    use prelude::*;
83
84    #[test]
85    fn chrono() {
86        let date = parse::<DefaultDateMapping<ChronoFormat>>("2015-07-03T14:55:02Z").unwrap();
87
88        assert_eq!(
89            (2015i32, 7u32, 3u32, 14u32, 55u32, 2u32),
90            (
91                date.year(),
92                date.month(),
93                date.day(),
94                date.hour(),
95                date.minute(),
96                date.second()
97            )
98        );
99
100        let fmtd = format(&date).to_string();
101        assert_eq!("2015-07-03T14:55:02Z", &fmtd);
102    }
103
104    #[test]
105    fn chrono_name() {
106        assert_eq!("yyyy-MM-dd'T'HH:mm:ssZ", ChronoFormat::name());
107    }
108
109    #[test]
110    fn basic_datetime_no_millis() {
111        let date = parse::<DefaultDateMapping<BasicDateTimeNoMillis>>("20150703T145502Z").unwrap();
112
113        assert_eq!(
114            (2015i32, 7u32, 3u32, 14u32, 55u32, 2u32),
115            (
116                date.year(),
117                date.month(),
118                date.day(),
119                date.hour(),
120                date.minute(),
121                date.second()
122            )
123        );
124
125        let fmtd = format(&date).to_string();
126        assert_eq!("20150703T145502Z", &fmtd);
127    }
128
129    #[test]
130    fn basic_datetime_no_millis_name() {
131        assert_eq!("basic_date_time_no_millis", BasicDateTimeNoMillis::name());
132    }
133
134    #[test]
135    fn basic_date_time() {
136        let date = parse::<DefaultDateMapping<BasicDateTime>>("20150703T145502.478Z").unwrap();
137
138        assert_eq!(
139            (2015i32, 7u32, 3u32, 14u32, 55u32, 2u32, 478u32),
140            (
141                date.year(),
142                date.month(),
143                date.day(),
144                date.hour(),
145                date.minute(),
146                date.second(),
147                date.nanosecond() / 1000000
148            )
149        );
150
151        let fmtd = format(&date).to_string();
152        assert_eq!("20150703T145502.478Z", &fmtd);
153    }
154
155    #[test]
156    fn basic_date_time_name() {
157        assert_eq!("basic_date_time", BasicDateTime::name());
158    }
159
160    #[test]
161    fn epoch_millis() {
162        let date = parse::<DefaultDateMapping<EpochMillis>>("1435935302478").unwrap();
163
164        assert_eq!(
165            (2015i32, 7u32, 3u32, 14u32, 55u32, 2u32, 478u32),
166            (
167                date.year(),
168                date.month(),
169                date.day(),
170                date.hour(),
171                date.minute(),
172                date.second(),
173                date.nanosecond() / 1000000
174            )
175        );
176
177        let fmtd = format(&date).to_string();
178        assert_eq!("1435935302478", &fmtd);
179    }
180
181    #[test]
182    fn epoch_millis_name() {
183        assert_eq!("epoch_millis", EpochMillis::name());
184    }
185
186    #[test]
187    fn epoch_millis_no_millis() {
188        let date = parse::<DefaultDateMapping<EpochMillis>>("1435935302000").unwrap();
189
190        assert_eq!(
191            (2015i32, 7u32, 3u32, 14u32, 55u32, 2u32, 0u32),
192            (
193                date.year(),
194                date.month(),
195                date.day(),
196                date.hour(),
197                date.minute(),
198                date.second(),
199                date.nanosecond() / 1000000
200            )
201        );
202
203        let fmtd = format(&date).to_string();
204        assert_eq!("1435935302000", &fmtd);
205    }
206
207    #[test]
208    fn epoch_millis_minus() {
209        let date = parse::<DefaultDateMapping<EpochMillis>>("-8031171898478").unwrap();
210
211        assert_eq!(
212            (1715i32, 7u32, 3u32, 14u32, 55u32, 1u32, 522u32),
213            (
214                date.year(),
215                date.month(),
216                date.day(),
217                date.hour(),
218                date.minute(),
219                date.second(),
220                date.nanosecond() / 1000000
221            )
222        );
223
224        let fmtd = format(&date).to_string();
225        assert_eq!("-8031171898478", &fmtd);
226    }
227
228    #[test]
229    fn epoch_millis_minus_no_millis() {
230        let date = parse::<DefaultDateMapping<EpochMillis>>("-8031171898000").unwrap();
231
232        assert_eq!(
233            (1715i32, 7u32, 3u32, 14u32, 55u32, 1u32, 1000u32),
234            (
235                date.year(),
236                date.month(),
237                date.day(),
238                date.hour(),
239                date.minute(),
240                date.second(),
241                date.nanosecond() / 1000000
242            )
243        );
244
245        let fmtd = format(&date).to_string();
246        assert_eq!("-8031171898000", &fmtd);
247    }
248
249    #[test]
250    fn epoch_millis_very_short() {
251        let date = parse::<DefaultDateMapping<EpochMillis>>("100").unwrap();
252
253        assert_eq!(
254            (1970i32, 1u32, 1u32, 0u32, 0u32, 0u32, 100u32),
255            (
256                date.year(),
257                date.month(),
258                date.day(),
259                date.hour(),
260                date.minute(),
261                date.second(),
262                date.nanosecond() / 1000000
263            )
264        );
265
266        let fmtd = format(&date).to_string();
267        assert_eq!("100", &fmtd);
268    }
269
270    #[test]
271    fn epoch_millis_short() {
272        let date = parse::<DefaultDateMapping<EpochMillis>>("5100").unwrap();
273
274        assert_eq!(
275            (1970i32, 1u32, 1u32, 0u32, 0u32, 5u32, 100u32),
276            (
277                date.year(),
278                date.month(),
279                date.day(),
280                date.hour(),
281                date.minute(),
282                date.second(),
283                date.nanosecond() / 1000000
284            )
285        );
286
287        let fmtd = format(&date).to_string();
288        assert_eq!("5100", &fmtd);
289    }
290
291    #[test]
292    fn epoch_millis_very_short_minus() {
293        let date = parse::<DefaultDateMapping<EpochMillis>>("-100").unwrap();
294
295        assert_eq!(
296            (1969i32, 12u32, 31u32, 23u32, 59u32, 59u32, 900u32),
297            (
298                date.year(),
299                date.month(),
300                date.day(),
301                date.hour(),
302                date.minute(),
303                date.second(),
304                date.nanosecond() / 1000000
305            )
306        );
307
308        let fmtd = format(&date).to_string();
309        assert_eq!("-100", &fmtd);
310    }
311
312    #[test]
313    fn epoch_millis_short_minus() {
314        let date = parse::<DefaultDateMapping<EpochMillis>>("-5100").unwrap();
315
316        assert_eq!(
317            (1969i32, 12u32, 31u32, 23u32, 59u32, 54u32, 900u32),
318            (
319                date.year(),
320                date.month(),
321                date.day(),
322                date.hour(),
323                date.minute(),
324                date.second(),
325                date.nanosecond() / 1000000
326            )
327        );
328
329        let fmtd = format(&date).to_string();
330        assert_eq!("-5100", &fmtd);
331    }
332
333    #[test]
334    fn epoch_millis_zero() {
335        let date = parse::<DefaultDateMapping<EpochMillis>>("0").unwrap();
336
337        assert_eq!(
338            (1970i32, 1u32, 1u32, 0u32, 0u32, 0u32, 0u32),
339            (
340                date.year(),
341                date.month(),
342                date.day(),
343                date.hour(),
344                date.minute(),
345                date.second(),
346                date.nanosecond() / 1000000
347            )
348        );
349
350        let fmtd = format(&date).to_string();
351        assert_eq!("0", &fmtd);
352    }
353
354    #[test]
355    fn custom_format() {
356        #[derive(Default)]
357        struct MyCustomFormat;
358        impl DateFormat for MyCustomFormat {
359            fn name() -> &'static str {
360                "yyyy-MM-dd'T'HH:mm:ssZ"
361            }
362
363            fn format<'a>(date: &'a DateValue) -> FormattedDate<'a> {
364                date.to_rfc3339().into()
365            }
366
367            fn parse(date: &str) -> Result<DateValue, ParseError> {
368                let date = DateTime::parse_from_rfc3339(date).map_err(|e| ParseError::from(e))?;
369
370                Ok(DateTime::from_utc(date.naive_local(), Utc).into())
371            }
372        }
373
374        let date = parse::<DefaultDateMapping<MyCustomFormat>>("2015-07-03T14:55:02+00:00").unwrap();
375
376        assert_eq!(
377            (2015i32, 7u32, 3u32, 14u32, 55u32, 2u32),
378            (
379                date.year(),
380                date.month(),
381                date.day(),
382                date.hour(),
383                date.minute(),
384                date.second()
385            )
386        );
387
388        let fmtd = format(&date).to_string();
389        assert_eq!("2015-07-03T14:55:02+00:00", &fmtd);
390    }
391}