timespan/
date_time_span.rs

1// timespan - A simple timespan for chrono times.
2//
3// Copyright (C) 2017
4//     Fin Christensen <fin.christensen@posteo.de>
5//
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10//
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14// GNU General Public License for more details.
15//
16// You should have received a copy of the GNU General Public License
17// along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
19use crate::Error;
20use crate::Formatable;
21use crate::NaiveDateTimeSpan;
22use crate::Parsable;
23use crate::Span;
24use crate::Spanable;
25use chrono::format::{DelayedFormat, StrftimeItems};
26use chrono::offset::{FixedOffset, Local, Utc};
27use chrono::{DateTime as ChronoDateTime, Duration, TimeZone};
28use std;
29
30impl<T: TimeZone + std::marker::Copy> Spanable for ChronoDateTime<T>
31where
32    <T as TimeZone>::Offset: std::marker::Copy,
33{
34    #[inline]
35    fn signed_duration_since(self, other: Self) -> Duration {
36        ChronoDateTime::signed_duration_since(self, other)
37    }
38}
39
40impl<T: TimeZone> Formatable for ChronoDateTime<T>
41where
42    <T as TimeZone>::Offset: std::fmt::Display,
43{
44    #[inline]
45    fn format<'a>(&self, fmt: &'a str) -> DelayedFormat<StrftimeItems<'a>> {
46        ChronoDateTime::format(self, fmt)
47    }
48}
49
50impl Parsable for ChronoDateTime<Local> {
51    #[inline]
52    fn parse_from_str(s: &str, fmt: &str) -> Result<ChronoDateTime<Local>, Error> {
53        Local
54            .datetime_from_str(s, fmt)
55            .map_err(|e| Error::Parsing(e))
56    }
57}
58
59impl Parsable for ChronoDateTime<Utc> {
60    #[inline]
61    fn parse_from_str(s: &str, fmt: &str) -> Result<ChronoDateTime<Utc>, Error> {
62        Utc.datetime_from_str(s, fmt).map_err(|e| Error::Parsing(e))
63    }
64}
65
66impl Parsable for ChronoDateTime<FixedOffset> {
67    #[inline]
68    fn parse_from_str(s: &str, fmt: &str) -> Result<ChronoDateTime<FixedOffset>, Error> {
69        ChronoDateTime::parse_from_str(s, fmt).map_err(|e| Error::Parsing(e))
70    }
71}
72
73/// The `DateTimeSpan` alias is a span consisting of `chrono::DateTime`s.
74///
75/// It can be used to represent datetime spans that depend on a specific time zone.
76///
77/// The `DateTimeSpan` can be formatted and parsed from a string. It can be used for serialization
78/// and deserialization with `serde`. The deserialization is currently only supported for the
79/// `Utc`, `Local` and `FixedOffset` time zones. The time zones provided by `chrono-tz` do not
80/// implement `from_str` and `parse_from_str` for `chrono::DateTime<Tz>` and can therefore not be
81/// deserialized.
82///
83/// # Example
84///
85/// ~~~~
86/// # extern crate timespan; extern crate chrono; fn main() {
87/// use timespan::DateTimeSpan;
88/// use chrono::Utc;
89///
90/// let a: DateTimeSpan<Utc> = "2017-01-01T15:10:00 +0200 - 2017-01-02T09:30:00 +0200"
91///    .parse().unwrap();
92///
93/// assert!(
94///     format!("{}", a.format("{start} to {end}", "%c", "%c")) ==
95///     "Sun Jan  1 13:10:00 2017 to Mon Jan  2 07:30:00 2017"
96/// );
97/// # }
98/// ~~~~
99pub type DateTimeSpan<T> = Span<ChronoDateTime<T>>;
100
101impl<T: TimeZone> DateTimeSpan<T> {
102    /// Create a `DateTimeSpan` from a `NaiveDateTimeSpan` with the time zone set to the local time zone.
103    ///
104    /// Currently the result handling of the internally used `TimeZone::from_local_datetime` is not
105    /// implemented properly. Therefore only date spans with a single local time zone can be created.
106    /// Ambigious local time zones will lead to `Error::LocalAmbigious`.
107    ///
108    /// To avoid this `from_utc_datetimespan` should be prefered.
109    pub fn from_local_datetimespan(span: &NaiveDateTimeSpan, tz: &T) -> Result<Self, Error> {
110        Ok(DateTimeSpan {
111            start: tz
112                .from_local_datetime(&span.start)
113                .single()
114                .ok_or(Error::LocalAmbigious)?,
115            end: tz
116                .from_local_datetime(&span.end)
117                .single()
118                .ok_or(Error::LocalAmbigious)?,
119        })
120    }
121
122    /// Create a `DateTimeSpan` from a `NaiveDateTimeSpan` with the time zone set to UTC.
123    ///
124    /// # Example
125    ///
126    /// ~~~~
127    /// # extern crate timespan; extern crate chrono_tz; fn main() {
128    /// use timespan::DateTimeSpan;
129    /// use chrono_tz::America::Puerto_Rico;
130    ///
131    /// let a = DateTimeSpan::from_utc_datetimespan(
132    ///     &"2017-03-12T12:00:00 - 2017-03-15T14:00:00".parse().unwrap(),
133    ///     &Puerto_Rico,
134    /// );
135    ///
136    /// assert!(format!("{}", a) == "2017-03-12 08:00:00 AST - 2017-03-15 10:00:00 AST");
137    /// # }
138    /// ~~~~
139    pub fn from_utc_datetimespan(span: &NaiveDateTimeSpan, tz: &T) -> Self {
140        DateTimeSpan {
141            start: tz.from_utc_datetime(&span.start),
142            end: tz.from_utc_datetime(&span.end),
143        }
144    }
145}
146
147#[cfg(feature = "with-chrono-tz")]
148pub use self::with_chrono_tz::DateTime;
149
150#[cfg(feature = "with-chrono-tz")]
151mod with_chrono_tz {
152    use super::DateTimeSpan;
153    use super::Error;
154    use super::Parsable;
155    use chrono::{DateTime as ChronoDateTime, ParseError, TimeZone};
156    use chrono_tz::Tz;
157    use regex::Regex;
158    use std::convert::From;
159    use std::str::FromStr;
160
161    pub struct DateTime<T: TimeZone>(pub ChronoDateTime<T>);
162
163    impl<T: TimeZone> From<ChronoDateTime<T>> for DateTime<T> {
164        fn from(dt: ChronoDateTime<T>) -> DateTime<T> {
165            DateTime(dt)
166        }
167    }
168
169    impl FromStr for DateTime<Tz> {
170        type Err = ParseError;
171
172        #[inline]
173        fn from_str(s: &str) -> Result<Self, ParseError> {
174            // this is very unsafe:
175            // All Options and Results get unwrapped as we cannot create a ParseError
176
177            let re = Regex::new(r"(.*)\s+(\w+)$").unwrap();
178            let caps = re.captures(s).unwrap();
179
180            let c1 = caps.get(1).map(|m| m.as_str()).unwrap();
181            let c2 = caps.get(2).map(|m| m.as_str()).unwrap();
182
183            let tz = c2.parse::<Tz>().unwrap();
184            Tz::datetime_from_str(&tz, &c1, "%F %T").map(|dt| DateTime(dt))
185        }
186    }
187
188    impl Parsable for DateTime<Tz> {
189        #[inline]
190        fn parse_from_str(s: &str, fmt: &str) -> Result<DateTime<Tz>, Error> {
191            let re = Regex::new(r"(.*)\s+(\w+)$").unwrap();
192            let caps = re.captures(s).ok_or(Error::BadFormat)?;
193
194            let c1 = caps.get(1).map(|m| m.as_str()).ok_or(Error::BadFormat)?;
195            let c2 = caps.get(2).map(|m| m.as_str()).ok_or(Error::BadFormat)?;
196
197            let tz = c2.parse::<Tz>().map_err(|_| Error::BadFormat)?;
198            Tz::datetime_from_str(&tz, &c1, fmt)
199                .map(|dt| DateTime(dt))
200                .map_err(|e| Error::Parsing(e))
201        }
202    }
203
204    /// Parses a `Span` from a string in the format `{start} - {end}`.
205    impl FromStr for DateTimeSpan<Tz> {
206        type Err = Error;
207
208        fn from_str(s: &str) -> Result<Self, Self::Err> {
209            let re = Regex::new(r"(.*)\s+-\s+(.*)").unwrap();
210            let caps = re.captures(s).ok_or(Error::Empty)?;
211
212            let c1 = caps.get(1).ok_or(Error::NoStart)?;
213            let c2 = caps.get(2).ok_or(Error::NoEnd)?;
214
215            DateTimeSpan::new(
216                DateTime::from_str(c1.as_str()).map(|dt| dt.0)?,
217                DateTime::from_str(c2.as_str()).map(|dt| dt.0)?,
218            )
219        }
220    }
221}