From 646b6c27b91dac07dbe171f66ccea7d4468832e9 Mon Sep 17 00:00:00 2001 From: Kang Seonghoon Date: Fri, 28 Mar 2014 20:38:11 +0900 Subject: [PATCH] initial commit. --- .gitignore | 14 + Makefile.in | 33 ++ configure | 4 + src/chrono/date.rs | 1283 ++++++++++++++++++++++++++++++++++++++++ src/chrono/datetime.rs | 271 +++++++++ src/chrono/lib.rs | 9 + src/chrono/time.rs | 161 +++++ 7 files changed, 1775 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile.in create mode 100755 configure create mode 100644 src/chrono/date.rs create mode 100644 src/chrono/datetime.rs create mode 100644 src/chrono/lib.rs create mode 100644 src/chrono/time.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e186bfc --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +*~ +*# +*.o +*.so +*.dylib +*.dSYM +*.dll +*.rlib +*.dummy +*.exe +build +Makefile +*-test +doc diff --git a/Makefile.in b/Makefile.in new file mode 100644 index 0000000..8aa625b --- /dev/null +++ b/Makefile.in @@ -0,0 +1,33 @@ +VPATH=%VPATH% + +CC ?= gcc +CXX ?= g++ +CXXFLAGS ?= +AR ?= ar +RUSTC ?= rustc +RUSTDOC ?= rustdoc +RUSTFLAGS ?= -O + +LIB_RS = src/chrono/lib.rs +RUST_SRC = $(shell find $(VPATH)/src/chrono -type f -name '*.rs') + +.PHONY: all +all: libchrono.dummy + +libchrono.dummy: $(LIB_RS) $(RUST_SRC) + $(RUSTC) $(RUSTFLAGS) $< --out-dir . + touch $@ + +chrono-test: $(LIB_RS) $(RUST_SRC) + $(RUSTC) $(RUSTFLAGS) $< -o $@ --test + +check: chrono-test + ./chrono-test + +doc: $(LIB_RS) $(RUST_SRC) + $(RUSTDOC) $(LIB_RS) + +.PHONY: clean +clean: + rm -f *.o *.a *.so *.dylib *.rlib *.dll *.dummy *.exe *-test + diff --git a/configure b/configure new file mode 100755 index 0000000..62a0f4c --- /dev/null +++ b/configure @@ -0,0 +1,4 @@ +#!/bin/bash + +SRCDIR="$(cd $(dirname $0) && pwd)" +sed "s#%VPATH%#${SRCDIR}#" ${SRCDIR}/Makefile.in > Makefile diff --git a/src/chrono/date.rs b/src/chrono/date.rs new file mode 100644 index 0000000..25966ad --- /dev/null +++ b/src/chrono/date.rs @@ -0,0 +1,1283 @@ +/*! + * ISO 8601 calendar date. + */ + +use std::fmt; + +use self::internals::{Of, Mdf, YearFlags}; +use self::internals::{DateImpl, MIN_YEAR, MAX_YEAR}; + +/// The day of week (DOW). +#[deriving(Eq, TotalEq, FromPrimitive, Show)] +pub enum Weekday { + Mon = 0, + Tue = 1, + Wed = 2, + Thu = 3, + Fri = 4, + Sat = 5, + Sun = 6, +} + +impl Weekday { + /// The next day in the week. + #[inline] + pub fn succ(&self) -> Weekday { + match *self { + Mon => Tue, + Tue => Wed, + Wed => Thu, + Thu => Fri, + Fri => Sat, + Sat => Sun, + Sun => Mon, + } + } + + /// The previous day in the week. + #[inline] + pub fn pred(&self) -> Weekday { + match *self { + Mon => Sun, + Tue => Mon, + Wed => Tue, + Thu => Wed, + Fri => Thu, + Sat => Fri, + Sun => Sat, + } + } + + /// Returns a DOW number starting from Monday = 1. (ISO 8601 weekday number) + #[inline] + pub fn number_from_monday(&self) -> uint { + match *self { + Mon => 1, + Tue => 2, + Wed => 3, + Thu => 4, + Fri => 5, + Sat => 6, + Sun => 7, + } + } + + /// Returns a DOW number starting from Sunday = 1. + #[inline] + pub fn number_from_sunday(&self) -> uint { + match *self { + Mon => 2, + Tue => 3, + Wed => 4, + Thu => 5, + Fri => 6, + Sat => 7, + Sun => 1, + } + } + + /// Returns a DOW number starting from Monday = 0. + #[inline] + pub fn ndays_from_monday(&self) -> uint { + match *self { + Mon => 0, + Tue => 1, + Wed => 2, + Thu => 3, + Fri => 4, + Sat => 5, + Sun => 6, + } + } + + /// Returns a DOW number starting from Sunday = 0. + #[inline] + pub fn ndays_from_sunday(&self) -> uint { + match *self { + Mon => 1, + Tue => 2, + Wed => 3, + Thu => 4, + Fri => 5, + Sat => 6, + Sun => 0, + } + } +} + +/// ISO 8601 calendar date. Also supports the conversion from ISO 8601 ordinal and week date. +/// Allows for every proleptic Gregorian date from Jan 1, 262145 BCE to Dec 31, 262143 CE. +#[deriving(Eq, TotalEq, Ord, TotalOrd, Hash)] +pub struct Date { + priv ymdf: DateImpl, // (year << 13) | mdf +} + +impl Date { + /// The internal constructor with the verification. + fn new(year: int, mdf: Mdf) -> Option { + if year >= MIN_YEAR as int && year <= MAX_YEAR as int && mdf.valid() { + let Mdf(mdf) = mdf; + Some(Date { ymdf: ((year << 13) as DateImpl) | (mdf as DateImpl) }) + } else { + None + } + } + + /// Makes a new `Date` from year, month and day. + /// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE. + /// + /// Returns `None` on the out-of-range date, invalid month and/or day. + pub fn from_ymd(year: int, month: uint, day: uint) -> Option { + let flags = YearFlags::from_year(year); + let mdf = Mdf::new(month, day, flags); + Date::new(year, mdf) + } + + /// Makes a new `Date` from year and day of year (DOY or "ordinal"). + /// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE. + /// + /// Returns `None` on the out-of-range date and/or invalid DOY. + pub fn from_yo(year: int, ordinal: uint) -> Option { + let flags = YearFlags::from_year(year); + let mdf = Of::new(ordinal, flags).to_mdf(); + Date::new(year, mdf) + } + + /// Makes a new `Date` from ISO week date (year and week number) and day of the week (DOW). + /// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE. + /// The resulting `Date` may have a different year from the input year. + /// + /// Returns `None` on the out-of-range date and/or invalid week number. + pub fn from_isoywd(year: int, week: uint, weekday: Weekday) -> Option { + let flags = YearFlags::from_year(year); + let nweeks = flags.nisoweeks(); + if 1 <= week && week <= nweeks { + // ordinal = week ordinal - delta + let weekord = week * 7 + weekday as uint; + let delta = flags.isoweek_delta(); + if weekord <= delta { // ordinal < 1, previous year + let prevflags = YearFlags::from_year(year - 1); + let mdf = Of::new(weekord + prevflags.ndays() - delta, prevflags).to_mdf(); + Date::new(year - 1, mdf) + } else { + let ordinal = weekord - delta; + let ndays = flags.ndays(); + if ordinal <= ndays { // this year + let mdf = Of::new(ordinal, flags).to_mdf(); + Date::new(year, mdf) + } else { // ordinal > ndays, next year + let nextflags = YearFlags::from_year(year + 1); + let mdf = Of::new(ordinal - ndays, nextflags).to_mdf(); + Date::new(year + 1, mdf) + } + } + } else { + None + } + } + + /// Returns the packed month, day and year flags. + #[inline] + fn mdf(&self) -> Mdf { + Mdf((self.ymdf & 0b1111_11111_1111) as uint) + } + + /// Returns the packed ordinal and year flags. + #[inline] + fn of(&self) -> Of { + self.mdf().to_of() + } + + /// Returns the year number. + #[inline] + pub fn year(&self) -> int { + (self.ymdf >> 13) as int + } + + /// Returns the absolute year number starting from 1 with a boolean flag, + /// which is false when the year predates the epoch (BCE/BC) and true otherwise (CE/AD). + #[inline] + pub fn year_ce(&self) -> (bool, uint) { + let year = self.year(); + if year < 1 { + (false, (1 - year) as uint) + } else { + (true, year as uint) + } + } + + /// Returns the month number starting from 1. + #[inline] + pub fn month(&self) -> uint { + self.mdf().month() + } + + /// Returns the month number starting from 0. + #[inline] + pub fn month0(&self) -> uint { + self.mdf().month() - 1 + } + + /// Returns the day of month starting from 1. + #[inline] + pub fn day(&self) -> uint { + self.mdf().day() + } + + /// Returns the day of month starting from 0. + #[inline] + pub fn day0(&self) -> uint { + self.mdf().day() - 1 + } + + /// Returns the day of year starting from 1. + #[inline] + pub fn ordinal(&self) -> uint { + self.of().ordinal() + } + + /// Returns the day of year starting from 0. + #[inline] + pub fn ordinal0(&self) -> uint { + self.of().ordinal() - 1 + } + + /// Returns the day of week. + #[inline] + pub fn weekday(&self) -> Weekday { + self.of().weekday() + } + + /// Returns the ISO week date: an adjusted year, week number and day of week. + /// The adjusted year may differ from that of the calendar date. + pub fn isoweekdate(&self) -> (int, uint, Weekday) { + let of = self.of(); + let year = self.year(); + let (rawweek, weekday) = of.isoweekdate_raw(); + if rawweek < 1 { // previous year + let prevlastweek = YearFlags::from_year(year - 1).nisoweeks(); + (year - 1, prevlastweek, weekday) + } else { + let lastweek = of.flags().nisoweeks(); + if rawweek > lastweek { // next year + (year + 1, 1, weekday) + } else { + (year, rawweek, weekday) + } + } + } + + /// Makes a new `Date` with the year number changed. + /// + /// Returns `None` when the resulting `Date` would be invalid. + #[inline] + pub fn with_year(&self, year: int) -> Option { + // adjust the flags as needed + let flags = YearFlags::from_year(year); + let mdf = self.mdf().with_flags(flags); + Date::new(year, mdf) + } + + /// Makes a new `Date` with the packed month, day and year flags changed. + /// + /// Returns `None` when the resulting `Date` would be invalid. + #[inline] + fn with_mdf(&self, mdf: Mdf) -> Option { + if mdf.valid() { + let Mdf(mdf) = mdf; + Some(Date { ymdf: (self.ymdf & !0b1111_11111_1111) | mdf as DateImpl }) + } else { + None + } + } + + /// Makes a new `Date` with the month number (starting from 1) changed. + /// + /// Returns `None` when the resulting `Date` would be invalid. + #[inline] + pub fn with_month(&self, month: uint) -> Option { + self.with_mdf(self.mdf().with_month(month)) + } + + /// Makes a new `Date` with the month number (starting from 0) changed. + /// + /// Returns `None` when the resulting `Date` would be invalid. + #[inline] + pub fn with_month0(&self, month0: uint) -> Option { + self.with_mdf(self.mdf().with_month(month0 + 1)) + } + + /// Makes a new `Date` with the day of month (starting from 1) changed. + /// + /// Returns `None` when the resulting `Date` would be invalid. + #[inline] + pub fn with_day(&self, day: uint) -> Option { + self.with_mdf(self.mdf().with_day(day)) + } + + /// Makes a new `Date` with the day of month (starting from 0) changed. + /// + /// Returns `None` when the resulting `Date` would be invalid. + #[inline] + pub fn with_day0(&self, day0: uint) -> Option { + self.with_mdf(self.mdf().with_day(day0 + 1)) + } + + /// Makes a new `Date` with the day of year (starting from 1) changed. + /// + /// Returns `None` when the resulting `Date` would be invalid. + #[inline] + pub fn with_ordinal(&self, ordinal: uint) -> Option { + self.with_mdf(self.mdf().to_of().with_ordinal(ordinal).to_mdf()) + } + + /// Makes a new `Date` with the day of year (starting from 0) changed. + /// + /// Returns `None` when the resulting `Date` would be invalid. + #[inline] + pub fn with_ordinal0(&self, ordinal0: uint) -> Option { + self.with_mdf(self.mdf().to_of().with_ordinal(ordinal0 + 1).to_mdf()) + } + + /// Returns the number of days since January 1, 1 (Day 1) in the proleptic Gregorian calendar. + pub fn ndays_from_ce(&self) -> int { + // we know this wouldn't overflow since year is limited to 1/2^13 of i32's full range. + let mut year = self.year() - 1; + let mut ndays = 0; + if year < 0 { + let excess = 1 + (-year) / 400; + year += excess * 400; + ndays -= excess * 146097; + } + let div_100 = year / 100; + ndays += ((year * 1461) >> 2) - div_100 + (div_100 >> 2); + ndays + self.of().ordinal() as int + } +} + +impl fmt::Show for Date { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f.buf, "{:04}-{:02}-{:02}", self.year(), self.month(), self.day()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::{int, uint}; + use std::iter::range_inclusive; + + #[test] + fn test_date_from_ymd() { + assert!(Date::from_ymd(2012, 0, 1).is_none()); + assert!(Date::from_ymd(2012, 1, 1).is_some()); + assert!(Date::from_ymd(2012, 2, 29).is_some()); + assert!(Date::from_ymd(2014, 2, 29).is_none()); + assert!(Date::from_ymd(2014, 3, 0).is_none()); + assert!(Date::from_ymd(2014, 3, 1).is_some()); + assert!(Date::from_ymd(2014, 3, 31).is_some()); + assert!(Date::from_ymd(2014, 3, 32).is_none()); + assert!(Date::from_ymd(2014, 12, 31).is_some()); + assert!(Date::from_ymd(2014, 13, 1).is_none()); + } + + #[test] + fn test_date_from_yo() { + assert!(Date::from_yo(2012, 0).is_none()); + assert_eq!(Date::from_yo(2012, 1), Date::from_ymd(2012, 1, 1)); + assert_eq!(Date::from_yo(2012, 2), Date::from_ymd(2012, 1, 2)); + assert_eq!(Date::from_yo(2012, 32), Date::from_ymd(2012, 2, 1)); + assert_eq!(Date::from_yo(2012, 60), Date::from_ymd(2012, 2, 29)); + assert_eq!(Date::from_yo(2012, 61), Date::from_ymd(2012, 3, 1)); + assert_eq!(Date::from_yo(2012, 100), Date::from_ymd(2012, 4, 9)); + assert_eq!(Date::from_yo(2012, 200), Date::from_ymd(2012, 7, 18)); + assert_eq!(Date::from_yo(2012, 300), Date::from_ymd(2012, 10, 26)); + assert_eq!(Date::from_yo(2012, 366), Date::from_ymd(2012, 12, 31)); + assert!(Date::from_yo(2012, 367).is_none()); + + assert!(Date::from_yo(2014, 0).is_none()); + assert_eq!(Date::from_yo(2014, 1), Date::from_ymd(2014, 1, 1)); + assert_eq!(Date::from_yo(2014, 2), Date::from_ymd(2014, 1, 2)); + assert_eq!(Date::from_yo(2014, 32), Date::from_ymd(2014, 2, 1)); + assert_eq!(Date::from_yo(2014, 59), Date::from_ymd(2014, 2, 28)); + assert_eq!(Date::from_yo(2014, 60), Date::from_ymd(2014, 3, 1)); + assert_eq!(Date::from_yo(2014, 100), Date::from_ymd(2014, 4, 10)); + assert_eq!(Date::from_yo(2014, 200), Date::from_ymd(2014, 7, 19)); + assert_eq!(Date::from_yo(2014, 300), Date::from_ymd(2014, 10, 27)); + assert_eq!(Date::from_yo(2014, 365), Date::from_ymd(2014, 12, 31)); + assert!(Date::from_yo(2014, 366).is_none()); + } + + #[test] + fn test_date_from_isoywd() { + assert!(Date::from_isoywd(2004, 0, Sun).is_none()); + assert_eq!(Date::from_isoywd(2004, 1, Mon), Date::from_ymd(2003, 12, 29)); + assert_eq!(Date::from_isoywd(2004, 1, Sun), Date::from_ymd(2004, 1, 4)); + assert_eq!(Date::from_isoywd(2004, 2, Mon), Date::from_ymd(2004, 1, 5)); + assert_eq!(Date::from_isoywd(2004, 2, Sun), Date::from_ymd(2004, 1, 11)); + assert_eq!(Date::from_isoywd(2004, 52, Mon), Date::from_ymd(2004, 12, 20)); + assert_eq!(Date::from_isoywd(2004, 52, Sun), Date::from_ymd(2004, 12, 26)); + assert_eq!(Date::from_isoywd(2004, 53, Mon), Date::from_ymd(2004, 12, 27)); + assert_eq!(Date::from_isoywd(2004, 53, Sun), Date::from_ymd(2005, 1, 2)); + assert!(Date::from_isoywd(2004, 54, Mon).is_none()); + + assert!(Date::from_isoywd(2011, 0, Sun).is_none()); + assert_eq!(Date::from_isoywd(2011, 1, Mon), Date::from_ymd(2011, 1, 3)); + assert_eq!(Date::from_isoywd(2011, 1, Sun), Date::from_ymd(2011, 1, 9)); + assert_eq!(Date::from_isoywd(2011, 2, Mon), Date::from_ymd(2011, 1, 10)); + assert_eq!(Date::from_isoywd(2011, 2, Sun), Date::from_ymd(2011, 1, 16)); + + assert_eq!(Date::from_isoywd(2018, 51, Mon), Date::from_ymd(2018, 12, 17)); + assert_eq!(Date::from_isoywd(2018, 51, Sun), Date::from_ymd(2018, 12, 23)); + assert_eq!(Date::from_isoywd(2018, 52, Mon), Date::from_ymd(2018, 12, 24)); + assert_eq!(Date::from_isoywd(2018, 52, Sun), Date::from_ymd(2018, 12, 30)); + assert!(Date::from_isoywd(2018, 53, Mon).is_none()); + } + + #[test] + fn test_date_from_isoymd_and_isoweekdate() { + for year in range_inclusive(2000i, 2400) { + for week in range_inclusive(1u, 53) { + for &weekday in [Mon, Tue, Wed, Thu, Fri, Sat, Sun].iter() { + let d = Date::from_isoywd(year, week, weekday); + if d.is_some() { + let d = d.unwrap(); + assert_eq!(d.weekday(), weekday); + let (year_, week_, weekday_) = d.isoweekdate(); + assert_eq!(year_, year); + assert_eq!(week_, week); + assert_eq!(weekday_, weekday); + } + } + } + } + + for year in range_inclusive(2000i, 2400) { + for month in range_inclusive(1u, 12) { + for day in range_inclusive(1u, 31) { + let d = Date::from_ymd(year, month, day); + if d.is_some() { + let d = d.unwrap(); + let (year_, week_, weekday_) = d.isoweekdate(); + let d_ = Date::from_isoywd(year_, week_, weekday_).unwrap(); + assert_eq!(d, d_); + } + } + } + } + } + + #[test] + fn test_date_fields() { + fn check(year: int, month: uint, day: uint, ordinal: uint) { + let d1 = Date::from_ymd(year, month, day); + assert!(d1.is_some()); + assert_eq!(d1.unwrap().year(), year); + assert_eq!(d1.unwrap().month(), month); + assert_eq!(d1.unwrap().day(), day); + assert_eq!(d1.unwrap().ordinal(), ordinal); + + let d2 = Date::from_yo(year, ordinal); + assert!(d2.is_some()); + assert_eq!(d2.unwrap().year(), year); + assert_eq!(d2.unwrap().month(), month); + assert_eq!(d2.unwrap().day(), day); + assert_eq!(d2.unwrap().ordinal(), ordinal); + + assert_eq!(d1, d2); + } + + check(2012, 1, 1, 1); + check(2012, 1, 2, 2); + check(2012, 2, 1, 32); + check(2012, 2, 29, 60); + check(2012, 3, 1, 61); + check(2012, 4, 9, 100); + check(2012, 7, 18, 200); + check(2012, 10, 26, 300); + check(2012, 12, 31, 366); + + check(2014, 1, 1, 1); + check(2014, 1, 2, 2); + check(2014, 2, 1, 32); + check(2014, 2, 28, 59); + check(2014, 3, 1, 60); + check(2014, 4, 10, 100); + check(2014, 7, 19, 200); + check(2014, 10, 27, 300); + check(2014, 12, 31, 365); + } + + #[test] + fn test_date_weekday() { + assert_eq!(Date::from_ymd(1582, 10, 15).unwrap().weekday(), Fri); + assert_eq!(Date::from_ymd(1875, 5, 20).unwrap().weekday(), Thu); // ISO 8601 reference date + assert_eq!(Date::from_ymd(2000, 1, 1).unwrap().weekday(), Sat); + } + + #[test] + fn test_date_with_fields() { + let d = Date::from_ymd(2000, 2, 29).unwrap(); + assert_eq!(d.with_year(-400), Date::from_ymd(-400, 2, 29)); + assert_eq!(d.with_year(-100), None); + assert_eq!(d.with_year(1600), Date::from_ymd(1600, 2, 29)); + assert_eq!(d.with_year(1900), None); + assert_eq!(d.with_year(2000), Date::from_ymd(2000, 2, 29)); + assert_eq!(d.with_year(2001), None); + assert_eq!(d.with_year(2004), Date::from_ymd(2004, 2, 29)); + assert_eq!(d.with_year(int::MAX), None); + + let d = Date::from_ymd(2000, 4, 30).unwrap(); + assert_eq!(d.with_month(0), None); + assert_eq!(d.with_month(1), Date::from_ymd(2000, 1, 30)); + assert_eq!(d.with_month(2), None); + assert_eq!(d.with_month(3), Date::from_ymd(2000, 3, 30)); + assert_eq!(d.with_month(4), Date::from_ymd(2000, 4, 30)); + assert_eq!(d.with_month(12), Date::from_ymd(2000, 12, 30)); + assert_eq!(d.with_month(13), None); + assert_eq!(d.with_month(uint::MAX), None); + + let d = Date::from_ymd(2000, 2, 8).unwrap(); + assert_eq!(d.with_day(0), None); + assert_eq!(d.with_day(1), Date::from_ymd(2000, 2, 1)); + assert_eq!(d.with_day(29), Date::from_ymd(2000, 2, 29)); + assert_eq!(d.with_day(30), None); + assert_eq!(d.with_day(uint::MAX), None); + + let d = Date::from_ymd(2000, 5, 5).unwrap(); + assert_eq!(d.with_ordinal(0), None); + assert_eq!(d.with_ordinal(1), Date::from_ymd(2000, 1, 1)); + assert_eq!(d.with_ordinal(60), Date::from_ymd(2000, 2, 29)); + assert_eq!(d.with_ordinal(61), Date::from_ymd(2000, 3, 1)); + assert_eq!(d.with_ordinal(366), Date::from_ymd(2000, 12, 31)); + assert_eq!(d.with_ordinal(367), None); + assert_eq!(d.with_ordinal(uint::MAX), None); + } + + #[test] + fn test_date_ndays_from_ce() { + assert_eq!(Date::from_ymd(1, 1, 1).unwrap().ndays_from_ce(), 1); + + for year in range_inclusive(-9999i, 10000) { + assert_eq!(Date::from_ymd(year, 1, 1).unwrap().ndays_from_ce(), + Date::from_ymd(year - 1, 12, 31).unwrap().ndays_from_ce() + 1); + } + } +} + +/** + * The internal implementation of the calendar and ordinal date. + * + * The current implementation is optimized for determining year, month, day and day of week. + * 4-bit `YearFlags` map to one of 14 possible classes of year in the Gregorian calendar, + * which are included in every packed `Date` instance. + * The conversion between the packed calendar date (`Mdf`) and the ordinal date (`Of`) is + * based on the moderately-sized lookup table (~1.5KB) + * and the packed representation is chosen for the efficient lookup. + * Every internal data structure does not validate its input, + * but the conversion keeps the valid value valid and the invalid value invalid + * so that the user-facing `Date` can validate the input as late as possible. + */ +mod internals { + use std::{i32, num, fmt}; + pub use super::{Weekday, Sun, Mon, Tue, Wed, Thu, Fri, Sat}; + + /// The internal date representation. This also includes the packed `Mdf` value. + pub type DateImpl = i32; + + pub static MAX_YEAR: DateImpl = i32::MAX >> 13; + pub static MIN_YEAR: DateImpl = i32::MIN >> 13; + + /// The year flags (aka the dominical letter). + /// + /// There are 14 possible classes of year in the Gregorian calendar: + /// common and leap years starting with Monday through Sunday. + /// The `YearFlags` stores this information into 4 bits `abbb`, + /// where `a` is `1` for the common year (simplifies the `Of` validation) + /// and `bbb` is a non-zero `Weekday` (mapping `Mon` to 7) of the last day in the past year + /// (simplifies the day of week calculation from the 1-based ordinal). + #[deriving(Eq, TotalEq)] + pub struct YearFlags(u8); + + pub static A: YearFlags = YearFlags(0o15); pub static AG: YearFlags = YearFlags(0o05); + pub static B: YearFlags = YearFlags(0o14); pub static BA: YearFlags = YearFlags(0o04); + pub static C: YearFlags = YearFlags(0o13); pub static CB: YearFlags = YearFlags(0o03); + pub static D: YearFlags = YearFlags(0o12); pub static DC: YearFlags = YearFlags(0o02); + pub static E: YearFlags = YearFlags(0o11); pub static ED: YearFlags = YearFlags(0o01); + pub static F: YearFlags = YearFlags(0o17); pub static FE: YearFlags = YearFlags(0o07); + pub static G: YearFlags = YearFlags(0o16); pub static GF: YearFlags = YearFlags(0o06); + + static YEAR_TO_FLAGS: [YearFlags, ..400] = [ + BA, G, F, E, DC, B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, + ED, C, B, A, GF, E, D, C, BA, G, F, E, DC, B, A, G, FE, D, C, B, + AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, E, + DC, B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, + GF, E, D, C, BA, G, F, E, DC, B, A, G, FE, D, C, B, AG, F, E, D, // 100 + C, B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, + GF, E, D, C, BA, G, F, E, DC, B, A, G, FE, D, C, B, AG, F, E, D, + CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, E, DC, B, A, G, + FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, + BA, G, F, E, DC, B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, // 200 + E, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, + BA, G, F, E, DC, B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, + ED, C, B, A, GF, E, D, C, BA, G, F, E, DC, B, A, G, FE, D, C, B, + AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, E, + DC, B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, // 300 + G, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, E, + DC, B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, + GF, E, D, C, BA, G, F, E, DC, B, A, G, FE, D, C, B, AG, F, E, D, + CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, E, DC, B, A, G, + FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, // 400 + ]; + + impl YearFlags { + #[inline] + pub fn from_year(year: int) -> YearFlags { + let mut year = year % 400; + if year < 0 { year += 400; } + YEAR_TO_FLAGS[year] + } + + #[inline] + pub fn ndays(&self) -> uint { + let YearFlags(flags) = *self; + 366 - (flags >> 3) as uint + } + + #[inline] + pub fn isoweek_delta(&self) -> uint { + let YearFlags(flags) = *self; + let mut delta = flags as uint & 0b111; + if delta < 3 { delta += 7; } + delta + } + + #[inline] + pub fn nisoweeks(&self) -> uint { + let YearFlags(flags) = *self; + 52 + ((0b00000100_00000110 >> flags as uint) & 1) + } + } + + impl fmt::Show for YearFlags { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let YearFlags(flags) = *self; + match flags { + 0o15 => "A".fmt(f), 0o05 => "AG".fmt(f), + 0o14 => "B".fmt(f), 0o04 => "BA".fmt(f), + 0o13 => "C".fmt(f), 0o03 => "CB".fmt(f), + 0o12 => "D".fmt(f), 0o02 => "DC".fmt(f), + 0o11 => "E".fmt(f), 0o01 => "ED".fmt(f), + 0o10 => "F?".fmt(f), 0o00 => "FE?".fmt(f), // non-canonical + 0o17 => "F".fmt(f), 0o07 => "FE".fmt(f), + 0o16 => "G".fmt(f), 0o06 => "GF".fmt(f), + _ => write!(f.buf, "YearFlags({})", flags), + } + } + } + + static MIN_OL: uint = 1 << 1; + static MAX_OL: uint = 366 << 1; // larger than the non-leap last day `(365 << 1) | 1` + static MIN_MDL: uint = (1 << 6) | (1 << 1); + static MAX_MDL: uint = (12 << 6) | (31 << 1) | 1; + + static XX: i8 = -128; + static MDL_TO_OL: [i8, ..MAX_MDL+1] = [ + XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, + XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, + XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, + XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, // 0 + XX, XX, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, // 1 + XX, XX, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, + 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, + 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, + 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, XX, XX, XX, XX, XX, // 2 + XX, XX, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, + 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, + 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, + 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, // 3 + XX, XX, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, + 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, + 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, + 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, XX, XX, // 4 + XX, XX, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, + 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, + 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, + 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, // 5 + XX, XX, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, + 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, + 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, + 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, XX, XX, // 6 + XX, XX, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, + 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, + 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, + 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, // 7 + XX, XX, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, + 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, + 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, + 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, // 8 + XX, XX, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, + 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, + 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, + 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, XX, XX, // 9 + XX, XX, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, + 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, + 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, + 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, // 10 + XX, XX, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, + 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, + 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, + 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, XX, XX, // 11 + XX, XX, 98,100, 98,100, 98,100, 98,100, 98,100, 98,100, 98,100, + 98,100, 98,100, 98,100, 98,100, 98,100, 98,100, 98,100, 98,100, + 98,100, 98,100, 98,100, 98,100, 98,100, 98,100, 98,100, 98,100, + 98,100, 98,100, 98,100, 98,100, 98,100, 98,100, 98,100, 98,100, // 12 + ]; + + static OL_TO_MDL: [u8, ..MAX_OL+1] = [ + 0, 0, // 0 + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, // 1 + 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, + 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, + 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, + 66, 66, 66, 66, 66, 66, 66, 66, 66, // 2 + 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, + 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, + 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, + 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, 74, 72, // 3 + 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, + 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, + 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, + 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, 76, 74, // 4 + 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, + 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, + 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, + 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, 80, 78, // 5 + 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, + 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, + 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, + 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, 82, 80, // 6 + 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, + 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, + 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, + 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, 86, 84, // 7 + 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, + 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, + 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, + 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, 88, 86, // 8 + 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, + 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, + 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, + 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, 90, 88, // 9 + 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, + 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, + 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, + 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, 94, 92, // 10 + 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, + 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, + 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, + 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, 96, 94, // 11 + 100, 98,100, 98,100, 98,100, 98,100, 98,100, 98,100, 98,100, + 98,100, 98,100, 98,100, 98,100, 98,100, 98,100, 98,100, 98,100, + 98,100, 98,100, 98,100, 98,100, 98,100, 98,100, 98,100, 98,100, + 98,100, 98,100, 98,100, 98,100, 98,100, 98,100, 98,100, 98, // 12 + ]; + + /// Ordinal (day of year) and year flags: `(ordinal << 4) | flags`. + /// + /// The whole bits except for the least 3 bits are referred as `Ol` (ordinal and leap flag), + /// which is an index to the `OL_TO_MDL` lookup table. + #[deriving(Eq, Ord)] + pub struct Of(uint); + + impl Of { + #[inline] + fn clamp_ordinal(ordinal: uint) -> uint { + if ordinal > 366 {0} else {ordinal} + } + + #[inline] + pub fn new(ordinal: uint, YearFlags(flags): YearFlags) -> Of { + let ordinal = Of::clamp_ordinal(ordinal); + Of((ordinal << 4) | (flags as uint)) + } + + #[inline] + pub fn from_mdf(Mdf(mdf): Mdf) -> Of { + let mdl = mdf >> 3; + match MDL_TO_OL.get(mdl) { + Some(&v) => Of(mdf - ((v as int as uint & 0x3ff) << 3)), + None => Of(0) + } + } + + #[inline] + pub fn valid(&self) -> bool { + let Of(of) = *self; + let ol = of >> 3; + ol - MIN_OL <= MAX_OL - MIN_OL + } + + #[inline] + pub fn ordinal(&self) -> uint { + let Of(of) = *self; + (of >> 4) as uint + } + + #[inline] + pub fn with_ordinal(&self, ordinal: uint) -> Of { + let ordinal = Of::clamp_ordinal(ordinal); + let Of(of) = *self; + Of((of & 0b1111) | (ordinal << 4)) + } + + #[inline] + pub fn flags(&self) -> YearFlags { + let Of(of) = *self; + YearFlags((of & 0b1111) as u8) + } + + #[inline] + pub fn with_flags(&self, YearFlags(flags): YearFlags) -> Of { + let Of(of) = *self; + Of((of & !0b1111) | (flags as uint)) + } + + #[inline] + pub fn weekday(&self) -> Weekday { + let Of(of) = *self; + num::from_uint(((of >> 4) + (of & 0b111)) % 7).unwrap() + } + + #[inline] + pub fn isoweekdate_raw(&self) -> (uint, Weekday) { + // week ordinal = ordinal + delta + let Of(of) = *self; + let weekord = (of >> 4) + self.flags().isoweek_delta(); + (weekord / 7, num::from_uint(weekord % 7).unwrap()) + } + + #[inline] + pub fn to_mdf(&self) -> Mdf { + Mdf::from_of(*self) + } + } + + impl fmt::Show for Of { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let Of(of) = *self; + write!(f.buf, "Of(({} << 4) | {:#04o} /*{}*/)", + of >> 4, of & 0b1111, YearFlags((of & 0b1111) as u8)) + } + } + + /// Month, day of month and year flags: `(month << 9) | (day << 4) | flags` + /// + /// The whole bits except for the least 3 bits are referred as `Mdl` + /// (month, day of month and leap flag), + /// which is an index to the `MDL_TO_OL` lookup table. + #[deriving(Eq, Ord)] + pub struct Mdf(uint); + + impl Mdf { + #[inline] + fn clamp_month(month: uint) -> uint { + if month > 12 {0} else {month} + } + + #[inline] + fn clamp_day(day: uint) -> uint { + if day > 31 {0} else {day} + } + + #[inline] + pub fn new(month: uint, day: uint, YearFlags(flags): YearFlags) -> Mdf { + let month = Mdf::clamp_month(month); + let day = Mdf::clamp_day(day); + Mdf((month << 9) | (day << 4) | (flags as uint)) + } + + #[inline] + pub fn from_of(Of(of): Of) -> Mdf { + let ol = of >> 3; + match OL_TO_MDL.get(ol) { + Some(&v) => Mdf(of + (v as uint << 3)), + None => Mdf(0) + } + } + + #[inline] + pub fn valid(&self) -> bool { + let Mdf(mdf) = *self; + let mdl = mdf >> 3; + match MDL_TO_OL.get(mdl) { + Some(&v) => v >= 0, + None => false + } + } + + #[inline] + pub fn month(&self) -> uint { + let Mdf(mdf) = *self; + (mdf >> 9) as uint + } + + #[inline] + pub fn with_month(&self, month: uint) -> Mdf { + let month = Mdf::clamp_month(month); + let Mdf(mdf) = *self; + Mdf((mdf & 0b11111_1111) | (month << 9)) + } + + #[inline] + pub fn day(&self) -> uint { + let Mdf(mdf) = *self; + ((mdf >> 4) & 0b11111) as uint + } + + #[inline] + pub fn with_day(&self, day: uint) -> Mdf { + let day = Mdf::clamp_day(day); + let Mdf(mdf) = *self; + Mdf((mdf & !0b11111_0000) | (day << 4)) + } + + #[inline] + pub fn flags(&self) -> YearFlags { + let Mdf(mdf) = *self; + YearFlags((mdf & 0b1111) as u8) + } + + #[inline] + pub fn with_flags(&self, YearFlags(flags): YearFlags) -> Mdf { + let Mdf(mdf) = *self; + Mdf((mdf & !0b1111) | (flags as uint)) + } + + #[inline] + pub fn to_of(&self) -> Of { + Of::from_mdf(*self) + } + } + + impl fmt::Show for Mdf { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let Mdf(mdf) = *self; + write!(f.buf, "Mdf(({} << 9) | ({} << 4) | {:#04o} /*{}*/)", + mdf >> 9, (mdf >> 4) & 0b11111, mdf & 0b1111, YearFlags((mdf & 0b1111) as u8)) + } + } + + #[cfg(test)] + mod tests { + extern crate test; + + use super::*; + use std::iter::range_inclusive; + use std::uint; + + static NONLEAP_FLAGS: [YearFlags, ..7] = [A, B, C, D, E, F, G]; + static LEAP_FLAGS: [YearFlags, ..7] = [AG, BA, CB, DC, ED, FE, GF]; + static FLAGS: [YearFlags, ..14] = [A, B, C, D, E, F, G, AG, BA, CB, DC, ED, FE, GF]; + + #[test] + fn test_year_flags_ndays_from_year() { + assert_eq!(YearFlags::from_year(2014).ndays(), 365); + assert_eq!(YearFlags::from_year(2012).ndays(), 366); + assert_eq!(YearFlags::from_year(2000).ndays(), 366); + assert_eq!(YearFlags::from_year(1900).ndays(), 365); + assert_eq!(YearFlags::from_year(1600).ndays(), 366); + assert_eq!(YearFlags::from_year( 1).ndays(), 365); + assert_eq!(YearFlags::from_year( 0).ndays(), 366); // 1 BCE (proleptic Gregorian) + assert_eq!(YearFlags::from_year( -1).ndays(), 365); // 2 BCE + assert_eq!(YearFlags::from_year( -4).ndays(), 366); // 5 BCE + assert_eq!(YearFlags::from_year( -99).ndays(), 365); // 100 BCE + assert_eq!(YearFlags::from_year(-100).ndays(), 365); // 101 BCE + assert_eq!(YearFlags::from_year(-399).ndays(), 365); // 400 BCE + assert_eq!(YearFlags::from_year(-400).ndays(), 366); // 401 BCE + } + + #[test] + fn test_year_flags_nisoweeks() { + assert_eq!(A.nisoweeks(), 52); + assert_eq!(B.nisoweeks(), 52); + assert_eq!(C.nisoweeks(), 52); + assert_eq!(D.nisoweeks(), 53); + assert_eq!(E.nisoweeks(), 52); + assert_eq!(F.nisoweeks(), 52); + assert_eq!(G.nisoweeks(), 52); + assert_eq!(AG.nisoweeks(), 52); + assert_eq!(BA.nisoweeks(), 52); + assert_eq!(CB.nisoweeks(), 52); + assert_eq!(DC.nisoweeks(), 53); + assert_eq!(ED.nisoweeks(), 53); + assert_eq!(FE.nisoweeks(), 52); + assert_eq!(GF.nisoweeks(), 52); + } + + #[bench] + fn bench_year_flags_from_year(bh: &mut test::BenchHarness) { + bh.iter(|| { + for year in range(-999i, 1000) { + YearFlags::from_year(year); + } + }); + } + + #[test] + fn test_of() { + fn check(expected: bool, flags: YearFlags, ordinal1: uint, ordinal2: uint) { + for ordinal in range_inclusive(ordinal1, ordinal2) { + let of = Of::new(ordinal, flags); + assert!(of.valid() == expected, + "ordinal {} = {} should be {} for dominical year {}", + ordinal, of, if expected {"valid"} else {"invalid"}, flags); + } + } + + for &flags in NONLEAP_FLAGS.iter() { + check(false, flags, 0, 0); + check(true, flags, 1, 365); + check(false, flags, 366, 1024); + check(false, flags, uint::MAX, uint::MAX); + } + + for &flags in LEAP_FLAGS.iter() { + check(false, flags, 0, 0); + check(true, flags, 1, 366); + check(false, flags, 367, 1024); + check(false, flags, uint::MAX, uint::MAX); + } + } + + #[test] + fn test_mdf_valid() { + fn check(expected: bool, flags: YearFlags, month1: uint, day1: uint, + month2: uint, day2: uint) { + for month in range_inclusive(month1, month2) { + for day in range_inclusive(day1, day2) { + let mdf = Mdf::new(month, day, flags); + assert!(mdf.valid() == expected, + "month {} day {} = {} should be {} for dominical year {}", + month, day, mdf, if expected {"valid"} else {"invalid"}, flags); + } + } + } + + for &flags in NONLEAP_FLAGS.iter() { + check(false, flags, 0, 0, 0, 1024); + check(false, flags, 0, 0, 16, 0); + check(true, flags, 1, 1, 1, 31); check(false, flags, 1, 32, 1, 1024); + check(true, flags, 2, 1, 2, 28); check(false, flags, 2, 29, 2, 1024); + check(true, flags, 3, 1, 3, 31); check(false, flags, 3, 32, 3, 1024); + check(true, flags, 4, 1, 4, 30); check(false, flags, 4, 31, 4, 1024); + check(true, flags, 5, 1, 5, 31); check(false, flags, 5, 32, 5, 1024); + check(true, flags, 6, 1, 6, 30); check(false, flags, 6, 31, 6, 1024); + check(true, flags, 7, 1, 7, 31); check(false, flags, 7, 32, 7, 1024); + check(true, flags, 8, 1, 8, 31); check(false, flags, 8, 32, 8, 1024); + check(true, flags, 9, 1, 9, 30); check(false, flags, 9, 31, 9, 1024); + check(true, flags, 10, 1, 10, 31); check(false, flags, 10, 32, 10, 1024); + check(true, flags, 11, 1, 11, 30); check(false, flags, 11, 31, 11, 1024); + check(true, flags, 12, 1, 12, 31); check(false, flags, 12, 32, 12, 1024); + check(false, flags, 13, 0, 16, 1024); + check(false, flags, uint::MAX, 0, uint::MAX, 1024); + check(false, flags, 0, uint::MAX, 16, uint::MAX); + check(false, flags, uint::MAX, uint::MAX, uint::MAX, uint::MAX); + } + + for &flags in LEAP_FLAGS.iter() { + check(false, flags, 0, 0, 0, 1024); + check(false, flags, 0, 0, 16, 0); + check(true, flags, 1, 1, 1, 31); check(false, flags, 1, 32, 1, 1024); + check(true, flags, 2, 1, 2, 29); check(false, flags, 2, 30, 2, 1024); + check(true, flags, 3, 1, 3, 31); check(false, flags, 3, 32, 3, 1024); + check(true, flags, 4, 1, 4, 30); check(false, flags, 4, 31, 4, 1024); + check(true, flags, 5, 1, 5, 31); check(false, flags, 5, 32, 5, 1024); + check(true, flags, 6, 1, 6, 30); check(false, flags, 6, 31, 6, 1024); + check(true, flags, 7, 1, 7, 31); check(false, flags, 7, 32, 7, 1024); + check(true, flags, 8, 1, 8, 31); check(false, flags, 8, 32, 8, 1024); + check(true, flags, 9, 1, 9, 30); check(false, flags, 9, 31, 9, 1024); + check(true, flags, 10, 1, 10, 31); check(false, flags, 10, 32, 10, 1024); + check(true, flags, 11, 1, 11, 30); check(false, flags, 11, 31, 11, 1024); + check(true, flags, 12, 1, 12, 31); check(false, flags, 12, 32, 12, 1024); + check(false, flags, 13, 0, 16, 1024); + check(false, flags, uint::MAX, 0, uint::MAX, 1024); + check(false, flags, 0, uint::MAX, 16, uint::MAX); + check(false, flags, uint::MAX, uint::MAX, uint::MAX, uint::MAX); + } + } + + #[test] + fn test_of_fields() { + for &flags in FLAGS.iter() { + for ordinal in range_inclusive(1u, 366) { + let of = Of::new(ordinal, flags); + if of.valid() { + assert_eq!(of.ordinal(), ordinal); + } + } + } + } + + #[test] + fn test_of_with_fields() { + fn check(flags: YearFlags, ordinal: uint) { + let of = Of::new(ordinal, flags); + + for ordinal in range_inclusive(0u, 1024) { + let of = of.with_ordinal(ordinal); + assert_eq!(of.valid(), Of::new(ordinal, flags).valid()); + if of.valid() { + assert_eq!(of.ordinal(), ordinal); + } + } + } + + for &flags in NONLEAP_FLAGS.iter() { + check(flags, 1); + check(flags, 365); + } + for &flags in LEAP_FLAGS.iter() { + check(flags, 1); + check(flags, 366); + } + } + + #[test] + fn test_of_weekday() { + assert_eq!(Of::new(1, A).weekday(), Sun); + assert_eq!(Of::new(1, B).weekday(), Sat); + assert_eq!(Of::new(1, C).weekday(), Fri); + assert_eq!(Of::new(1, D).weekday(), Thu); + assert_eq!(Of::new(1, E).weekday(), Wed); + assert_eq!(Of::new(1, F).weekday(), Tue); + assert_eq!(Of::new(1, G).weekday(), Mon); + assert_eq!(Of::new(1, AG).weekday(), Sun); + assert_eq!(Of::new(1, BA).weekday(), Sat); + assert_eq!(Of::new(1, CB).weekday(), Fri); + assert_eq!(Of::new(1, DC).weekday(), Thu); + assert_eq!(Of::new(1, ED).weekday(), Wed); + assert_eq!(Of::new(1, FE).weekday(), Tue); + assert_eq!(Of::new(1, GF).weekday(), Mon); + + for &flags in FLAGS.iter() { + let mut prev = Of::new(1, flags).weekday(); + for ordinal in range_inclusive(2u, flags.ndays()) { + let of = Of::new(ordinal, flags); + let expected = prev.succ(); + assert_eq!(of.weekday(), expected); + prev = expected; + } + } + } + + #[test] + fn test_mdf_fields() { + for &flags in FLAGS.iter() { + for month in range_inclusive(1u, 12) { + for day in range_inclusive(1u, 31) { + let mdf = Mdf::new(month, day, flags); + if mdf.valid() { + assert_eq!(mdf.month(), month); + assert_eq!(mdf.day(), day); + } + } + } + } + } + + #[test] + fn test_mdf_with_fields() { + fn check(flags: YearFlags, month: uint, day: uint) { + let mdf = Mdf::new(month, day, flags); + + for month in range_inclusive(0u, 16) { + let mdf = mdf.with_month(month); + assert_eq!(mdf.valid(), Mdf::new(month, day, flags).valid()); + if mdf.valid() { + assert_eq!(mdf.month(), month); + assert_eq!(mdf.day(), day); + } + } + + for day in range_inclusive(0u, 1024) { + let mdf = mdf.with_day(day); + assert_eq!(mdf.valid(), Mdf::new(month, day, flags).valid()); + if mdf.valid() { + assert_eq!(mdf.month(), month); + assert_eq!(mdf.day(), day); + } + } + } + + for &flags in NONLEAP_FLAGS.iter() { + check(flags, 1, 1); + check(flags, 1, 31); + check(flags, 2, 1); + check(flags, 2, 28); + check(flags, 2, 29); + check(flags, 12, 31); + } + for &flags in LEAP_FLAGS.iter() { + check(flags, 1, 1); + check(flags, 1, 31); + check(flags, 2, 1); + check(flags, 2, 29); + check(flags, 2, 30); + check(flags, 12, 31); + } + } + + #[test] + fn test_of_isoweekdate_raw() { + for &flags in FLAGS.iter() { + // January 4 should be in the first week + let (week, _) = Of::new(4 /* January 4 */, flags).isoweekdate_raw(); + assert_eq!(week, 1); + } + } + + #[test] + fn test_of_to_mdf() { + for i in range_inclusive(0u, 8192) { + let of = Of(i); + assert_eq!(of.valid(), of.to_mdf().valid()); + } + } + + #[test] + fn test_mdf_to_of() { + for i in range_inclusive(0u, 8192) { + let mdf = Mdf(i); + assert_eq!(mdf.valid(), mdf.to_of().valid()); + } + } + + #[test] + fn test_of_to_mdf_to_of() { + for i in range_inclusive(0u, 8192) { + let of = Of(i); + if of.valid() { + assert_eq!(of, of.to_mdf().to_of()); + } + } + } + + #[test] + fn test_mdf_to_of_to_mdf() { + for i in range_inclusive(0u, 8192) { + let mdf = Mdf(i); + if mdf.valid() { + assert_eq!(mdf, mdf.to_of().to_mdf()); + } + } + } + } +} + diff --git a/src/chrono/datetime.rs b/src/chrono/datetime.rs new file mode 100644 index 0000000..9e09824 --- /dev/null +++ b/src/chrono/datetime.rs @@ -0,0 +1,271 @@ +/*! + * ISO 8601 date and time. + */ + +use std::fmt; +use time::Time; +use date::{Date, Weekday}; + +#[deriving(Eq, TotalEq, Ord, TotalOrd, Hash)] +pub struct DateTime { + date: Date, + time: Time, +} + +impl DateTime { + #[inline] + pub fn new(date: Date, time: Time) -> DateTime { + DateTime { date: date, time: time } + } + + #[inline] + pub fn from_ymdhms(year: int, month: uint, day: uint, + hour: uint, min: uint, sec: uint) -> Option { + match (Date::from_ymd(year, month, day), Time::from_hms(hour, min, sec)) { + (Some(d), Some(t)) => Some(DateTime::new(d, t)), + (_, _) => None, + } + } + + #[inline] + pub fn from_yohms(year: int, ordinal: uint, + hour: uint, min: uint, sec: uint) -> Option { + match (Date::from_yo(year, ordinal), Time::from_hms(hour, min, sec)) { + (Some(d), Some(t)) => Some(DateTime::new(d, t)), + (_, _) => None, + } + } + + #[inline] + pub fn from_isoywdhms(year: int, week: uint, weekday: Weekday, + hour: uint, min: uint, sec: uint) -> Option { + match (Date::from_isoywd(year, week, weekday), Time::from_hms(hour, min, sec)) { + (Some(d), Some(t)) => Some(DateTime::new(d, t)), + (_, _) => None, + } + } + + /// Returns the year number. + #[inline] + pub fn year(&self) -> int { + self.date.year() + } + + /// Returns the absolute year number starting from 1 with a boolean flag, + /// which is false when the year predates the epoch (BCE/BC) and true otherwise (CE/AD). + #[inline] + pub fn year_ce(&self) -> (bool, uint) { + self.date.year_ce() + } + + /// Returns the month number starting from 1. + #[inline] + pub fn month(&self) -> uint { + self.date.month() + } + + /// Returns the month number starting from 0. + #[inline] + pub fn month0(&self) -> uint { + self.date.month0() + } + + /// Returns the day of month starting from 1. + #[inline] + pub fn day(&self) -> uint { + self.date.day() + } + + /// Returns the day of month starting from 0. + #[inline] + pub fn day0(&self) -> uint { + self.date.day0() + } + + /// Returns the day of year starting from 1. + #[inline] + pub fn ordinal(&self) -> uint { + self.date.ordinal() + } + + /// Returns the day of year starting from 0. + #[inline] + pub fn ordinal0(&self) -> uint { + self.date.ordinal0() + } + + /// Returns the day of week. + #[inline] + pub fn weekday(&self) -> Weekday { + self.date.weekday() + } + + /// Returns the ISO week date: an adjusted year, week number and day of week. + /// The adjusted year may differ from that of the calendar date. + #[inline] + pub fn isoweekdate(&self) -> (int, uint, Weekday) { + self.date.isoweekdate() + } + + /// Returns the hour number from 0 to 23. + #[inline] + pub fn hour(&self) -> uint { + self.time.hour() + } + + /// Returns the hour number from 1 to 12 with a boolean flag, + /// which is false for AM and true for PM. + #[inline] + pub fn hour12(&self) -> (bool, uint) { + self.time.hour12() + } + + /// Returns the minute number from 0 to 59. + #[inline] + pub fn minute(&self) -> uint { + self.time.minute() + } + + /// Returns the second number from 0 to 59. + #[inline] + pub fn second(&self) -> uint { + self.time.second() + } + + /// Returns the number of nanoseconds since the whole non-leap second. + /// The range from 1,000,000,000 to 1,999,999,999 represents the leap second. + #[inline] + pub fn nanosecond(&self) -> uint { + self.time.nanosecond() + } + + /// Makes a new `DateTime` with the year number changed. + /// + /// Returns `None` when the resulting `DateTime` would be invalid. + #[inline] + pub fn with_year(&self, year: int) -> Option { + self.date.with_year(year).map(|d| DateTime { date: d, ..*self }) + } + + /// Makes a new `DateTime` with the month number (starting from 1) changed. + /// + /// Returns `None` when the resulting `DateTime` would be invalid. + #[inline] + pub fn with_month(&self, month: uint) -> Option { + self.date.with_month(month).map(|d| DateTime { date: d, ..*self }) + } + + /// Makes a new `DateTime` with the month number (starting from 0) changed. + /// + /// Returns `None` when the resulting `DateTime` would be invalid. + #[inline] + pub fn with_month0(&self, month0: uint) -> Option { + self.date.with_month0(month0).map(|d| DateTime { date: d, ..*self }) + } + + /// Makes a new `DateTime` with the day of month (starting from 1) changed. + /// + /// Returns `None` when the resulting `DateTime` would be invalid. + #[inline] + pub fn with_day(&self, day: uint) -> Option { + self.date.with_day(day).map(|d| DateTime { date: d, ..*self }) + } + + /// Makes a new `DateTime` with the day of month (starting from 0) changed. + /// + /// Returns `None` when the resulting `DateTime` would be invalid. + #[inline] + pub fn with_day0(&self, day0: uint) -> Option { + self.date.with_day0(day0).map(|d| DateTime { date: d, ..*self }) + } + + /// Makes a new `DateTime` with the day of year (starting from 1) changed. + /// + /// Returns `None` when the resulting `DateTime` would be invalid. + #[inline] + pub fn with_ordinal(&self, ordinal: uint) -> Option { + self.date.with_ordinal(ordinal).map(|d| DateTime { date: d, ..*self }) + } + + /// Makes a new `DateTime` with the day of year (starting from 0) changed. + /// + /// Returns `None` when the resulting `DateTime` would be invalid. + #[inline] + pub fn with_ordinal0(&self, ordinal0: uint) -> Option { + self.date.with_ordinal0(ordinal0).map(|d| DateTime { date: d, ..*self }) + } + + /// Makes a new `Time` with the hour number changed. + /// + /// Returns `None` when the resulting `Time` would be invalid. + #[inline] + pub fn with_hour(&self, hour: uint) -> Option { + self.time.with_hour(hour).map(|t| DateTime { time: t, ..*self }) + } + + /// Makes a new `Time` with the minute number changed. + /// + /// Returns `None` when the resulting `Time` would be invalid. + #[inline] + pub fn with_minute(&self, min: uint) -> Option { + self.time.with_minute(min).map(|t| DateTime { time: t, ..*self }) + } + + /// Makes a new `Time` with the second number changed. + /// + /// Returns `None` when the resulting `Time` would be invalid. + #[inline] + pub fn with_second(&self, sec: uint) -> Option { + self.time.with_second(sec).map(|t| DateTime { time: t, ..*self }) + } + + /// Makes a new `Time` with nanoseconds since the whole non-leap second changed. + /// + /// Returns `None` when the resulting `Time` would be invalid. + #[inline] + pub fn with_nanosecond(&self, nano: uint) -> Option { + self.time.with_nanosecond(nano).map(|t| DateTime { time: t, ..*self }) + } + + /// Returns the number of days since January 1, 1 (Day 1) in the proleptic Gregorian calendar. + #[inline] + pub fn ndays_from_ce(&self) -> int { + self.date.ndays_from_ce() + } + + /// Returns the number of non-leap seconds past the last midnight. + #[inline] + pub fn nseconds_from_midnight(&self) -> uint { + self.time.nseconds_from_midnight() + } + + /// Returns the number of non-leap seconds since January 1, 1970 0:00:00. + /// Note that this does *not* account for the timezone! + #[inline] + pub fn nseconds_from_unix_epoch(&self) -> int { + (self.date.ndays_from_ce() - 719163) * 86400 + self.time.nseconds_from_midnight() as int + } +} + +impl fmt::Show for DateTime { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f.buf, "{}T{}", self.date, self.time) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_time_nseconds_from_unix_epoch() { + let to_timestamp = + |y,m,d,h,n,s| DateTime::from_ymdhms(y,m,d,h,n,s).unwrap().nseconds_from_unix_epoch(); + assert_eq!(to_timestamp(1969, 12, 31, 23, 59, 59), -1); + assert_eq!(to_timestamp(1970, 1, 1, 0, 0, 0), 0); + assert_eq!(to_timestamp(1970, 1, 1, 0, 0, 1), 1); + assert_eq!(to_timestamp(2001, 9, 9, 1, 46, 40), 1_000_000_000); + assert_eq!(to_timestamp(2038, 1, 19, 3, 14, 7), 0x7fffffff); + } +} + diff --git a/src/chrono/lib.rs b/src/chrono/lib.rs new file mode 100644 index 0000000..37ed50c --- /dev/null +++ b/src/chrono/lib.rs @@ -0,0 +1,9 @@ +#[crate_id = "chrono#0.1.0"]; +#[crate_type = "lib"]; + +#[feature(globs, macro_rules)]; + +pub mod date; +pub mod time; +pub mod datetime; + diff --git a/src/chrono/time.rs b/src/chrono/time.rs new file mode 100644 index 0000000..18bcb63 --- /dev/null +++ b/src/chrono/time.rs @@ -0,0 +1,161 @@ +/*! + * ISO 8601 time. + */ + +use std::fmt; + +/// ISO 8601 time. Allows for the nanosecond precision and optional leap second representation. +#[deriving(Eq, TotalEq, Ord, TotalOrd, Hash)] +pub struct Time { + priv hour: u8, + priv min: u8, + priv sec: u8, + priv frac: u32, +} + +impl Time { + /// Makes a new `Time` from hour, minute and second. + /// + /// Returns `None` on invalid hour, minute and/or second. + #[inline] + pub fn from_hms(hour: uint, min: uint, sec: uint) -> Option