| //@ run-pass |
| |
| #![feature(fn_traits, |
| step_trait, |
| unboxed_closures, |
| )] |
| |
| //! Derived from: <https://raw.githubusercontent.com/quickfur/dcal/master/dcal.d>. |
| //! |
| //! Originally converted to Rust by [Daniel Keep](https://github.com/DanielKeep). |
| |
| use std::fmt::Write; |
| |
| /// Date representation. |
| #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] |
| struct NaiveDate(i32, u32, u32); |
| |
| impl NaiveDate { |
| pub fn from_ymd(y: i32, m: u32, d: u32) -> NaiveDate { |
| assert!(1 <= m && m <= 12, "m = {:?}", m); |
| assert!(1 <= d && d <= NaiveDate(y, m, 1).days_in_month(), "d = {:?}", d); |
| NaiveDate(y, m, d) |
| } |
| |
| pub fn year(&self) -> i32 { |
| self.0 |
| } |
| |
| pub fn month(&self) -> u32 { |
| self.1 |
| } |
| |
| pub fn day(&self) -> u32 { |
| self.2 |
| } |
| |
| pub fn succ(&self) -> NaiveDate { |
| let (mut y, mut m, mut d, n) = ( |
| self.year(), self.month(), self.day()+1, self.days_in_month()); |
| if d > n { |
| d = 1; |
| m += 1; |
| } |
| if m > 12 { |
| m = 1; |
| y += 1; |
| } |
| NaiveDate::from_ymd(y, m, d) |
| } |
| |
| pub fn weekday(&self) -> Weekday { |
| use Weekday::*; |
| |
| // 0 = Sunday |
| let year = self.year(); |
| let dow_jan_1 = (year*365 + ((year-1) / 4) - ((year-1) / 100) + ((year-1) / 400)) % 7; |
| let dow = (dow_jan_1 + (self.day_of_year() as i32 - 1)) % 7; |
| [Sun, Mon, Tue, Wed, Thu, Fri, Sat][dow as usize] |
| } |
| |
| pub fn isoweekdate(&self) -> (i32, u32, Weekday) { |
| let first_dow_mon_0 = self.year_first_day_of_week().num_days_from_monday(); |
| |
| // Work out this date's DOtY and week number, not including year adjustment. |
| let doy_0 = self.day_of_year() - 1; |
| let mut week_mon_0: i32 = ((first_dow_mon_0 + doy_0) / 7) as i32; |
| |
| if self.first_week_in_prev_year() { |
| week_mon_0 -= 1; |
| } |
| |
| let weeks_in_year = self.last_week_number(); |
| |
| // Work out the final result. |
| // If the week is `-1` or `>= weeks_in_year`, we will need to adjust the year. |
| let year = self.year(); |
| let wd = self.weekday(); |
| |
| if week_mon_0 < 0 { |
| (year - 1, NaiveDate::from_ymd(year - 1, 1, 1).last_week_number(), wd) |
| } else if week_mon_0 >= weeks_in_year as i32 { |
| (year + 1, (week_mon_0 + 1 - weeks_in_year as i32) as u32, wd) |
| } else { |
| (year, (week_mon_0 + 1) as u32, wd) |
| } |
| } |
| |
| fn first_week_in_prev_year(&self) -> bool { |
| let first_dow_mon_0 = self.year_first_day_of_week().num_days_from_monday(); |
| |
| // Any day in the year *before* the first Monday of that year |
| // is considered to be in the last week of the previous year, |
| // assuming the first week has *less* than four days in it. |
| // Adjust the week appropriately. |
| ((7 - first_dow_mon_0) % 7) < 4 |
| } |
| |
| fn year_first_day_of_week(&self) -> Weekday { |
| NaiveDate::from_ymd(self.year(), 1, 1).weekday() |
| } |
| |
| fn weeks_in_year(&self) -> u32 { |
| let days_in_last_week = self.year_first_day_of_week().num_days_from_monday() + 1; |
| if days_in_last_week >= 4 { 53 } else { 52 } |
| } |
| |
| fn last_week_number(&self) -> u32 { |
| let wiy = self.weeks_in_year(); |
| if self.first_week_in_prev_year() { wiy - 1 } else { wiy } |
| } |
| |
| fn day_of_year(&self) -> u32 { |
| (1..self.1).map(|m| NaiveDate::from_ymd(self.year(), m, 1).days_in_month()) |
| .fold(0, |a,b| a+b) + self.day() |
| } |
| |
| fn is_leap_year(&self) -> bool { |
| let year = self.year(); |
| if year % 4 != 0 { |
| return false |
| } else if year % 100 != 0 { |
| return true |
| } else if year % 400 != 0 { |
| return false |
| } else { |
| return true |
| } |
| } |
| |
| fn days_in_month(&self) -> u32 { |
| match self.month() { |
| /* Jan */ 1 => 31, |
| /* Feb */ 2 => if self.is_leap_year() { 29 } else { 28 }, |
| /* Mar */ 3 => 31, |
| /* Apr */ 4 => 30, |
| /* May */ 5 => 31, |
| /* Jun */ 6 => 30, |
| /* Jul */ 7 => 31, |
| /* Aug */ 8 => 31, |
| /* Sep */ 9 => 30, |
| /* Oct */ 10 => 31, |
| /* Nov */ 11 => 30, |
| /* Dec */ 12 => 31, |
| _ => unreachable!() |
| } |
| } |
| } |
| |
| impl<'a, 'b> std::ops::Add<&'b NaiveDate> for &'a NaiveDate { |
| type Output = NaiveDate; |
| |
| fn add(self, other: &'b NaiveDate) -> NaiveDate { |
| assert_eq!(*other, NaiveDate(0, 0, 1)); |
| self.succ() |
| } |
| } |
| |
| impl std::iter::Step for NaiveDate { |
| fn steps_between(_: &Self, _: &Self) -> Option<usize> { |
| unimplemented!() |
| } |
| |
| fn forward_checked(start: Self, n: usize) -> Option<Self> { |
| Some((0..n).fold(start, |x, _| x.succ())) |
| } |
| |
| fn backward_checked(_: Self, _: usize) -> Option<Self> { |
| unimplemented!() |
| } |
| } |
| |
| #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] |
| pub enum Weekday { |
| Mon, |
| Tue, |
| Wed, |
| Thu, |
| Fri, |
| Sat, |
| Sun, |
| } |
| |
| impl Weekday { |
| pub fn num_days_from_monday(&self) -> u32 { |
| use Weekday::*; |
| match *self { |
| Mon => 0, |
| Tue => 1, |
| Wed => 2, |
| Thu => 3, |
| Fri => 4, |
| Sat => 5, |
| Sun => 6, |
| } |
| } |
| |
| pub fn num_days_from_sunday(&self) -> u32 { |
| use Weekday::*; |
| match *self { |
| Sun => 0, |
| Mon => 1, |
| Tue => 2, |
| Wed => 3, |
| Thu => 4, |
| Fri => 5, |
| Sat => 6, |
| } |
| } |
| } |
| |
| /// `GroupBy` implementation. |
| struct GroupBy<It: Iterator, F> { |
| it: std::iter::Peekable<It>, |
| f: F, |
| } |
| |
| impl<It, F> Clone for GroupBy<It, F> |
| where |
| It: Iterator + Clone, |
| It::Item: Clone, |
| F: Clone, |
| { |
| fn clone(&self) -> Self { |
| GroupBy { |
| it: self.it.clone(), |
| f: self.f.clone(), |
| } |
| } |
| } |
| |
| impl<'a, G, It: 'a, F: 'a> Iterator for GroupBy<It, F> |
| where It: Iterator + Clone, |
| It::Item: Clone, |
| F: Clone + FnMut(&It::Item) -> G, |
| G: Eq + Clone |
| { |
| type Item = (G, InGroup<std::iter::Peekable<It>, F, G>); |
| |
| fn next(&mut self) -> Option<Self::Item> { |
| self.it.peek().map(&mut self.f).map(|key| { |
| let start = self.it.clone(); |
| while let Some(k) = self.it.peek().map(&mut self.f) { |
| if key != k { |
| break; |
| } |
| self.it.next(); |
| } |
| |
| (key.clone(), InGroup { |
| it: start, |
| f: self.f.clone(), |
| g: key |
| }) |
| }) |
| } |
| } |
| |
| #[derive(Copy, Clone)] |
| struct InGroup<It, F, G> { |
| it: It, |
| f: F, |
| g: G |
| } |
| |
| impl<It: Iterator, F: FnMut(&It::Item) -> G, G: Eq> Iterator for InGroup<It, F, G> { |
| type Item = It::Item; |
| |
| fn next(&mut self) -> Option<It::Item> { |
| self.it.next().and_then(|x| { |
| if (self.f)(&x) == self.g { Some(x) } else { None } |
| }) |
| } |
| } |
| |
| trait IteratorExt: Iterator + Sized { |
| fn group_by<G, F>(self, f: F) -> GroupBy<Self, F> |
| where F: Clone + FnMut(&Self::Item) -> G, |
| G: Eq |
| { |
| GroupBy { it: self.peekable(), f } |
| } |
| |
| fn join(mut self, sep: &str) -> String |
| where Self::Item: std::fmt::Display { |
| let mut s = String::new(); |
| if let Some(e) = self.next() { |
| write!(s, "{}", e).unwrap(); |
| for e in self { |
| s.push_str(sep); |
| write!(s, "{}", e).unwrap(); |
| } |
| } |
| s |
| } |
| |
| // HACK(eddyb): only needed because `impl Trait` can't be |
| // used with trait methods: `.foo()` becomes `.__(foo)`. |
| fn __<F, R>(self, f: F) -> R |
| where F: FnOnce(Self) -> R { |
| f(self) |
| } |
| } |
| |
| impl<It> IteratorExt for It where It: Iterator {} |
| |
| /// Generates an iterator that yields exactly `n` spaces. |
| fn spaces(n: usize) -> std::iter::Take<std::iter::Repeat<char>> { |
| std::iter::repeat(' ').take(n) |
| } |
| |
| fn test_spaces() { |
| assert_eq!(spaces(0).collect::<String>(), ""); |
| assert_eq!(spaces(10).collect::<String>(), " ") |
| } |
| |
| /// Returns an iterator of dates in a given year. |
| fn dates_in_year(year: i32) -> impl Iterator<Item=NaiveDate>+Clone { |
| InGroup { |
| it: NaiveDate::from_ymd(year, 1, 1).., |
| f: |d: &NaiveDate| d.year(), |
| g: year |
| } |
| } |
| |
| fn test_dates_in_year() { |
| { |
| let mut dates = dates_in_year(2013); |
| assert_eq!(dates.next(), Some(NaiveDate::from_ymd(2013, 1, 1))); |
| |
| // Check increment. |
| assert_eq!(dates.next(), Some(NaiveDate::from_ymd(2013, 1, 2))); |
| |
| // Check monthly roll-over. |
| for _ in 3..31 { |
| assert!(dates.next() != None); |
| } |
| |
| assert_eq!(dates.next(), Some(NaiveDate::from_ymd(2013, 1, 31))); |
| assert_eq!(dates.next(), Some(NaiveDate::from_ymd(2013, 2, 1))); |
| } |
| |
| { |
| // Check length of year. |
| let mut dates = dates_in_year(2013); |
| for _ in 0..365 { |
| assert!(dates.next() != None); |
| } |
| assert_eq!(dates.next(), None); |
| } |
| |
| { |
| // Check length of leap year. |
| let mut dates = dates_in_year(1984); |
| for _ in 0..366 { |
| assert!(dates.next() != None); |
| } |
| assert_eq!(dates.next(), None); |
| } |
| } |
| |
| /// Convenience trait for verifying that a given type iterates over |
| /// `NaiveDate`s. |
| trait DateIterator: Iterator<Item=NaiveDate> + Clone {} |
| impl<It> DateIterator for It where It: Iterator<Item=NaiveDate> + Clone {} |
| |
| fn test_group_by() { |
| let input = [ |
| [1, 1], |
| [1, 1], |
| [1, 2], |
| [2, 2], |
| [2, 3], |
| [2, 3], |
| [3, 3] |
| ]; |
| |
| let by_x = input.iter().cloned().group_by(|a| a[0]); |
| let expected_1: &[&[[i32; 2]]] = &[ |
| &[[1, 1], [1, 1], [1, 2]], |
| &[[2, 2], [2, 3], [2, 3]], |
| &[[3, 3]] |
| ]; |
| for ((_, a), b) in by_x.zip(expected_1.iter().cloned()) { |
| assert_eq!(&a.collect::<Vec<_>>()[..], b); |
| } |
| |
| let by_y = input.iter().cloned().group_by(|a| a[1]); |
| let expected_2: &[&[[i32; 2]]] = &[ |
| &[[1, 1], [1, 1]], |
| &[[1, 2], [2, 2]], |
| &[[2, 3], [2, 3], [3, 3]] |
| ]; |
| for ((_, a), b) in by_y.zip(expected_2.iter().cloned()) { |
| assert_eq!(&a.collect::<Vec<_>>()[..], b); |
| } |
| } |
| |
| /// Groups an iterator of dates by month. |
| fn by_month(it: impl Iterator<Item=NaiveDate> + Clone) |
| -> impl Iterator<Item=(u32, impl Iterator<Item=NaiveDate> + Clone)> + Clone |
| { |
| it.group_by(|d| d.month()) |
| } |
| |
| fn test_by_month() { |
| let mut months = dates_in_year(2013).__(by_month); |
| for (month, (_, mut date)) in (1..13).zip(&mut months) { |
| assert_eq!(date.nth(0).unwrap(), NaiveDate::from_ymd(2013, month, 1)); |
| } |
| assert!(months.next().is_none()); |
| } |
| |
| /// Groups an iterator of dates by week. |
| fn by_week(it: impl DateIterator) |
| -> impl Iterator<Item=(u32, impl DateIterator)> + Clone |
| { |
| // We go forward one day because `isoweekdate` considers the week to start on a Monday. |
| it.group_by(|d| d.succ().isoweekdate().1) |
| } |
| |
| fn test_isoweekdate() { |
| fn weeks_uniq(year: i32) -> Vec<((i32, u32), u32)> { |
| let mut weeks = dates_in_year(year).map(|d| d.isoweekdate()) |
| .map(|(y,w,_)| (y,w)); |
| let mut result = vec![]; |
| let mut accum = (weeks.next().unwrap(), 1); |
| for yw in weeks { |
| if accum.0 == yw { |
| accum.1 += 1; |
| } else { |
| result.push(accum); |
| accum = (yw, 1); |
| } |
| } |
| result.push(accum); |
| result |
| } |
| |
| let wu_1984 = weeks_uniq(1984); |
| assert_eq!(&wu_1984[..2], &[((1983, 52), 1), ((1984, 1), 7)]); |
| assert_eq!(&wu_1984[wu_1984.len()-2..], &[((1984, 52), 7), ((1985, 1), 1)]); |
| |
| let wu_2013 = weeks_uniq(2013); |
| assert_eq!(&wu_2013[..2], &[((2013, 1), 6), ((2013, 2), 7)]); |
| assert_eq!(&wu_2013[wu_2013.len()-2..], &[((2013, 52), 7), ((2014, 1), 2)]); |
| |
| let wu_2015 = weeks_uniq(2015); |
| assert_eq!(&wu_2015[..2], &[((2015, 1), 4), ((2015, 2), 7)]); |
| assert_eq!(&wu_2015[wu_2015.len()-2..], &[((2015, 52), 7), ((2015, 53), 4)]); |
| } |
| |
| fn test_by_week() { |
| let mut weeks = dates_in_year(2013).__(by_week); |
| assert_eq!( |
| &*weeks.next().unwrap().1.collect::<Vec<_>>(), |
| &[ |
| NaiveDate::from_ymd(2013, 1, 1), |
| NaiveDate::from_ymd(2013, 1, 2), |
| NaiveDate::from_ymd(2013, 1, 3), |
| NaiveDate::from_ymd(2013, 1, 4), |
| NaiveDate::from_ymd(2013, 1, 5), |
| ] |
| ); |
| assert_eq!( |
| &*weeks.next().unwrap().1.collect::<Vec<_>>(), |
| &[ |
| NaiveDate::from_ymd(2013, 1, 6), |
| NaiveDate::from_ymd(2013, 1, 7), |
| NaiveDate::from_ymd(2013, 1, 8), |
| NaiveDate::from_ymd(2013, 1, 9), |
| NaiveDate::from_ymd(2013, 1, 10), |
| NaiveDate::from_ymd(2013, 1, 11), |
| NaiveDate::from_ymd(2013, 1, 12), |
| ] |
| ); |
| assert_eq!(weeks.next().unwrap().1.nth(0).unwrap(), NaiveDate::from_ymd(2013, 1, 13)); |
| } |
| |
| /// The number of columns per day in the formatted output. |
| const COLS_PER_DAY: u32 = 3; |
| |
| /// The number of columns per week in the formatted output. |
| const COLS_PER_WEEK: u32 = 7 * COLS_PER_DAY; |
| |
| /// Formats an iterator of weeks into an iterator of strings. |
| fn format_weeks(it: impl Iterator<Item = impl DateIterator>) -> impl Iterator<Item=String> { |
| it.map(|week| { |
| let mut buf = String::with_capacity((COLS_PER_DAY * COLS_PER_WEEK + 2) as usize); |
| |
| // Format each day into its own cell and append to target string. |
| let mut last_day = 0; |
| let mut first = true; |
| for d in week { |
| last_day = d.weekday().num_days_from_sunday(); |
| |
| // Insert enough filler to align the first day with its respective day-of-week. |
| if first { |
| buf.extend(spaces((COLS_PER_DAY * last_day) as usize)); |
| first = false; |
| } |
| |
| write!(buf, " {:>2}", d.day()).unwrap(); |
| } |
| |
| // Insert more filler at the end to fill up the remainder of the week, |
| // if its a short week (e.g., at the end of the month). |
| buf.extend(spaces((COLS_PER_DAY * (6 - last_day)) as usize)); |
| buf |
| }) |
| } |
| |
| fn test_format_weeks() { |
| let jan_2013 = dates_in_year(2013) |
| .__(by_month).next() // pick January 2013 for testing purposes |
| // NOTE: This `map` is because `next` returns an `Option<_>`. |
| .map(|(_, month)| |
| month.__(by_week) |
| .map(|(_, weeks)| weeks) |
| .__(format_weeks) |
| .join("\n")); |
| |
| assert_eq!( |
| jan_2013.as_ref().map(|s| &**s), |
| Some(" 1 2 3 4 5\n\ |
| \x20 6 7 8 9 10 11 12\n\ |
| \x2013 14 15 16 17 18 19\n\ |
| \x2020 21 22 23 24 25 26\n\ |
| \x2027 28 29 30 31 ") |
| ); |
| } |
| |
| /// Formats the name of a month, centered on `COLS_PER_WEEK`. |
| fn month_title(month: u32) -> String { |
| const MONTH_NAMES: &'static [&'static str] = &[ |
| "January", "February", "March", "April", "May", "June", |
| "July", "August", "September", "October", "November", "December" |
| ]; |
| assert_eq!(MONTH_NAMES.len(), 12); |
| |
| // Determine how many spaces before and after the month name |
| // we need to center it over the formatted weeks in the month. |
| let name = MONTH_NAMES[(month - 1) as usize]; |
| assert!(name.len() < COLS_PER_WEEK as usize); |
| let before = (COLS_PER_WEEK as usize - name.len()) / 2; |
| let after = COLS_PER_WEEK as usize - name.len() - before; |
| |
| // Note: being slightly more verbose to avoid extra allocations. |
| let mut result = String::with_capacity(COLS_PER_WEEK as usize); |
| result.extend(spaces(before)); |
| result.push_str(name); |
| result.extend(spaces(after)); |
| result |
| } |
| |
| fn test_month_title() { |
| assert_eq!(month_title(1).len(), COLS_PER_WEEK as usize); |
| } |
| |
| /// Formats a month. |
| fn format_month(it: impl DateIterator) -> impl Iterator<Item=String> { |
| let mut month_days = it.peekable(); |
| let title = month_title(month_days.peek().unwrap().month()); |
| |
| Some(title).into_iter() |
| .chain(month_days.__(by_week) |
| .map(|(_, week)| week) |
| .__(format_weeks)) |
| } |
| |
| fn test_format_month() { |
| let month_fmt = dates_in_year(2013) |
| .__(by_month).next() // Pick January as a test case |
| .map(|(_, days)| days.into_iter() |
| .__(format_month) |
| .join("\n")); |
| |
| assert_eq!( |
| month_fmt.as_ref().map(|s| &**s), |
| Some(" January \n\ |
| \x20 1 2 3 4 5\n\ |
| \x20 6 7 8 9 10 11 12\n\ |
| \x2013 14 15 16 17 18 19\n\ |
| \x2020 21 22 23 24 25 26\n\ |
| \x2027 28 29 30 31 ") |
| ); |
| } |
| |
| /// Formats an iterator of months. |
| fn format_months(it: impl Iterator<Item = impl DateIterator>) |
| -> impl Iterator<Item=impl Iterator<Item=String>> |
| { |
| it.map(format_month) |
| } |
| |
| /// Takes an iterator of iterators of strings; the sub-iterators are consumed |
| /// in lock-step, with their elements joined together. |
| trait PasteBlocks: Iterator + Sized |
| where Self::Item: Iterator<Item = String> { |
| fn paste_blocks(self, sep_width: usize) -> PasteBlocksIter<Self::Item> { |
| PasteBlocksIter { |
| iters: self.collect(), |
| cache: vec![], |
| col_widths: None, |
| sep_width: sep_width, |
| } |
| } |
| } |
| |
| impl<It> PasteBlocks for It where It: Iterator, It::Item: Iterator<Item=String> {} |
| |
| struct PasteBlocksIter<StrIt> |
| where StrIt: Iterator<Item=String> { |
| iters: Vec<StrIt>, |
| cache: Vec<Option<String>>, |
| col_widths: Option<Vec<usize>>, |
| sep_width: usize, |
| } |
| |
| impl<StrIt> Iterator for PasteBlocksIter<StrIt> |
| where StrIt: Iterator<Item=String> { |
| type Item = String; |
| |
| fn next(&mut self) -> Option<String> { |
| self.cache.clear(); |
| |
| // `cache` is now the next line from each iterator. |
| self.cache.extend(self.iters.iter_mut().map(|it| it.next())); |
| |
| // If every line in `cache` is `None`, we have nothing further to do. |
| if self.cache.iter().all(|e| e.is_none()) { return None } |
| |
| // Get the column widths if we haven't already. |
| let col_widths = match self.col_widths { |
| Some(ref v) => &**v, |
| None => { |
| self.col_widths = Some(self.cache.iter() |
| .map(|ms| ms.as_ref().map(|s| s.len()).unwrap_or(0)) |
| .collect()); |
| &**self.col_widths.as_ref().unwrap() |
| } |
| }; |
| |
| // Fill in any `None`s with spaces. |
| let mut parts = col_widths.iter().cloned().zip(self.cache.iter_mut()) |
| .map(|(w,ms)| ms.take().unwrap_or_else(|| spaces(w).collect())); |
| |
| // Join them all together. |
| let first = parts.next().unwrap_or(String::new()); |
| let sep_width = self.sep_width; |
| Some(parts.fold(first, |mut accum, next| { |
| accum.extend(spaces(sep_width)); |
| accum.push_str(&next); |
| accum |
| })) |
| } |
| } |
| |
| fn test_paste_blocks() { |
| let row = dates_in_year(2013) |
| .__(by_month).map(|(_, days)| days) |
| .take(3) |
| .__(format_months) |
| .paste_blocks(1) |
| .join("\n"); |
| assert_eq!( |
| &*row, |
| " January February March \n\ |
| \x20 1 2 3 4 5 1 2 1 2\n\ |
| \x20 6 7 8 9 10 11 12 3 4 5 6 7 8 9 3 4 5 6 7 8 9\n\ |
| \x2013 14 15 16 17 18 19 10 11 12 13 14 15 16 10 11 12 13 14 15 16\n\ |
| \x2020 21 22 23 24 25 26 17 18 19 20 21 22 23 17 18 19 20 21 22 23\n\ |
| \x2027 28 29 30 31 24 25 26 27 28 24 25 26 27 28 29 30\n\ |
| \x20 31 " |
| ); |
| } |
| |
| /// Produces an iterator that yields `n` elements at a time. |
| trait Chunks: Iterator + Sized { |
| fn chunks(self, n: usize) -> ChunksIter<Self> { |
| assert!(n > 0); |
| ChunksIter { |
| it: self, |
| n: n, |
| } |
| } |
| } |
| |
| impl<It> Chunks for It where It: Iterator {} |
| |
| struct ChunksIter<It> |
| where It: Iterator { |
| it: It, |
| n: usize, |
| } |
| |
| // Note: `chunks` in Rust is more-or-less impossible without overhead of some kind. |
| // Aliasing rules mean you need to add dynamic borrow checking, and the design of |
| // `Iterator` means that you need to have the iterator's state kept in an allocation |
| // that is jointly owned by the iterator itself and the sub-iterator. |
| // As such, I've chosen to cop-out and just heap-allocate each chunk. |
| |
| impl<It> Iterator for ChunksIter<It> |
| where It: Iterator { |
| type Item = Vec<It::Item>; |
| |
| fn next(&mut self) -> Option<Vec<It::Item>> { |
| let first = self.it.next()?; |
| |
| let mut result = Vec::with_capacity(self.n); |
| result.push(first); |
| |
| Some((&mut self.it).take(self.n-1) |
| .fold(result, |mut acc, next| { acc.push(next); acc })) |
| } |
| } |
| |
| fn test_chunks() { |
| let r = &[1, 2, 3, 4, 5, 6, 7]; |
| let c = r.iter().cloned().chunks(3).collect::<Vec<_>>(); |
| assert_eq!(&*c, &[vec![1, 2, 3], vec![4, 5, 6], vec![7]]); |
| } |
| |
| /// Formats a year. |
| fn format_year(year: i32, months_per_row: usize) -> String { |
| const COL_SPACING: usize = 1; |
| |
| // Start by generating all dates for the given year. |
| dates_in_year(year) |
| |
| // Group them by month and throw away month number. |
| .__(by_month).map(|(_, days)| days) |
| |
| // Group the months into horizontal rows. |
| .chunks(months_per_row) |
| |
| // Format each row... |
| .map(|r| r.into_iter() |
| // ... by formatting each month ... |
| .__(format_months) |
| |
| // ... and horizontally pasting each respective month's lines together. |
| .paste_blocks(COL_SPACING) |
| .join("\n") |
| ) |
| |
| // Insert a blank line between each row. |
| .join("\n\n") |
| } |
| |
| fn test_format_year() { |
| const MONTHS_PER_ROW: usize = 3; |
| |
| macro_rules! assert_eq_cal { |
| ($lhs:expr, $rhs:expr) => { |
| if $lhs != $rhs { |
| println!("got:\n```\n{}\n```\n", $lhs.replace(" ", ".")); |
| println!("expected:\n```\n{}\n```", $rhs.replace(" ", ".")); |
| panic!("calendars didn't match!"); |
| } |
| } |
| } |
| |
| assert_eq_cal!(&format_year(1984, MONTHS_PER_ROW), "\ |
| \x20 January February March \n\ |
| \x20 1 2 3 4 5 6 7 1 2 3 4 1 2 3\n\ |
| \x20 8 9 10 11 12 13 14 5 6 7 8 9 10 11 4 5 6 7 8 9 10\n\ |
| \x2015 16 17 18 19 20 21 12 13 14 15 16 17 18 11 12 13 14 15 16 17\n\ |
| \x2022 23 24 25 26 27 28 19 20 21 22 23 24 25 18 19 20 21 22 23 24\n\ |
| \x2029 30 31 26 27 28 29 25 26 27 28 29 30 31\n\ |
| \n\ |
| \x20 April May June \n\ |
| \x20 1 2 3 4 5 6 7 1 2 3 4 5 1 2\n\ |
| \x20 8 9 10 11 12 13 14 6 7 8 9 10 11 12 3 4 5 6 7 8 9\n\ |
| \x2015 16 17 18 19 20 21 13 14 15 16 17 18 19 10 11 12 13 14 15 16\n\ |
| \x2022 23 24 25 26 27 28 20 21 22 23 24 25 26 17 18 19 20 21 22 23\n\ |
| \x2029 30 27 28 29 30 31 24 25 26 27 28 29 30\n\ |
| \n\ |
| \x20 July August September \n\ |
| \x20 1 2 3 4 5 6 7 1 2 3 4 1\n\ |
| \x20 8 9 10 11 12 13 14 5 6 7 8 9 10 11 2 3 4 5 6 7 8\n\ |
| \x2015 16 17 18 19 20 21 12 13 14 15 16 17 18 9 10 11 12 13 14 15\n\ |
| \x2022 23 24 25 26 27 28 19 20 21 22 23 24 25 16 17 18 19 20 21 22\n\ |
| \x2029 30 31 26 27 28 29 30 31 23 24 25 26 27 28 29\n\ |
| \x20 30 \n\ |
| \n\ |
| \x20 October November December \n\ |
| \x20 1 2 3 4 5 6 1 2 3 1\n\ |
| \x20 7 8 9 10 11 12 13 4 5 6 7 8 9 10 2 3 4 5 6 7 8\n\ |
| \x2014 15 16 17 18 19 20 11 12 13 14 15 16 17 9 10 11 12 13 14 15\n\ |
| \x2021 22 23 24 25 26 27 18 19 20 21 22 23 24 16 17 18 19 20 21 22\n\ |
| \x2028 29 30 31 25 26 27 28 29 30 23 24 25 26 27 28 29\n\ |
| \x20 30 31 "); |
| |
| assert_eq_cal!(&format_year(2015, MONTHS_PER_ROW), "\ |
| \x20 January February March \n\ |
| \x20 1 2 3 1 2 3 4 5 6 7 1 2 3 4 5 6 7\n\ |
| \x20 4 5 6 7 8 9 10 8 9 10 11 12 13 14 8 9 10 11 12 13 14\n\ |
| \x2011 12 13 14 15 16 17 15 16 17 18 19 20 21 15 16 17 18 19 20 21\n\ |
| \x2018 19 20 21 22 23 24 22 23 24 25 26 27 28 22 23 24 25 26 27 28\n\ |
| \x2025 26 27 28 29 30 31 29 30 31 \n\ |
| \n\ |
| \x20 April May June \n\ |
| \x20 1 2 3 4 1 2 1 2 3 4 5 6\n\ |
| \x20 5 6 7 8 9 10 11 3 4 5 6 7 8 9 7 8 9 10 11 12 13\n\ |
| \x2012 13 14 15 16 17 18 10 11 12 13 14 15 16 14 15 16 17 18 19 20\n\ |
| \x2019 20 21 22 23 24 25 17 18 19 20 21 22 23 21 22 23 24 25 26 27\n\ |
| \x2026 27 28 29 30 24 25 26 27 28 29 30 28 29 30 \n\ |
| \x20 31 \n\ |
| \n\ |
| \x20 July August September \n\ |
| \x20 1 2 3 4 1 1 2 3 4 5\n\ |
| \x20 5 6 7 8 9 10 11 2 3 4 5 6 7 8 6 7 8 9 10 11 12\n\ |
| \x2012 13 14 15 16 17 18 9 10 11 12 13 14 15 13 14 15 16 17 18 19\n\ |
| \x2019 20 21 22 23 24 25 16 17 18 19 20 21 22 20 21 22 23 24 25 26\n\ |
| \x2026 27 28 29 30 31 23 24 25 26 27 28 29 27 28 29 30 \n\ |
| \x20 30 31 \n\ |
| \n\ |
| \x20 October November December \n\ |
| \x20 1 2 3 1 2 3 4 5 6 7 1 2 3 4 5\n\ |
| \x20 4 5 6 7 8 9 10 8 9 10 11 12 13 14 6 7 8 9 10 11 12\n\ |
| \x2011 12 13 14 15 16 17 15 16 17 18 19 20 21 13 14 15 16 17 18 19\n\ |
| \x2018 19 20 21 22 23 24 22 23 24 25 26 27 28 20 21 22 23 24 25 26\n\ |
| \x2025 26 27 28 29 30 31 29 30 27 28 29 30 31 "); |
| } |
| |
| fn main() { |
| // Run tests. |
| test_spaces(); |
| test_dates_in_year(); |
| test_group_by(); |
| test_by_month(); |
| test_isoweekdate(); |
| test_by_week(); |
| test_format_weeks(); |
| test_month_title(); |
| test_format_month(); |
| test_paste_blocks(); |
| test_chunks(); |
| test_format_year(); |
| } |