1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#![deny(warnings)]

use std::collections::VecDeque;
use crate::types::{DateTime, Range, TimeSequence};

// Guard against impossible sequences, eg: 32nd day of the month
const INFINITE_FUSE: usize = 1000;

#[derive(Clone)]
pub struct LastOf<Frame, Win>(pub usize, pub Win, pub Frame)
    where Frame: TimeSequence,
          Win: TimeSequence + Clone;


impl<Frame, Win> LastOf<Frame, Win>
    where Frame: TimeSequence,
          Win: TimeSequence + Clone
{
    fn _base(&self, t0: &DateTime, future: bool)
        -> Box<Iterator<Item=Range> + '_>
    {
        let win = self.1.clone();
        let nth = self.0;
        let frame = if future {
            self.2._future_raw(t0)
        } else {
            self.2._past_raw(t0)
        };
        Box::new(frame
            .map(move |outer| {
                let mut buf = VecDeque::new();
                for inner in win._future_raw(&outer.start) {
                    if inner.start >= outer.end {
                        return buf.remove(nth-1);
                    }
                    buf.push_front(inner);
                    buf.truncate(nth);
                }
                None
            })
            .enumerate()
            .filter_map(|(i, elem)| { assert!(i <= INFINITE_FUSE); elem })
        )
    }
}

impl<Frame, Win> TimeSequence for LastOf<Frame, Win>
    where Frame: TimeSequence,
          Win: TimeSequence + Clone
{
    fn _future_raw(&self, t0: &DateTime) -> Box<Iterator<Item=Range> + '_> {
        self._base(t0, true)
    }

    fn _past_raw(&self, t0: &DateTime) -> Box<Iterator<Item=Range> + '_> {
        self._base(t0, false)
    }
}


#[cfg(test)]
mod test {
    use super::*;
    use crate::types::{Date, Grain};
    use crate::seq_grain::Grains;
    use crate::seq_named::{Weekend, Month};


    fn dt(year: i32, month: u32, day: u32) -> DateTime {
        Date::from_ymd(year, month, day).and_hms(0, 0, 0)
    }

    #[test]
    #[should_panic]
    fn lastof_fuse() {
        let badlastof = LastOf(32, Grains(Grain::Day), Grains(Grain::Month));
        badlastof.future(&dt(2015, 2, 25)).next();
    }

    #[test]
    fn lastof() {
        // last weekend of the year
        let weekendofyear = LastOf(1, Weekend, Grains(Grain::Year));
        let mut weekendofyear = weekendofyear.future(&dt(2015, 2, 25));
        assert_eq!(weekendofyear.next().unwrap(),
            Range{start: dt(2015, 12, 26), end: dt(2015, 12, 28), grain: Grain::Day});
        assert_eq!(weekendofyear.next().unwrap(),
            Range{start: dt(2016, 12, 31), end: dt(2017, 1, 2), grain: Grain::Day});

        // 2nd-to-last day of february
        let daybeforelastfeb = LastOf(2, Grains(Grain::Day), Month(2));
        let mut daybeforelastfeb = daybeforelastfeb.future(&dt(2015, 2, 25));
        assert_eq!(daybeforelastfeb.next().unwrap(),
            Range{start: dt(2015, 2, 27), end: dt(2015, 2, 28), grain: Grain::Day});
        assert_eq!(daybeforelastfeb.next().unwrap(),
            Range{start: dt(2016, 2, 28), end: dt(2016, 2, 29), grain: Grain::Day});

        // 29th-to-last day of feb
        let t29th_before_last = LastOf(29, Grains(Grain::Day), Month(2));
        let mut t29th_before_last = t29th_before_last.future(&dt(2015, 2, 25));
        assert_eq!(t29th_before_last.next().unwrap(),
            Range{start: dt(2016, 2, 1), end: dt(2016, 2, 2), grain: Grain::Day});
        assert_eq!(t29th_before_last.next().unwrap(),
            Range{start: dt(2020, 2, 1), end: dt(2020, 2, 2), grain: Grain::Day});

        // backward: 2nd-to-last day of february
        let daybeforelastfeb = LastOf(2, Grains(Grain::Day), Month(2));
        let mut daybeforelastfeb = daybeforelastfeb.past(&dt(2015, 2, 25));
        assert_eq!(daybeforelastfeb.next().unwrap(),
            Range{start: dt(2014, 2, 27), end: dt(2014, 2, 28), grain: Grain::Day});
        assert_eq!(daybeforelastfeb.next().unwrap(),
            Range{start: dt(2013, 2, 27), end: dt(2013, 2, 28), grain: Grain::Day});
        assert_eq!(daybeforelastfeb.next().unwrap(),
            Range{start: dt(2012, 2, 28), end: dt(2012, 2, 29), grain: Grain::Day});

        // backward: 5th-to-last day of february
        let fithbeforelastfeb = LastOf(5, Grains(Grain::Day), Month(2));
        let mut fithbeforelastfeb = fithbeforelastfeb.past(&dt(2015, 2, 26));
        assert_eq!(fithbeforelastfeb.next().unwrap(),
            Range{start: dt(2015, 2, 24), end: dt(2015, 2, 25), grain: Grain::Day});

        // backward: 5th-to-last day of february starting that day - inclusive/raw
        let fithbeforelastfeb = LastOf(5, Grains(Grain::Day), Month(2));
        let mut fithbeforelastfeb = fithbeforelastfeb._past_raw(&dt(2015, 2, 24));
        assert_eq!(fithbeforelastfeb.next().unwrap(),
            Range{start: dt(2015, 2, 24), end: dt(2015, 2, 25), grain: Grain::Day});
    }
}