1 // run-pass
2 // ignore-compare-mode-chalk
3
4 #![feature(fn_traits,
5 step_trait,
6 unboxed_closures,
7 )]
8
9 //! Derived from: <https://raw.githubusercontent.com/quickfur/dcal/master/dcal.d>.
10 //!
11 //! Originally converted to Rust by [Daniel Keep](https://github.com/DanielKeep).
12
13 use std::fmt::Write;
14
15 /// Date representation.
16 #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
17 struct NaiveDate(i32, u32, u32);
18
19 impl NaiveDate {
from_ymd(y: i32, m: u32, d: u32) -> NaiveDate20 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);
23 NaiveDate(y, m, d)
24 }
25
year(&self) -> i3226 pub fn year(&self) -> i32 {
27 self.0
28 }
29
month(&self) -> u3230 pub fn month(&self) -> u32 {
31 self.1
32 }
33
day(&self) -> u3234 pub fn day(&self) -> u32 {
35 self.2
36 }
37
succ(&self) -> NaiveDate38 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());
41 if d > n {
42 d = 1;
43 m += 1;
44 }
45 if m > 12 {
46 m = 1;
47 y += 1;
48 }
49 NaiveDate::from_ymd(y, m, d)
50 }
51
weekday(&self) -> Weekday52 pub fn weekday(&self) -> Weekday {
53 use Weekday::*;
54
55 // 0 = Sunday
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]
60 }
61
isoweekdate(&self) -> (i32, u32, Weekday)62 pub fn isoweekdate(&self) -> (i32, u32, Weekday) {
63 let first_dow_mon_0 = self.year_first_day_of_week().num_days_from_monday();
64
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;
68
69 if self.first_week_in_prev_year() {
70 week_mon_0 -= 1;
71 }
72
73 let weeks_in_year = self.last_week_number();
74
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();
79
80 if week_mon_0 < 0 {
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)
84 } else {
85 (year, (week_mon_0 + 1) as u32, wd)
86 }
87 }
88
first_week_in_prev_year(&self) -> bool89 fn first_week_in_prev_year(&self) -> bool {
90 let first_dow_mon_0 = self.year_first_day_of_week().num_days_from_monday();
91
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
97 }
98
year_first_day_of_week(&self) -> Weekday99 fn year_first_day_of_week(&self) -> Weekday {
100 NaiveDate::from_ymd(self.year(), 1, 1).weekday()
101 }
102
weeks_in_year(&self) -> u32103 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 }
106 }
107
last_week_number(&self) -> u32108 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 }
111 }
112
day_of_year(&self) -> u32113 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()
116 }
117
is_leap_year(&self) -> bool118 fn is_leap_year(&self) -> bool {
119 let year = self.year();
120 if year % 4 != 0 {
121 return false
122 } else if year % 100 != 0 {
123 return true
124 } else if year % 400 != 0 {
125 return false
126 } else {
127 return true
128 }
129 }
130
days_in_month(&self) -> u32131 fn days_in_month(&self) -> u32 {
132 match self.month() {
133 /* Jan */ 1 => 31,
134 /* Feb */ 2 => if self.is_leap_year() { 29 } else { 28 },
135 /* Mar */ 3 => 31,
136 /* Apr */ 4 => 30,
137 /* May */ 5 => 31,
138 /* Jun */ 6 => 30,
139 /* Jul */ 7 => 31,
140 /* Aug */ 8 => 31,
141 /* Sep */ 9 => 30,
142 /* Oct */ 10 => 31,
143 /* Nov */ 11 => 30,
144 /* Dec */ 12 => 31,
145 _ => unreachable!()
146 }
147 }
148 }
149
150 impl<'a, 'b> std::ops::Add<&'b NaiveDate> for &'a NaiveDate {
151 type Output = NaiveDate;
152
add(self, other: &'b NaiveDate) -> NaiveDate153 fn add(self, other: &'b NaiveDate) -> NaiveDate {
154 assert_eq!(*other, NaiveDate(0, 0, 1));
155 self.succ()
156 }
157 }
158
159 impl std::iter::Step for NaiveDate {
steps_between(_: &Self, _: &Self) -> Option<usize>160 fn steps_between(_: &Self, _: &Self) -> Option<usize> {
161 unimplemented!()
162 }
163
forward_checked(start: Self, n: usize) -> Option<Self>164 fn forward_checked(start: Self, n: usize) -> Option<Self> {
165 Some((0..n).fold(start, |x, _| x.succ()))
166 }
167
backward_checked(_: Self, _: usize) -> Option<Self>168 fn backward_checked(_: Self, _: usize) -> Option<Self> {
169 unimplemented!()
170 }
171 }
172
173 #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
174 pub enum Weekday {
175 Mon,
176 Tue,
177 Wed,
178 Thu,
179 Fri,
180 Sat,
181 Sun,
182 }
183
184 impl Weekday {
num_days_from_monday(&self) -> u32185 pub fn num_days_from_monday(&self) -> u32 {
186 use Weekday::*;
187 match *self {
188 Mon => 0,
189 Tue => 1,
190 Wed => 2,
191 Thu => 3,
192 Fri => 4,
193 Sat => 5,
194 Sun => 6,
195 }
196 }
197
num_days_from_sunday(&self) -> u32198 pub fn num_days_from_sunday(&self) -> u32 {
199 use Weekday::*;
200 match *self {
201 Sun => 0,
202 Mon => 1,
203 Tue => 2,
204 Wed => 3,
205 Thu => 4,
206 Fri => 5,
207 Sat => 6,
208 }
209 }
210 }
211
212 /// `GroupBy` implementation.
213 struct GroupBy<It: Iterator, F> {
214 it: std::iter::Peekable<It>,
215 f: F,
216 }
217
218 impl<It, F> Clone for GroupBy<It, F>
219 where
220 It: Iterator + Clone,
221 It::Item: Clone,
222 F: Clone,
223 {
clone(&self) -> Self224 fn clone(&self) -> Self {
225 GroupBy {
226 it: self.it.clone(),
227 f: self.f.clone(),
228 }
229 }
230 }
231
232 impl<'a, G, It: 'a, F: 'a> Iterator for GroupBy<It, F>
233 where It: Iterator + Clone,
234 It::Item: Clone,
235 F: Clone + FnMut(&It::Item) -> G,
236 G: Eq + Clone
237 {
238 type Item = (G, InGroup<std::iter::Peekable<It>, F, G>);
239
next(&mut self) -> Option<Self::Item>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) {
244 if key != k {
245 break;
246 }
247 self.it.next();
248 }
249
250 (key.clone(), InGroup {
251 it: start,
252 f: self.f.clone(),
253 g: key
254 })
255 })
256 }
257 }
258
259 #[derive(Copy, Clone)]
260 struct InGroup<It, F, G> {
261 it: It,
262 f: F,
263 g: G
264 }
265
266 impl<It: Iterator, F: FnMut(&It::Item) -> G, G: Eq> Iterator for InGroup<It, F, G> {
267 type Item = It::Item;
268
next(&mut self) -> Option<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 }
272 })
273 }
274 }
275
276 trait IteratorExt: Iterator + Sized {
group_by<G, F>(self, f: F) -> GroupBy<Self, F> where F: Clone + FnMut(&Self::Item) -> G, G: Eq277 fn group_by<G, F>(self, f: F) -> GroupBy<Self, F>
278 where F: Clone + FnMut(&Self::Item) -> G,
279 G: Eq
280 {
281 GroupBy { it: self.peekable(), f }
282 }
283
join(mut self, sep: &str) -> String where Self::Item: std::fmt::Display284 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();
289 for e in self {
290 s.push_str(sep);
291 write!(s, "{}", e).unwrap();
292 }
293 }
294 s
295 }
296
297 // HACK(eddyb): only needed because `impl Trait` can't be
298 // used with trait methods: `.foo()` becomes `.__(foo)`.
__<F, R>(self, f: F) -> R where F: FnOnce(Self) -> R299 fn __<F, R>(self, f: F) -> R
300 where F: FnOnce(Self) -> R {
301 f(self)
302 }
303 }
304
305 impl<It> IteratorExt for It where It: Iterator {}
306
307 /// Generates an iterator that yields exactly `n` spaces.
spaces(n: usize) -> std::iter::Take<std::iter::Repeat<char>>308 fn spaces(n: usize) -> std::iter::Take<std::iter::Repeat<char>> {
309 std::iter::repeat(' ').take(n)
310 }
311
test_spaces()312 fn test_spaces() {
313 assert_eq!(spaces(0).collect::<String>(), "");
314 assert_eq!(spaces(10).collect::<String>(), " ")
315 }
316
317 /// Returns an iterator of dates in a given year.
dates_in_year(year: i32) -> impl Iterator<Item=NaiveDate>+Clone318 fn dates_in_year(year: i32) -> impl Iterator<Item=NaiveDate>+Clone {
319 InGroup {
320 it: NaiveDate::from_ymd(year, 1, 1)..,
321 f: |d: &NaiveDate| d.year(),
322 g: year
323 }
324 }
325
test_dates_in_year()326 fn test_dates_in_year() {
327 {
328 let mut dates = dates_in_year(2013);
329 assert_eq!(dates.next(), Some(NaiveDate::from_ymd(2013, 1, 1)));
330
331 // Check increment.
332 assert_eq!(dates.next(), Some(NaiveDate::from_ymd(2013, 1, 2)));
333
334 // Check monthly roll-over.
335 for _ in 3..31 {
336 assert!(dates.next() != None);
337 }
338
339 assert_eq!(dates.next(), Some(NaiveDate::from_ymd(2013, 1, 31)));
340 assert_eq!(dates.next(), Some(NaiveDate::from_ymd(2013, 2, 1)));
341 }
342
343 {
344 // Check length of year.
345 let mut dates = dates_in_year(2013);
346 for _ in 0..365 {
347 assert!(dates.next() != None);
348 }
349 assert_eq!(dates.next(), None);
350 }
351
352 {
353 // Check length of leap year.
354 let mut dates = dates_in_year(1984);
355 for _ in 0..366 {
356 assert!(dates.next() != None);
357 }
358 assert_eq!(dates.next(), None);
359 }
360 }
361
362 /// Convenience trait for verifying that a given type iterates over
363 /// `NaiveDate`s.
364 trait DateIterator: Iterator<Item=NaiveDate> + Clone {}
365 impl<It> DateIterator for It where It: Iterator<Item=NaiveDate> + Clone {}
366
test_group_by()367 fn test_group_by() {
368 let input = [
369 [1, 1],
370 [1, 1],
371 [1, 2],
372 [2, 2],
373 [2, 3],
374 [2, 3],
375 [3, 3]
376 ];
377
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]],
382 &[[3, 3]]
383 ];
384 for ((_, a), b) in by_x.zip(expected_1.iter().cloned()) {
385 assert_eq!(&a.collect::<Vec<_>>()[..], b);
386 }
387
388 let by_y = input.iter().cloned().group_by(|a| a[1]);
389 let expected_2: &[&[[i32; 2]]] = &[
390 &[[1, 1], [1, 1]],
391 &[[1, 2], [2, 2]],
392 &[[2, 3], [2, 3], [3, 3]]
393 ];
394 for ((_, a), b) in by_y.zip(expected_2.iter().cloned()) {
395 assert_eq!(&a.collect::<Vec<_>>()[..], b);
396 }
397 }
398
399 /// Groups an iterator of dates by month.
by_month(it: impl Iterator<Item=NaiveDate> + Clone) -> impl Iterator<Item=(u32, impl Iterator<Item=NaiveDate> + Clone)> + Clone400 fn by_month(it: impl Iterator<Item=NaiveDate> + Clone)
401 -> impl Iterator<Item=(u32, impl Iterator<Item=NaiveDate> + Clone)> + Clone
402 {
403 it.group_by(|d| d.month())
404 }
405
test_by_month()406 fn test_by_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));
410 }
411 assert!(months.next().is_none());
412 }
413
414 /// Groups an iterator of dates by week.
by_week(it: impl DateIterator) -> impl Iterator<Item=(u32, impl DateIterator)> + Clone415 fn by_week(it: impl DateIterator)
416 -> impl Iterator<Item=(u32, impl DateIterator)> + Clone
417 {
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)
420 }
421
test_isoweekdate()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);
428 for yw in weeks {
429 if accum.0 == yw {
430 accum.1 += 1;
431 } else {
432 result.push(accum);
433 accum = (yw, 1);
434 }
435 }
436 result.push(accum);
437 result
438 }
439
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)]);
443
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)]);
447
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)]);
451 }
452
test_by_week()453 fn test_by_week() {
454 let mut weeks = dates_in_year(2013).__(by_week);
455 assert_eq!(
456 &*weeks.next().unwrap().1.collect::<Vec<_>>(),
457 &[
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),
463 ]
464 );
465 assert_eq!(
466 &*weeks.next().unwrap().1.collect::<Vec<_>>(),
467 &[
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),
475 ]
476 );
477 assert_eq!(weeks.next().unwrap().1.nth(0).unwrap(), NaiveDate::from_ymd(2013, 1, 13));
478 }
479
480 /// The number of columns per day in the formatted output.
481 const COLS_PER_DAY: u32 = 3;
482
483 /// The number of columns per week in the formatted output.
484 const COLS_PER_WEEK: u32 = 7 * COLS_PER_DAY;
485
486 /// Formats an iterator of weeks into an iterator of strings.
format_weeks(it: impl Iterator<Item = impl DateIterator>) -> impl Iterator<Item=String>487 fn format_weeks(it: impl Iterator<Item = impl DateIterator>) -> impl Iterator<Item=String> {
488 it.map(|week| {
489 let mut buf = String::with_capacity((COLS_PER_DAY * COLS_PER_WEEK + 2) as usize);
490
491 // Format each day into its own cell and append to target string.
492 let mut last_day = 0;
493 let mut first = true;
494 for d in week {
495 last_day = d.weekday().num_days_from_sunday();
496
497 // Insert enough filler to align the first day with its respective day-of-week.
498 if first {
499 buf.extend(spaces((COLS_PER_DAY * last_day) as usize));
500 first = false;
501 }
502
503 write!(buf, " {:>2}", d.day()).unwrap();
504 }
505
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));
509 buf
510 })
511 }
512
test_format_weeks()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<_>`.
517 .map(|(_, month)|
518 month.__(by_week)
519 .map(|(_, weeks)| weeks)
520 .__(format_weeks)
521 .join("\n"));
522
523 assert_eq!(
524 jan_2013.as_ref().map(|s| &**s),
525 Some(" 1 2 3 4 5\n\
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 ")
530 );
531 }
532
533 /// Formats the name of a month, centered on `COLS_PER_WEEK`.
month_title(month: u32) -> String534 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"
538 ];
539 assert_eq!(MONTH_NAMES.len(), 12);
540
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;
547
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));
553 result
554 }
555
test_month_title()556 fn test_month_title() {
557 assert_eq!(month_title(1).len(), COLS_PER_WEEK as usize);
558 }
559
560 /// Formats a month.
format_month(it: impl DateIterator) -> impl Iterator<Item=String>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());
564
565 Some(title).into_iter()
566 .chain(month_days.__(by_week)
567 .map(|(_, week)| week)
568 .__(format_weeks))
569 }
570
test_format_month()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()
575 .__(format_month)
576 .join("\n"));
577
578 assert_eq!(
579 month_fmt.as_ref().map(|s| &**s),
580 Some(" January \n\
581 \x20 1 2 3 4 5\n\
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 ")
586 );
587 }
588
589 /// Formats an iterator of months.
format_months(it: impl Iterator<Item = impl DateIterator>) -> impl Iterator<Item=impl Iterator<Item=String>>590 fn format_months(it: impl Iterator<Item = impl DateIterator>)
591 -> impl Iterator<Item=impl Iterator<Item=String>>
592 {
593 it.map(format_month)
594 }
595
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> {
paste_blocks(self, sep_width: usize) -> PasteBlocksIter<Self::Item>600 fn paste_blocks(self, sep_width: usize) -> PasteBlocksIter<Self::Item> {
601 PasteBlocksIter {
602 iters: self.collect(),
603 cache: vec![],
604 col_widths: None,
605 sep_width: sep_width,
606 }
607 }
608 }
609
610 impl<It> PasteBlocks for It where It: Iterator, It::Item: Iterator<Item=String> {}
611
612 struct PasteBlocksIter<StrIt>
613 where StrIt: Iterator<Item=String> {
614 iters: Vec<StrIt>,
615 cache: Vec<Option<String>>,
616 col_widths: Option<Vec<usize>>,
617 sep_width: usize,
618 }
619
620 impl<StrIt> Iterator for PasteBlocksIter<StrIt>
621 where StrIt: Iterator<Item=String> {
622 type Item = String;
623
next(&mut self) -> Option<String>624 fn next(&mut self) -> Option<String> {
625 self.cache.clear();
626
627 // `cache` is now the next line from each iterator.
628 self.cache.extend(self.iters.iter_mut().map(|it| it.next()));
629
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 }
632
633 // Get the column widths if we haven't already.
634 let col_widths = match self.col_widths {
635 Some(ref v) => &**v,
636 None => {
637 self.col_widths = Some(self.cache.iter()
638 .map(|ms| ms.as_ref().map(|s| s.len()).unwrap_or(0))
639 .collect());
640 &**self.col_widths.as_ref().unwrap()
641 }
642 };
643
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()));
647
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);
654 accum
655 }))
656 }
657 }
658
test_paste_blocks()659 fn test_paste_blocks() {
660 let row = dates_in_year(2013)
661 .__(by_month).map(|(_, days)| days)
662 .take(3)
663 .__(format_months)
664 .paste_blocks(1)
665 .join("\n");
666 assert_eq!(
667 &*row,
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\
674 \x20 31 "
675 );
676 }
677
678 /// Produces an iterator that yields `n` elements at a time.
679 trait Chunks: Iterator + Sized {
chunks(self, n: usize) -> ChunksIter<Self>680 fn chunks(self, n: usize) -> ChunksIter<Self> {
681 assert!(n > 0);
682 ChunksIter {
683 it: self,
684 n: n,
685 }
686 }
687 }
688
689 impl<It> Chunks for It where It: Iterator {}
690
691 struct ChunksIter<It>
692 where It: Iterator {
693 it: It,
694 n: usize,
695 }
696
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.
702
703 impl<It> Iterator for ChunksIter<It>
704 where It: Iterator {
705 type Item = Vec<It::Item>;
706
next(&mut self) -> Option<Vec<It::Item>>707 fn next(&mut self) -> Option<Vec<It::Item>> {
708 let first = self.it.next()?;
709
710 let mut result = Vec::with_capacity(self.n);
711 result.push(first);
712
713 Some((&mut self.it).take(self.n-1)
714 .fold(result, |mut acc, next| { acc.push(next); acc }))
715 }
716 }
717
test_chunks()718 fn test_chunks() {
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]]);
722 }
723
724 /// Formats a year.
format_year(year: i32, months_per_row: usize) -> String725 fn format_year(year: i32, months_per_row: usize) -> String {
726 const COL_SPACING: usize = 1;
727
728 // Start by generating all dates for the given year.
729 dates_in_year(year)
730
731 // Group them by month and throw away month number.
732 .__(by_month).map(|(_, days)| days)
733
734 // Group the months into horizontal rows.
735 .chunks(months_per_row)
736
737 // Format each row...
738 .map(|r| r.into_iter()
739 // ... by formatting each month ...
740 .__(format_months)
741
742 // ... and horizontally pasting each respective month's lines together.
743 .paste_blocks(COL_SPACING)
744 .join("\n")
745 )
746
747 // Insert a blank line between each row.
748 .join("\n\n")
749 }
750
test_format_year()751 fn test_format_year() {
752 const MONTHS_PER_ROW: usize = 3;
753
754 macro_rules! assert_eq_cal {
755 ($lhs:expr, $rhs:expr) => {
756 if $lhs != $rhs {
757 println!("got:\n```\n{}\n```\n", $lhs.replace(" ", "."));
758 println!("expected:\n```\n{}\n```", $rhs.replace(" ", "."));
759 panic!("calendars didn't match!");
760 }
761 }
762 }
763
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\
771 \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\
778 \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\
785 \x20 30 \n\
786 \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\
793 \x20 30 31 ");
794
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\
802 \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\
809 \x20 31 \n\
810 \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\
817 \x20 30 31 \n\
818 \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 ");
825 }
826
main()827 fn main() {
828 // Run tests.
829 test_spaces();
830 test_dates_in_year();
831 test_group_by();
832 test_by_month();
833 test_isoweekdate();
834 test_by_week();
835 test_format_weeks();
836 test_month_title();
837 test_format_month();
838 test_paste_blocks();
839 test_chunks();
840 test_format_year();
841 }
842