Skip to content

Instantly share code, notes, and snippets.

@dermesser
Last active February 10, 2023 11:21
Show Gist options
  • Save dermesser/7a1691e62ff83064d5cd to your computer and use it in GitHub Desktop.
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.
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