kronos/
seq_interval.rs

1#![deny(warnings)]
2
3use crate::utils;
4use crate::types::{DateTime, Range, TimeSequence};
5
6// example duckling intervals http://tinyurl.com/hk2vu34
7
8#[derive(Clone)]
9pub struct Interval<SeqA, SeqB>
10    where SeqA: TimeSequence,
11          SeqB: TimeSequence + Clone
12{
13    start: SeqA,
14    end: SeqB,
15    inclusive: bool,
16}
17
18impl<SeqA, SeqB> Interval<SeqA, SeqB>
19    where SeqA: TimeSequence,
20          SeqB: TimeSequence + Clone
21{
22    fn _base(&self, t0: &DateTime, future: bool) -> Box<dyn Iterator<Item=Range> + '_> {
23        let endseq = self.end.clone();
24        let inclusive = self.inclusive;
25
26        // interval generator
27        let interval = move |istart: Range| {
28            use std::cmp;
29            let iend = endseq._future_raw(&istart.start).next().unwrap();
30            Range{
31                start: istart.start,
32                end: if inclusive { iend.end } else { iend.start },
33                grain: cmp::min(istart.grain, iend.grain)
34            }
35        };
36
37        // guesstimate resolution for framing/truncating reftime so that
38        // initial interval can contain t0 even if end-of start element past
39        let probe = interval(self.start._future_raw(t0).next().unwrap());
40        // estimate grain from interval length
41        let trunc_grain = utils::enclosing_grain_from_duration(probe.duration());
42        // choose a time of reference aligned to interval on enclosing grain
43        let t0 = utils::truncate(*t0, trunc_grain);
44        let t0 = self.start._future_raw(&t0).next().unwrap().start;
45
46        Box::new(if future {
47            self.start._future_raw(&t0)
48        } else {
49            self.start._past_raw(&t0)
50        }.map(interval))
51    }
52}
53
54impl<SeqA, SeqB> TimeSequence for Interval<SeqA, SeqB>
55    where SeqA: TimeSequence,
56          SeqB: TimeSequence + Clone
57{
58    fn _future_raw(&self, t0: &DateTime) -> Box<dyn Iterator<Item=Range> + '_> {
59        self._base(t0, true)
60    }
61
62    fn _past_raw(&self, t0: &DateTime) -> Box<dyn Iterator<Item=Range> + '_> {
63        self._base(t0, false)
64    }
65}
66
67
68#[cfg(test)]
69mod test {
70    use super::*;
71    use crate::types::{Date, Grain};
72    use crate::seq_named::{Weekday, Month};
73    use crate::seq_nthof::NthOf;
74    use crate::seq_grain::Grains;
75
76    fn dt(year: i32, month: u32, day: u32) -> DateTime {
77        Date::from_ymd(year, month, day).and_hms(0, 0, 0)
78    }
79
80    fn dttm(year: i32, month: u32, day: u32, h: u32, m: u32, s: u32) -> DateTime {
81        Date::from_ymd(year, month, day).and_hms(h, m, s)
82    }
83
84    #[test]
85    fn interval_basic() {
86        // monday to friday
87        let mon2fri = Interval{start: Weekday(1), end: Weekday(5), inclusive: true};
88
89        let mut fut = mon2fri.future(&dt(2016, 2, 25));
90        assert_eq!(fut.next().unwrap(),
91            Range{start: dt(2016, 2, 22), end: dt(2016, 2, 27), grain: Grain::Day});
92        assert_eq!(fut.next().unwrap(),
93            Range{start: dt(2016, 2, 29), end: dt(2016, 3, 5), grain: Grain::Day});
94
95        // past non-inclusive
96        let mut past = mon2fri.past(&dt(2016, 2, 25));
97        assert_eq!(past.next().unwrap(),
98            Range{start: dt(2016, 2, 15), end: dt(2016, 2, 20), grain: Grain::Day});
99        assert_eq!(past.next().unwrap(),
100            Range{start: dt(2016, 2, 8), end: dt(2016, 2, 13), grain: Grain::Day});
101
102        // past inclusive
103        let mut past = mon2fri._past_raw(&dt(2016, 2, 25));
104        assert_eq!(past.next().unwrap(),
105            Range{start: dt(2016, 2, 22), end: dt(2016, 2, 27), grain: Grain::Day});
106        assert_eq!(past.next().unwrap(),
107            Range{start: dt(2016, 2, 15), end: dt(2016, 2, 20), grain: Grain::Day});
108        assert_eq!(past.next().unwrap(),
109            Range{start: dt(2016, 2, 8), end: dt(2016, 2, 13), grain: Grain::Day});
110    }
111
112    #[test]
113    fn interval_afternoon() {
114        let afternoon = Interval{
115            start: NthOf(13, Grains(Grain::Hour), Grains(Grain::Day)),
116            end: NthOf(19, Grains(Grain::Hour), Grains(Grain::Day)),
117            inclusive: false};
118
119        let mut iter = afternoon.future(&dt(2016, 2, 25));
120        assert_eq!(iter.next().unwrap(),
121            Range{start: dttm(2016, 2, 25, 12, 0, 0),
122                  end: dttm(2016, 2, 25, 18, 0, 0), grain: Grain::Hour});
123        assert_eq!(iter.next().unwrap(),
124           Range{start: dttm(2016, 2, 26, 12, 0, 0),
125                 end: dttm(2016, 2, 26, 18, 0, 0), grain: Grain::Hour});
126
127        // past non-inclusive
128        let mut iter = afternoon.past(&dttm(2016, 2, 25, 14, 0, 0));
129        assert_eq!(iter.next().unwrap(),
130            Range{start: dttm(2016, 2, 24, 12, 0, 0),
131                  end: dttm(2016, 2, 24, 18, 0, 0), grain: Grain::Hour});
132        assert_eq!(iter.next().unwrap(),
133           Range{start: dttm(2016, 2, 23, 12, 0, 0),
134                 end: dttm(2016, 2, 23, 18, 0, 0), grain: Grain::Hour});
135
136        // past inclusive
137        let mut iter = afternoon._past_raw(&dttm(2016, 2, 25, 14, 0, 0));
138        assert_eq!(iter.next().unwrap(),
139            Range{start: dttm(2016, 2, 25, 12, 0, 0),
140                  end: dttm(2016, 2, 25, 18, 0, 0), grain: Grain::Hour});
141        assert_eq!(iter.next().unwrap(),
142            Range{start: dttm(2016, 2, 24, 12, 0, 0),
143                  end: dttm(2016, 2, 24, 18, 0, 0), grain: Grain::Hour});
144        assert_eq!(iter.next().unwrap(),
145           Range{start: dttm(2016, 2, 23, 12, 0, 0),
146                 end: dttm(2016, 2, 23, 18, 0, 0), grain: Grain::Hour});
147    }
148
149    #[test]
150    fn interval_mixed() {
151        let june2ndtileom = Interval{
152            start: NthOf(2, Grains(Grain::Day), Month(6)),
153            end: Month(6), inclusive: true};
154
155        let mut iter = june2ndtileom.future(&dt(2016, 6, 25));
156        assert_eq!(iter.next().unwrap(),
157            Range{start: dt(2016, 6, 2), end: dt(2016, 7, 1), grain: Grain::Day});
158        assert_eq!(iter.next().unwrap(),
159            Range{start: dt(2017, 6, 2), end: dt(2017, 7, 1), grain: Grain::Day});
160
161        // past non-inclusive
162        let mut iter = june2ndtileom.past(&dt(2016, 6, 25));
163        assert_eq!(iter.next().unwrap(),
164            Range{start: dt(2015, 6, 2), end: dt(2015, 7, 1), grain: Grain::Day});
165        assert_eq!(iter.next().unwrap(),
166            Range{start: dt(2014, 6, 2), end: dt(2014, 7, 1), grain: Grain::Day});
167
168        // past inclusive
169        let mut iter = june2ndtileom._past_raw(&dt(2016, 6, 25));
170        assert_eq!(iter.next().unwrap(),
171            Range{start: dt(2016, 6, 2), end: dt(2016, 7, 1), grain: Grain::Day});
172        assert_eq!(iter.next().unwrap(),
173            Range{start: dt(2015, 6, 2), end: dt(2015, 7, 1), grain: Grain::Day});
174        assert_eq!(iter.next().unwrap(),
175            Range{start: dt(2014, 6, 2), end: dt(2014, 7, 1), grain: Grain::Day});
176    }
177}