Last active
February 10, 2023 11:21
-
-
Save dermesser/7a1691e62ff83064d5cd to your computer and use it in GitHub Desktop.
A simple but working perpetual calendar implementation (don't expect fancy displaying abilities, it's essentially an Iterator<Item=Date>). Should also be fast enough for actual applications: Takes 220us per year including formatting on an "Intel(R) Xeon(R) CPU @ 2.50GHz". Supports forward and backward iteration from a given date.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extern crate time; | |
extern crate argparse; | |
use std::cmp::Ordering; | |
use std::cmp::PartialOrd; | |
use std::fmt::Write; | |
use std::marker::Copy; | |
use std::option::Option; | |
use std::ops::Add; | |
use std::ops::Sub; | |
use std::string::ToString; | |
//use test::Bencher; | |
#[derive(Debug)] | |
struct DayOfWeek { | |
// 0 = Sunday | |
dow: u8 | |
} | |
impl Clone for DayOfWeek { | |
fn clone(&self) -> DayOfWeek { DayOfWeek { dow: self.dow } } | |
} | |
impl Copy for DayOfWeek {} | |
impl ToString for DayOfWeek { | |
fn to_string(&self) -> String { | |
match self.dow { | |
0 => "Sun", | |
1 => "Mon", | |
2 => "Tue", | |
3 => "Wed", | |
4 => "Thu", | |
5 => "Fri", | |
6 => "Sat", | |
_ => "Err" | |
}.to_string() | |
} | |
} | |
impl Add<i32> for DayOfWeek { | |
type Output = DayOfWeek; | |
fn add(self, d: i32) -> DayOfWeek { | |
DayOfWeek { dow: ((self.dow as i32 + d) % 7) as u8 } | |
} | |
} | |
impl Sub<i32> for DayOfWeek { | |
type Output = DayOfWeek; | |
fn sub(self, d: i32) -> DayOfWeek { | |
if d <= self.dow as i32 { | |
DayOfWeek { dow: self.dow - d as u8 } | |
} else if d > self.dow as i32 { | |
let mut new = self.dow as i32 - d; | |
while new < 0 { | |
new += 7; | |
} | |
assert!(new > 0); | |
DayOfWeek { dow: new as u8 } | |
} else { | |
unimplemented!() | |
} | |
} | |
} | |
impl PartialEq for DayOfWeek { | |
fn eq(&self, other: &DayOfWeek) -> bool { self.dow == other.dow } | |
} | |
#[derive(Debug)] | |
struct Date { | |
// 1900 = 0 (2015 = 115) | |
year: u32, | |
// Jan = 0 | |
month: u8, | |
// Min = 1 | |
day: u8, | |
dow: DayOfWeek, | |
} | |
impl Date { | |
/// Returns a new Date object, initialized to today's date. | |
fn new() -> Date { | |
let today = time::now(); | |
Date { year: today.tm_year as u32, | |
month: today.tm_mon as u8, | |
day: today.tm_mday as u8, | |
dow: DayOfWeek { dow: today.tm_wday as u8 } } | |
} | |
/// Returns a new Date object, initialized to the day given. | |
/// The day of the week is dynamically calculated. | |
fn new_static(day: u8, month: u8, year: u32) -> Date { | |
let mut date = Date { year: year-1900, month: month-1, day: day, dow: DayOfWeek { dow: 0 } }; | |
date.set_dow(); | |
date | |
} | |
/// Calculates the dow for a (year,month,day) tuple (represented by a Date object). | |
fn set_dow(&mut self) { | |
let base = Date { year: 115, month: 11, day: 24, dow: DayOfWeek { dow: 4 }}; | |
if &*self >= &base { | |
for d in base { | |
if d.day == self.day && d.month == self.month && d.year == self.year { | |
self.dow = d.dow; | |
break; | |
} | |
} | |
} else { | |
for d in base.rev() { | |
if d.day == self.day && d.month == self.month && d.year == self.year { | |
self.dow = d.dow; | |
break; | |
} | |
} | |
} | |
} | |
} | |
impl ToString for Date { | |
fn to_string(&self) -> String { | |
let mut s = String::new(); | |
let _ = s.write_fmt(format_args!("{}-{}-{}: {}", self.year+1900, self.month+1, self.day, self.dow.to_string())); | |
s | |
} | |
} | |
impl Clone for Date { | |
fn clone(&self) -> Date { | |
Date { year: self.year, month: self.month, day: self.day, dow: self.dow } | |
} | |
} | |
impl PartialEq for Date { | |
fn eq(&self, other: &Date) -> bool { | |
let eq = self.year == other.year && | |
self.month == other.month && | |
self.day == other.day; | |
if eq { | |
assert_eq!(self.dow, other.dow); | |
} | |
eq | |
} | |
} | |
impl PartialOrd for Date { | |
fn partial_cmp(&self, other: &Date) -> Option<Ordering> { | |
let ycmp = self.year.partial_cmp(&other.year).unwrap(); | |
let mcmp = self.month.partial_cmp(&other.month).unwrap(); | |
let dcmp = self.day.partial_cmp(&other.day).unwrap(); | |
if ycmp != Ordering::Equal { | |
Some(ycmp) | |
} else if mcmp != Ordering::Equal { | |
Some(mcmp) | |
} else { | |
Some(dcmp) | |
} | |
} | |
} | |
fn days_in_month(month: u8) -> u8 { | |
match month { | |
0|2|4|6|7|9|11 => 31, | |
3|5|8|10 => 30, | |
1 => 28, | |
_ => 0, | |
} | |
} | |
impl Iterator for Date { | |
type Item = Date; | |
fn next(&mut self) -> Option<Date> { | |
self.dow = self.dow + 1; | |
match *self { | |
// Sigh, February | |
// case: leap year | |
Date{ day: 28, month: 1, .. } if ((self.year % 4 == 0) && (self.year % 100 != 0)) || (self.year % 400 == 0) => { self.day += 1 }, | |
// case: normal year | |
Date{ day: 28, month: 1, .. } => { self.month += 1; self.day = 1 }, | |
Date{ day: 29, month: 1, .. } => { self.month += 1; self.day = 1 }, | |
// Normal months | |
Date{ day: 1...29, .. } => self.day += 1, | |
Date{ day: 30, month: m @ _, .. } if (days_in_month(m) == 31) => self.day += 1, | |
// Next year | |
Date{ day: 31, month: 11, .. } => { self.year += 1; self.month = 0; self.day = 1 }, | |
// Next month | |
Date{ day: 31, month: m @ _, .. } if (days_in_month(m) == 31) => { self.month += 1; self.day = 1 }, | |
Date{ day: 30, month: m @ _, .. } if (days_in_month(m) == 30) => { self.month += 1; self.day = 1 }, | |
_ => {println!("{} {} {}", self.year, self.month, self.day); return None}, | |
} | |
Some(self.clone()) | |
} | |
} | |
impl DoubleEndedIterator for Date { | |
fn next_back(&mut self) -> Option<Date> { | |
self.dow = self.dow - 1; | |
match *self { | |
Date{ day: 2...31, .. } => self.day -= 1, | |
// crossing new year's | |
Date{ day: 1, month: 0, year: y @ _, .. } => { assert!(y > 0); self.day = 31; self.month = 11; self.year -= 1 }, | |
// sigh, February | |
Date{ day: 1, month: 2, .. } if ((self.year % 4 == 0) && (self.year % 100 != 0)) || (self.year % 400 == 0) => { self.day = 29; self.month = 1 }, | |
Date{ day: 1, month: 2, .. } => { self.day = 28; self.month = 1 }, | |
// Catches 1st days of everything except for Jan and Mar | |
Date{ day: 1, month: m @ _, .. } => { self.day = days_in_month(m-1); self.month -= 1 }, | |
_ => {println!("{} {} {}", self.year, self.month, self.day); return None}, | |
} | |
Some(self.clone()) | |
} | |
} | |
fn main() { | |
let mut max_year: u32 = 0; | |
{ | |
let mut ap = argparse::ArgumentParser::new(); | |
ap.refer(&mut max_year).add_option(&["--max"], argparse::Store, "Year until which to go"); | |
ap.parse_args_or_exit(); | |
} | |
if max_year == 0 { | |
max_year = 2017; | |
} | |
let mut d = Date::new(); | |
d.month = 0; | |
d.day = 1; | |
d.dow.dow = 4; | |
if max_year >= 1900+d.year { | |
for date in d { | |
if date.year+1900 == max_year { | |
break | |
} | |
println!("{}", date.to_string()); | |
} | |
} else { | |
for date in d.rev() { | |
if date.year+1900 == max_year { | |
break | |
} | |
println!("{}", date.to_string()); | |
} | |
} | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::Date; | |
use std::iter::Iterator; | |
#[test] | |
fn test_date_dow() { | |
let d = Date::new_static(31, 12, 2015); | |
assert_eq!(d.dow.dow, 4); | |
} | |
#[test] | |
fn test_date_next() { | |
let mut d = Date::new_static(31, 12, 2015); | |
let cmp = Date::new_static(1, 1, 2016); | |
d.next(); | |
assert_eq!(d, cmp); | |
} | |
#[test] | |
fn test_date_next2() { | |
let mut d = Date::new_static(1, 1, 2016); | |
let cmp = Date::new_static(2, 1, 2016); | |
d.next(); | |
assert_eq!(d, cmp); | |
} | |
#[test] | |
fn test_date_next3() { | |
let mut d = Date::new_static(28, 2, 2016); | |
let cmp = Date::new_static(29, 2, 2016); | |
d.next(); | |
assert_eq!(d, cmp); | |
} | |
#[test] | |
fn test_date_next4() { | |
let mut d = Date::new_static(28, 2, 2017); | |
let cmp = Date::new_static(1, 3, 2017); | |
d.next(); | |
assert_eq!(d, cmp); | |
} | |
#[test] | |
fn test_date_prev1() { | |
let mut d = Date::new_static(28, 2, 2017); | |
let cmp = Date::new_static(27, 2, 2017); | |
d.next_back(); | |
assert_eq!(d, cmp); | |
} | |
#[test] | |
fn test_date_prev2() { | |
let mut d = Date::new_static(1, 3, 2017); | |
let cmp = Date::new_static(28, 2, 2017); | |
d.next_back(); | |
assert_eq!(d, cmp); | |
} | |
#[test] | |
fn test_date_prev3() { | |
let mut d = Date::new_static(1, 1, 2017); | |
let cmp = Date::new_static(31, 12, 2016); | |
d.next_back(); | |
assert_eq!(d, cmp); | |
} | |
#[test] | |
fn test_date_prev4() { | |
let mut d = Date::new_static(1, 3, 2016); | |
let cmp = Date::new_static(29, 2, 2016); | |
d.next_back(); | |
assert_eq!(d, cmp); | |
} | |
#[test] | |
fn test_lesser() { | |
assert!(Date::new_static(5, 5, 2000) < Date::new_static(5, 6, 2000)) | |
} | |
#[test] | |
fn test_greater() { | |
assert!(Date::new_static(6, 5, 2000) > Date::new_static(1, 5, 1999)) | |
} | |
#[test] | |
fn test_equal() { | |
assert!(Date::new_static(1, 5, 1999) == Date::new_static(1, 5, 1999)) | |
} | |
/* | |
#[bench] | |
fn bench_date1(b: &mut Bencher) { | |
let mut d = Date::new_static(1, 1, 2000); | |
b.iter(move || { if d.year < 3000 { d.next(); } }); | |
} | |
*/ | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment