date_range/
lib.rs

1use core::fmt;
2use std::error;
3
4use chrono::{Duration, NaiveDate, NaiveDateTime, Utc};
5
6mod iter;
7pub use iter::DateRangeIterator;
8
9#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
10pub struct DateRange {
11    since_: NaiveDate,
12    until_: NaiveDate,
13}
14
15impl Default for DateRange {
16    fn default() -> Self {
17        Self {
18            since_: NaiveDate::from_ymd_opt(1970, 1, 1).expect(""),
19            until_: NaiveDate::from_ymd_opt(1970, 1, 1).expect(""),
20        }
21    }
22}
23
24impl DateRange {
25    pub fn new(since: NaiveDate, until: NaiveDate) -> Result<Self, Error> {
26        if since > until {
27            return Err(Error::SinceShouldLtUntil);
28        }
29
30        Ok(Self {
31            since_: since,
32            until_: until,
33        })
34    }
35    pub fn from_timestamp(
36        since: i64,
37        mut until: i64,
38        configuration: impl Into<Option<DateRangeFromTimestampConfiguration>>,
39    ) -> Result<Self, Error> {
40        let configuration = configuration.into().unwrap_or_default();
41
42        if since > until {
43            return Err(Error::SinceShouldLtUntil);
44        }
45
46        if configuration.now_gteq_until {
47            let now = Utc::now().timestamp();
48            if until > now {
49                until = now;
50
51                if since > until {
52                    return Err(Error::SinceShouldLtUntil);
53                }
54            }
55        }
56
57        let since = NaiveDateTime::from_timestamp_opt(since, 0).expect("");
58        let until = NaiveDateTime::from_timestamp_opt(until, 0).expect("");
59
60        let mut since = since.date();
61        let until = until.date();
62
63        if (until - since).num_days() > configuration.max_interval.num_days() {
64            since = until - configuration.max_interval;
65        }
66
67        Ok(Self {
68            since_: since,
69            until_: until,
70        })
71    }
72
73    pub fn since(&self) -> NaiveDate {
74        self.since_.to_owned()
75    }
76    pub fn until(&self) -> NaiveDate {
77        self.until_.to_owned()
78    }
79
80    pub fn since_datetime(&self) -> NaiveDateTime {
81        self.since().and_hms_opt(0, 0, 0).expect("")
82    }
83    pub fn until_datetime(&self) -> NaiveDateTime {
84        self.until().and_hms_opt(23, 59, 59).expect("")
85    }
86
87    pub fn prev(&self) -> Self {
88        let prev_until = self.since() - Duration::days(1);
89        let len = self.until() - self.since();
90        Self {
91            since_: prev_until - len,
92            until_: prev_until,
93        }
94    }
95    pub fn next(&self) -> Self {
96        let next_since = self.until() + Duration::days(1);
97        let len = self.until() - self.since();
98        Self {
99            since_: next_since,
100            until_: next_since + len,
101        }
102    }
103
104    //
105    pub fn num_days(&self) -> usize {
106        (self.until() - self.since()).num_days() as usize
107    }
108}
109
110#[derive(Debug, Copy, Clone)]
111pub struct DateRangeFromTimestampConfiguration {
112    pub now_gteq_until: bool,
113    pub max_interval: Duration,
114}
115impl Default for DateRangeFromTimestampConfiguration {
116    fn default() -> Self {
117        Self {
118            now_gteq_until: true,
119            max_interval: Duration::days(2 * 365), // 2 years
120        }
121    }
122}
123impl DateRangeFromTimestampConfiguration {
124    pub fn new() -> Self {
125        Self::default()
126    }
127}
128
129//
130//
131//
132#[derive(Debug, Copy, Clone)]
133pub struct DateTimeRange {
134    since_: NaiveDateTime,
135    until_: NaiveDateTime,
136}
137impl DateTimeRange {
138    pub fn new(since: NaiveDateTime, until: NaiveDateTime) -> Result<Self, Error> {
139        if since > until {
140            return Err(Error::SinceShouldLtUntil);
141        }
142
143        Ok(Self {
144            since_: since,
145            until_: until,
146        })
147    }
148
149    pub fn since(&self) -> NaiveDateTime {
150        self.since_.to_owned()
151    }
152    pub fn until(&self) -> NaiveDateTime {
153        self.until_.to_owned()
154    }
155}
156impl From<&DateRange> for DateTimeRange {
157    fn from(dr: &DateRange) -> Self {
158        Self {
159            since_: dr.since_datetime(),
160            until_: dr.until_datetime(),
161        }
162    }
163}
164impl From<DateRange> for DateTimeRange {
165    fn from(dr: DateRange) -> Self {
166        Self {
167            since_: dr.since_datetime(),
168            until_: dr.until_datetime(),
169        }
170    }
171}
172
173//
174//
175//
176#[derive(Debug)]
177pub enum Error {
178    SinceShouldLtUntil,
179}
180impl fmt::Display for Error {
181    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182        write!(f, "{self:?}")
183    }
184}
185impl error::Error for Error {}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190
191    use std::error;
192
193    #[test]
194    fn test_date_range_default() {
195        assert_eq!(
196            DateRange::default(),
197            DateRange::new("1970-01-01".parse().unwrap(), "1970-01-01".parse().unwrap()).unwrap()
198        )
199    }
200
201    #[test]
202    fn test_date_range_new() -> Result<(), Box<dyn error::Error>> {
203        match DateRange::new("2021-08-02".parse().unwrap(), "2021-08-02".parse().unwrap()) {
204            Ok(_) => {}
205            Err(err) => {
206                panic!("{}", err)
207            }
208        }
209        match DateRange::new("2021-08-02".parse().unwrap(), "2021-08-01".parse().unwrap()) {
210            Ok(_) => panic!(),
211            Err(Error::SinceShouldLtUntil) => {}
212        }
213
214        Ok(())
215    }
216
217    #[test]
218    fn test_date_range_from_timestamp() {
219        let now = Utc::now();
220
221        //
222        let date_range = DateRange::from_timestamp(
223            (now - Duration::days(7)).timestamp(),
224            (now + Duration::days(3)).timestamp(),
225            None,
226        )
227        .unwrap();
228        assert_eq!(
229            date_range.since(),
230            now.naive_utc().date() - Duration::days(7)
231        );
232        assert_eq!(date_range.until(), now.naive_utc().date());
233
234        //
235        let mut configuration = DateRangeFromTimestampConfiguration::new();
236        configuration.now_gteq_until = false;
237        let date_range = DateRange::from_timestamp(
238            (now - Duration::days(7)).timestamp(),
239            (now + Duration::days(3)).timestamp(),
240            configuration,
241        )
242        .unwrap();
243        assert_eq!(
244            date_range.since(),
245            now.naive_utc().date() - Duration::days(7)
246        );
247        assert_eq!(
248            date_range.until(),
249            now.naive_utc().date() + Duration::days(3)
250        );
251
252        //
253        let date_range = DateRange::from_timestamp(
254            (now - Duration::days(10)).timestamp(),
255            now.timestamp(),
256            Some(DateRangeFromTimestampConfiguration {
257                now_gteq_until: true,
258                max_interval: Duration::days(7),
259            }),
260        )
261        .unwrap();
262        assert_eq!(
263            date_range.since(),
264            now.naive_utc().date() - Duration::days(7)
265        );
266        assert_eq!(date_range.until(), now.naive_utc().date());
267    }
268
269    #[test]
270    fn test_date_range_since_datetime_and_until_datetime() {
271        let date_range =
272            DateRange::new("2021-08-09".parse().unwrap(), "2021-08-15".parse().unwrap()).unwrap();
273
274        assert_eq!(
275            date_range.since_datetime(),
276            "2021-08-09T00:00:00".parse().unwrap()
277        );
278        assert_eq!(
279            date_range.until_datetime(),
280            "2021-08-15T23:59:59".parse().unwrap()
281        );
282    }
283
284    #[test]
285    fn test_date_range_prev_and_next() {
286        let date_range =
287            DateRange::new("2021-08-09".parse().unwrap(), "2021-08-15".parse().unwrap()).unwrap();
288
289        let prev_date_range = date_range.prev();
290        assert_eq!(prev_date_range.since(), "2021-08-02".parse().unwrap());
291        assert_eq!(prev_date_range.until(), "2021-08-08".parse().unwrap());
292
293        let next_date_range = date_range.next();
294        assert_eq!(next_date_range.since(), "2021-08-16".parse().unwrap());
295        assert_eq!(next_date_range.until(), "2021-08-22".parse().unwrap());
296    }
297
298    #[test]
299    fn test_date_range_num_days() {
300        let date_range =
301            DateRange::new("2022-02-23".parse().unwrap(), "2022-02-23".parse().unwrap()).unwrap();
302        assert_eq!(date_range.num_days(), 0);
303
304        let date_range =
305            DateRange::new("2022-02-21".parse().unwrap(), "2022-02-23".parse().unwrap()).unwrap();
306        assert_eq!(date_range.num_days(), 2);
307
308        let now = Utc::now();
309        let date_range =
310            DateRange::from_timestamp((now - Duration::days(7)).timestamp(), now.timestamp(), None)
311                .unwrap();
312        assert_eq!(date_range.num_days(), 7);
313    }
314}