2 // ignore-compare-mode-chalk
9 //! Derived from: <https://raw.githubusercontent.com/quickfur/dcal/master/dcal.d>.
11 //! Originally converted to Rust by [Daniel Keep](https://github.com/DanielKeep).
15 /// Date representation.
16 #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
17 struct NaiveDate(i32, u32, u32);
20 pub fn from_ymd(y: i32, m: u32, d: u32) -> NaiveDate {
21 assert!(1 <= m && m <= 12, "m = {:?}", m);
22 assert!(1 <= d && d <= NaiveDate(y, m, 1).days_in_month(), "d = {:?}", d);
26 pub fn year(&self) -> i32 {
30 pub fn month(&self) -> u32 {
34 pub fn day(&self) -> u32 {
38 pub fn succ(&self) -> NaiveDate {
39 let (mut y, mut m, mut d, n) = (
40 self.year(), self.month(), self.day()+1, self.days_in_month());
49 NaiveDate::from_ymd(y, m, d)
52 pub fn weekday(&self) -> Weekday {
56 let year = self.year();
57 let dow_jan_1 = (year*365 + ((year-1) / 4) - ((year-1) / 100) + ((year-1) / 400)) % 7;
58 let dow = (dow_jan_1 + (self.day_of_year() as i32 - 1)) % 7;
59 [Sun, Mon, Tue, Wed, Thu, Fri, Sat][dow as usize]
62 pub fn isoweekdate(&self) -> (i32, u32, Weekday) {
63 let first_dow_mon_0 = self.year_first_day_of_week().num_days_from_monday();
65 // Work out this date's DOtY and week number, not including year adjustment.
66 let doy_0 = self.day_of_year() - 1;
67 let mut week_mon_0: i32 = ((first_dow_mon_0 + doy_0) / 7) as i32;
69 if self.first_week_in_prev_year() {
73 let weeks_in_year = self.last_week_number();
75 // Work out the final result.
76 // If the week is `-1` or `>= weeks_in_year`, we will need to adjust the year.
77 let year = self.year();
78 let wd = self.weekday();
81 (year - 1, NaiveDate::from_ymd(year - 1, 1, 1).last_week_number(), wd)
82 } else if week_mon_0 >= weeks_in_year as i32 {
83 (year + 1, (week_mon_0 + 1 - weeks_in_year as i32) as u32, wd)
85 (year, (week_mon_0 + 1) as u32, wd)
89 fn first_week_in_prev_year(&self) -> bool {
90 let first_dow_mon_0 = self.year_first_day_of_week().num_days_from_monday();
92 // Any day in the year *before* the first Monday of that year
93 // is considered to be in the last week of the previous year,
94 // assuming the first week has *less* than four days in it.
95 // Adjust the week appropriately.
96 ((7 - first_dow_mon_0) % 7) < 4
99 fn year_first_day_of_week(&self) -> Weekday {
100 NaiveDate::from_ymd(self.year(), 1, 1).weekday()
103 fn weeks_in_year(&self) -> u32 {
104 let days_in_last_week = self.year_first_day_of_week().num_days_from_monday() + 1;
105 if days_in_last_week >= 4 { 53 } else { 52 }
108 fn last_week_number(&self) -> u32 {
109 let wiy = self.weeks_in_year();
110 if self.first_week_in_prev_year() { wiy - 1 } else { wiy }
113 fn day_of_year(&self) -> u32 {
114 (1..self.1).map(|m| NaiveDate::from_ymd(self.year(), m, 1).days_in_month())
115 .fold(0, |a,b| a+b) + self.day()
118 fn is_leap_year(&self) -> bool {
119 let year = self.year();
122 } else if year % 100 != 0 {
124 } else if year % 400 != 0 {
131 fn days_in_month(&self) -> u32 {
134 /* Feb */ 2 => if self.is_leap_year() { 29 } else { 28 },
150 impl<'a, 'b> std::ops::Add<&'b NaiveDate> for &'a NaiveDate {
151 type Output = NaiveDate;
153 fn add(self, other: &'b NaiveDate) -> NaiveDate {
154 assert_eq!(*other, NaiveDate(0, 0, 1));
159 impl std::iter::Step for NaiveDate {
160 fn steps_between(_: &Self, _: &Self) -> Option<usize> {
164 fn forward_checked(start: Self, n: usize) -> Option<Self> {
165 Some((0..n).fold(start, |x, _| x.succ()))
168 fn backward_checked(_: Self, _: usize) -> Option<Self> {
173 #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
185 pub fn num_days_from_monday(&self) -> u32 {
198 pub fn num_days_from_sunday(&self) -> u32 {
212 /// `GroupBy` implementation.
213 struct GroupBy<It: Iterator, F> {
214 it: std::iter::Peekable<It>,
218 impl<It, F> Clone for GroupBy<It, F>
220 It: Iterator + Clone,
224 fn clone(&self) -> Self {
232 impl<'a, G, It: 'a, F: 'a> Iterator for GroupBy<It, F>
233 where It: Iterator + Clone,
235 F: Clone + FnMut(&It::Item) -> G,
238 type Item = (G, InGroup<std::iter::Peekable<It>, F, G>);
240 fn next(&mut self) -> Option<Self::Item> {
241 self.it.peek().map(&mut self.f).map(|key| {
242 let start = self.it.clone();
243 while let Some(k) = self.it.peek().map(&mut self.f) {
250 (key.clone(), InGroup {
259 #[derive(Copy, Clone)]
260 struct InGroup<It, F, G> {
266 impl<It: Iterator, F: FnMut(&It::Item) -> G, G: Eq> Iterator for InGroup<It, F, G> {
267 type Item = It::Item;
269 fn next(&mut self) -> Option<It::Item> {
270 self.it.next().and_then(|x| {
271 if (self.f)(&x) == self.g { Some(x) } else { None }
276 trait IteratorExt: Iterator + Sized {
277 fn group_by<G, F>(self, f: F) -> GroupBy<Self, F>
278 where F: Clone + FnMut(&Self::Item) -> G,
281 GroupBy { it: self.peekable(), f }
284 fn join(mut self, sep: &str) -> String
285 where Self::Item: std::fmt::Display {
286 let mut s = String::new();
287 if let Some(e) = self.next() {
288 write!(s, "{}", e).unwrap();
291 write!(s, "{}", e).unwrap();
297 // HACK(eddyb): only needed because `impl Trait` can't be
298 // used with trait methods: `.foo()` becomes `.__(foo)`.
299 fn __<F, R>(self, f: F) -> R
300 where F: FnOnce(Self) -> R {
305 impl<It> IteratorExt for It where It: Iterator {}
307 /// Generates an iterator that yields exactly `n` spaces.
308 fn spaces(n: usize) -> std::iter::Take<std::iter::Repeat<char>> {
309 std::iter::repeat(' ').take(n)
313 assert_eq!(spaces(0).collect::<String>(), "");
314 assert_eq!(spaces(10).collect::<String>(), " ")
317 /// Returns an iterator of dates in a given year.
318 fn dates_in_year(year: i32) -> impl Iterator<Item=NaiveDate>+Clone {
320 it: NaiveDate::from_ymd(year, 1, 1)..,
321 f: |d: &NaiveDate| d.year(),
326 fn test_dates_in_year() {
328 let mut dates = dates_in_year(2013);
329 assert_eq!(dates.next(), Some(NaiveDate::from_ymd(2013, 1, 1)));
332 assert_eq!(dates.next(), Some(NaiveDate::from_ymd(2013, 1, 2)));
334 // Check monthly roll-over.
336 assert!(dates.next() != None);
339 assert_eq!(dates.next(), Some(NaiveDate::from_ymd(2013, 1, 31)));
340 assert_eq!(dates.next(), Some(NaiveDate::from_ymd(2013, 2, 1)));
344 // Check length of year.
345 let mut dates = dates_in_year(2013);
347 assert!(dates.next() != None);
349 assert_eq!(dates.next(), None);
353 // Check length of leap year.
354 let mut dates = dates_in_year(1984);
356 assert!(dates.next() != None);
358 assert_eq!(dates.next(), None);
362 /// Convenience trait for verifying that a given type iterates over
364 trait DateIterator: Iterator<Item=NaiveDate> + Clone {}
365 impl<It> DateIterator for It where It: Iterator<Item=NaiveDate> + Clone {}
378 let by_x = input.iter().cloned().group_by(|a| a[0]);
379 let expected_1: &[&[[i32; 2]]] = &[
380 &[[1, 1], [1, 1], [1, 2]],
381 &[[2, 2], [2, 3], [2, 3]],
384 for ((_, a), b) in by_x.zip(expected_1.iter().cloned()) {
385 assert_eq!(&a.collect::<Vec<_>>()[..], b);
388 let by_y = input.iter().cloned().group_by(|a| a[1]);
389 let expected_2: &[&[[i32; 2]]] = &[
392 &[[2, 3], [2, 3], [3, 3]]
394 for ((_, a), b) in by_y.zip(expected_2.iter().cloned()) {
395 assert_eq!(&a.collect::<Vec<_>>()[..], b);
399 /// Groups an iterator of dates by month.
400 fn by_month(it: impl Iterator<Item=NaiveDate> + Clone)
401 -> impl Iterator<Item=(u32, impl Iterator<Item=NaiveDate> + Clone)> + Clone
403 it.group_by(|d| d.month())
407 let mut months = dates_in_year(2013).__(by_month);
408 for (month, (_, mut date)) in (1..13).zip(&mut months) {
409 assert_eq!(date.nth(0).unwrap(), NaiveDate::from_ymd(2013, month, 1));
411 assert!(months.next().is_none());
414 /// Groups an iterator of dates by week.
415 fn by_week(it: impl DateIterator)
416 -> impl Iterator<Item=(u32, impl DateIterator)> + Clone
418 // We go forward one day because `isoweekdate` considers the week to start on a Monday.
419 it.group_by(|d| d.succ().isoweekdate().1)
422 fn test_isoweekdate() {
423 fn weeks_uniq(year: i32) -> Vec<((i32, u32), u32)> {
424 let mut weeks = dates_in_year(year).map(|d| d.isoweekdate())
425 .map(|(y,w,_)| (y,w));
426 let mut result = vec![];
427 let mut accum = (weeks.next().unwrap(), 1);
440 let wu_1984 = weeks_uniq(1984);
441 assert_eq!(&wu_1984[..2], &[((1983, 52), 1), ((1984, 1), 7)]);
442 assert_eq!(&wu_1984[wu_1984.len()-2..], &[((1984, 52), 7), ((1985, 1), 1)]);
444 let wu_2013 = weeks_uniq(2013);
445 assert_eq!(&wu_2013[..2], &[((2013, 1), 6), ((2013, 2), 7)]);
446 assert_eq!(&wu_2013[wu_2013.len()-2..], &[((2013, 52), 7), ((2014, 1), 2)]);
448 let wu_2015 = weeks_uniq(2015);
449 assert_eq!(&wu_2015[..2], &[((2015, 1), 4), ((2015, 2), 7)]);
450 assert_eq!(&wu_2015[wu_2015.len()-2..], &[((2015, 52), 7), ((2015, 53), 4)]);
454 let mut weeks = dates_in_year(2013).__(by_week);
456 &*weeks.next().unwrap().1.collect::<Vec<_>>(),
458 NaiveDate::from_ymd(2013, 1, 1),
459 NaiveDate::from_ymd(2013, 1, 2),
460 NaiveDate::from_ymd(2013, 1, 3),
461 NaiveDate::from_ymd(2013, 1, 4),
462 NaiveDate::from_ymd(2013, 1, 5),
466 &*weeks.next().unwrap().1.collect::<Vec<_>>(),
468 NaiveDate::from_ymd(2013, 1, 6),
469 NaiveDate::from_ymd(2013, 1, 7),
470 NaiveDate::from_ymd(2013, 1, 8),
471 NaiveDate::from_ymd(2013, 1, 9),
472 NaiveDate::from_ymd(2013, 1, 10),
473 NaiveDate::from_ymd(2013, 1, 11),
474 NaiveDate::from_ymd(2013, 1, 12),
477 assert_eq!(weeks.next().unwrap().1.nth(0).unwrap(), NaiveDate::from_ymd(2013, 1, 13));
480 /// The number of columns per day in the formatted output.
481 const COLS_PER_DAY: u32 = 3;
483 /// The number of columns per week in the formatted output.
484 const COLS_PER_WEEK: u32 = 7 * COLS_PER_DAY;
486 /// Formats an iterator of weeks into an iterator of strings.
487 fn format_weeks(it: impl Iterator<Item = impl DateIterator>) -> impl Iterator<Item=String> {
489 let mut buf = String::with_capacity((COLS_PER_DAY * COLS_PER_WEEK + 2) as usize);
491 // Format each day into its own cell and append to target string.
492 let mut last_day = 0;
493 let mut first = true;
495 last_day = d.weekday().num_days_from_sunday();
497 // Insert enough filler to align the first day with its respective day-of-week.
499 buf.extend(spaces((COLS_PER_DAY * last_day) as usize));
503 write!(buf, " {:>2}", d.day()).unwrap();
506 // Insert more filler at the end to fill up the remainder of the week,
507 // if its a short week (e.g., at the end of the month).
508 buf.extend(spaces((COLS_PER_DAY * (6 - last_day)) as usize));
513 fn test_format_weeks() {
514 let jan_2013 = dates_in_year(2013)
515 .__(by_month).next() // pick January 2013 for testing purposes
516 // NOTE: This `map` is because `next` returns an `Option<_>`.
519 .map(|(_, weeks)| weeks)
524 jan_2013.as_ref().map(|s| &**s),
526 \x20 6 7 8 9 10 11 12\n\
527 \x2013 14 15 16 17 18 19\n\
528 \x2020 21 22 23 24 25 26\n\
529 \x2027 28 29 30 31 ")
533 /// Formats the name of a month, centered on `COLS_PER_WEEK`.
534 fn month_title(month: u32) -> String {
535 const MONTH_NAMES: &'static [&'static str] = &[
536 "January", "February", "March", "April", "May", "June",
537 "July", "August", "September", "October", "November", "December"
539 assert_eq!(MONTH_NAMES.len(), 12);
541 // Determine how many spaces before and after the month name
542 // we need to center it over the formatted weeks in the month.
543 let name = MONTH_NAMES[(month - 1) as usize];
544 assert!(name.len() < COLS_PER_WEEK as usize);
545 let before = (COLS_PER_WEEK as usize - name.len()) / 2;
546 let after = COLS_PER_WEEK as usize - name.len() - before;
548 // Note: being slightly more verbose to avoid extra allocations.
549 let mut result = String::with_capacity(COLS_PER_WEEK as usize);
550 result.extend(spaces(before));
551 result.push_str(name);
552 result.extend(spaces(after));
556 fn test_month_title() {
557 assert_eq!(month_title(1).len(), COLS_PER_WEEK as usize);
561 fn format_month(it: impl DateIterator) -> impl Iterator<Item=String> {
562 let mut month_days = it.peekable();
563 let title = month_title(month_days.peek().unwrap().month());
565 Some(title).into_iter()
566 .chain(month_days.__(by_week)
567 .map(|(_, week)| week)
571 fn test_format_month() {
572 let month_fmt = dates_in_year(2013)
573 .__(by_month).next() // Pick January as a test case
574 .map(|(_, days)| days.into_iter()
579 month_fmt.as_ref().map(|s| &**s),
582 \x20 6 7 8 9 10 11 12\n\
583 \x2013 14 15 16 17 18 19\n\
584 \x2020 21 22 23 24 25 26\n\
585 \x2027 28 29 30 31 ")
589 /// Formats an iterator of months.
590 fn format_months(it: impl Iterator<Item = impl DateIterator>)
591 -> impl Iterator<Item=impl Iterator<Item=String>>
596 /// Takes an iterator of iterators of strings; the sub-iterators are consumed
597 /// in lock-step, with their elements joined together.
598 trait PasteBlocks: Iterator + Sized
599 where Self::Item: Iterator<Item = String> {
600 fn paste_blocks(self, sep_width: usize) -> PasteBlocksIter<Self::Item> {
602 iters: self.collect(),
605 sep_width: sep_width,
610 impl<It> PasteBlocks for It where It: Iterator, It::Item: Iterator<Item=String> {}
612 struct PasteBlocksIter<StrIt>
613 where StrIt: Iterator<Item=String> {
615 cache: Vec<Option<String>>,
616 col_widths: Option<Vec<usize>>,
620 impl<StrIt> Iterator for PasteBlocksIter<StrIt>
621 where StrIt: Iterator<Item=String> {
624 fn next(&mut self) -> Option<String> {
627 // `cache` is now the next line from each iterator.
628 self.cache.extend(self.iters.iter_mut().map(|it| it.next()));
630 // If every line in `cache` is `None`, we have nothing further to do.
631 if self.cache.iter().all(|e| e.is_none()) { return None }
633 // Get the column widths if we haven't already.
634 let col_widths = match self.col_widths {
637 self.col_widths = Some(self.cache.iter()
638 .map(|ms| ms.as_ref().map(|s| s.len()).unwrap_or(0))
640 &**self.col_widths.as_ref().unwrap()
644 // Fill in any `None`s with spaces.
645 let mut parts = col_widths.iter().cloned().zip(self.cache.iter_mut())
646 .map(|(w,ms)| ms.take().unwrap_or_else(|| spaces(w).collect()));
648 // Join them all together.
649 let first = parts.next().unwrap_or(String::new());
650 let sep_width = self.sep_width;
651 Some(parts.fold(first, |mut accum, next| {
652 accum.extend(spaces(sep_width));
653 accum.push_str(&next);
659 fn test_paste_blocks() {
660 let row = dates_in_year(2013)
661 .__(by_month).map(|(_, days)| days)
668 " January February March \n\
669 \x20 1 2 3 4 5 1 2 1 2\n\
670 \x20 6 7 8 9 10 11 12 3 4 5 6 7 8 9 3 4 5 6 7 8 9\n\
671 \x2013 14 15 16 17 18 19 10 11 12 13 14 15 16 10 11 12 13 14 15 16\n\
672 \x2020 21 22 23 24 25 26 17 18 19 20 21 22 23 17 18 19 20 21 22 23\n\
673 \x2027 28 29 30 31 24 25 26 27 28 24 25 26 27 28 29 30\n\
678 /// Produces an iterator that yields `n` elements at a time.
679 trait Chunks: Iterator + Sized {
680 fn chunks(self, n: usize) -> ChunksIter<Self> {
689 impl<It> Chunks for It where It: Iterator {}
691 struct ChunksIter<It>
697 // Note: `chunks` in Rust is more-or-less impossible without overhead of some kind.
698 // Aliasing rules mean you need to add dynamic borrow checking, and the design of
699 // `Iterator` means that you need to have the iterator's state kept in an allocation
700 // that is jointly owned by the iterator itself and the sub-iterator.
701 // As such, I've chosen to cop-out and just heap-allocate each chunk.
703 impl<It> Iterator for ChunksIter<It>
705 type Item = Vec<It::Item>;
707 fn next(&mut self) -> Option<Vec<It::Item>> {
708 let first = self.it.next()?;
710 let mut result = Vec::with_capacity(self.n);
713 Some((&mut self.it).take(self.n-1)
714 .fold(result, |mut acc, next| { acc.push(next); acc }))
719 let r = &[1, 2, 3, 4, 5, 6, 7];
720 let c = r.iter().cloned().chunks(3).collect::<Vec<_>>();
721 assert_eq!(&*c, &[vec![1, 2, 3], vec![4, 5, 6], vec![7]]);
725 fn format_year(year: i32, months_per_row: usize) -> String {
726 const COL_SPACING: usize = 1;
728 // Start by generating all dates for the given year.
731 // Group them by month and throw away month number.
732 .__(by_month).map(|(_, days)| days)
734 // Group the months into horizontal rows.
735 .chunks(months_per_row)
737 // Format each row...
738 .map(|r| r.into_iter()
739 // ... by formatting each month ...
742 // ... and horizontally pasting each respective month's lines together.
743 .paste_blocks(COL_SPACING)
747 // Insert a blank line between each row.
751 fn test_format_year() {
752 const MONTHS_PER_ROW: usize = 3;
754 macro_rules! assert_eq_cal {
755 ($lhs:expr, $rhs:expr) => {
757 println!("got:\n```\n{}\n```\n", $lhs.replace(" ", "."));
758 println!("expected:\n```\n{}\n```", $rhs.replace(" ", "."));
759 panic!("calendars didn't match!");
764 assert_eq_cal!(&format_year(1984, MONTHS_PER_ROW), "\
765 \x20 January February March \n\
766 \x20 1 2 3 4 5 6 7 1 2 3 4 1 2 3\n\
767 \x20 8 9 10 11 12 13 14 5 6 7 8 9 10 11 4 5 6 7 8 9 10\n\
768 \x2015 16 17 18 19 20 21 12 13 14 15 16 17 18 11 12 13 14 15 16 17\n\
769 \x2022 23 24 25 26 27 28 19 20 21 22 23 24 25 18 19 20 21 22 23 24\n\
770 \x2029 30 31 26 27 28 29 25 26 27 28 29 30 31\n\
772 \x20 April May June \n\
773 \x20 1 2 3 4 5 6 7 1 2 3 4 5 1 2\n\
774 \x20 8 9 10 11 12 13 14 6 7 8 9 10 11 12 3 4 5 6 7 8 9\n\
775 \x2015 16 17 18 19 20 21 13 14 15 16 17 18 19 10 11 12 13 14 15 16\n\
776 \x2022 23 24 25 26 27 28 20 21 22 23 24 25 26 17 18 19 20 21 22 23\n\
777 \x2029 30 27 28 29 30 31 24 25 26 27 28 29 30\n\
779 \x20 July August September \n\
780 \x20 1 2 3 4 5 6 7 1 2 3 4 1\n\
781 \x20 8 9 10 11 12 13 14 5 6 7 8 9 10 11 2 3 4 5 6 7 8\n\
782 \x2015 16 17 18 19 20 21 12 13 14 15 16 17 18 9 10 11 12 13 14 15\n\
783 \x2022 23 24 25 26 27 28 19 20 21 22 23 24 25 16 17 18 19 20 21 22\n\
784 \x2029 30 31 26 27 28 29 30 31 23 24 25 26 27 28 29\n\
787 \x20 October November December \n\
788 \x20 1 2 3 4 5 6 1 2 3 1\n\
789 \x20 7 8 9 10 11 12 13 4 5 6 7 8 9 10 2 3 4 5 6 7 8\n\
790 \x2014 15 16 17 18 19 20 11 12 13 14 15 16 17 9 10 11 12 13 14 15\n\
791 \x2021 22 23 24 25 26 27 18 19 20 21 22 23 24 16 17 18 19 20 21 22\n\
792 \x2028 29 30 31 25 26 27 28 29 30 23 24 25 26 27 28 29\n\
795 assert_eq_cal!(&format_year(2015, MONTHS_PER_ROW), "\
796 \x20 January February March \n\
797 \x20 1 2 3 1 2 3 4 5 6 7 1 2 3 4 5 6 7\n\
798 \x20 4 5 6 7 8 9 10 8 9 10 11 12 13 14 8 9 10 11 12 13 14\n\
799 \x2011 12 13 14 15 16 17 15 16 17 18 19 20 21 15 16 17 18 19 20 21\n\
800 \x2018 19 20 21 22 23 24 22 23 24 25 26 27 28 22 23 24 25 26 27 28\n\
801 \x2025 26 27 28 29 30 31 29 30 31 \n\
803 \x20 April May June \n\
804 \x20 1 2 3 4 1 2 1 2 3 4 5 6\n\
805 \x20 5 6 7 8 9 10 11 3 4 5 6 7 8 9 7 8 9 10 11 12 13\n\
806 \x2012 13 14 15 16 17 18 10 11 12 13 14 15 16 14 15 16 17 18 19 20\n\
807 \x2019 20 21 22 23 24 25 17 18 19 20 21 22 23 21 22 23 24 25 26 27\n\
808 \x2026 27 28 29 30 24 25 26 27 28 29 30 28 29 30 \n\
811 \x20 July August September \n\
812 \x20 1 2 3 4 1 1 2 3 4 5\n\
813 \x20 5 6 7 8 9 10 11 2 3 4 5 6 7 8 6 7 8 9 10 11 12\n\
814 \x2012 13 14 15 16 17 18 9 10 11 12 13 14 15 13 14 15 16 17 18 19\n\
815 \x2019 20 21 22 23 24 25 16 17 18 19 20 21 22 20 21 22 23 24 25 26\n\
816 \x2026 27 28 29 30 31 23 24 25 26 27 28 29 27 28 29 30 \n\
819 \x20 October November December \n\
820 \x20 1 2 3 1 2 3 4 5 6 7 1 2 3 4 5\n\
821 \x20 4 5 6 7 8 9 10 8 9 10 11 12 13 14 6 7 8 9 10 11 12\n\
822 \x2011 12 13 14 15 16 17 15 16 17 18 19 20 21 13 14 15 16 17 18 19\n\
823 \x2018 19 20 21 22 23 24 22 23 24 25 26 27 28 20 21 22 23 24 25 26\n\
824 \x2025 26 27 28 29 30 31 29 30 27 28 29 30 31 ");
830 test_dates_in_year();