//! epoch is seconds since 1 January, 1970, without leap seconds. //! //! year 0 is "1 BC", year -1 is "2 BC" and so on use errors::*; use std::fmt; fn is_leap_year(year: i16) -> bool { 0 == year % 4 && (0 != year % 100 || 0 == year % 400) } #[test] fn test_is_leap_year() { assert!(!is_leap_year(-500)); assert!(is_leap_year(-404)); assert!(is_leap_year(-400)); assert!(is_leap_year(-396)); assert!(!is_leap_year(-300)); assert!(!is_leap_year(-200)); assert!(!is_leap_year(-100)); assert!(is_leap_year(-4)); assert!(!is_leap_year(-3)); assert!(!is_leap_year(-1)); assert!(is_leap_year(0)); assert!(!is_leap_year(1)); assert!(!is_leap_year(3)); assert!(is_leap_year(4)); assert!(!is_leap_year(100)); assert!(!is_leap_year(200)); assert!(!is_leap_year(300)); assert!(is_leap_year(396)); assert!(is_leap_year(400)); assert!(is_leap_year(404)); assert!(!is_leap_year(500)); } fn month_day_of_year_since_march(month: u8) -> u16 { debug_assert!(month >= 1 && month <= 12); let month_from_march = if month > 2 { month as u16 - 3} else { month as u16 + 9 }; (153 * month_from_march + 2) / 5 } fn month_and_day_from_day_of_year_since_march(day_of_year: i32) -> (u8, u8) { let month_from_march = (5 * day_of_year + 2) / 153; debug_assert!(month_from_march >= 0 && month_from_march <= 11); let day = day_of_year - (153 * month_from_march + 2) / 5 + 1; debug_assert!(day >= 1 && day <= 31); let month_from_march = month_from_march as u8; let month = if month_from_march < 10 { month_from_march + 3 } else { month_from_march - 9 }; debug_assert!(month >= 1 && month <= 12); (month, day as u8) } #[test] fn test_month_day_of_year_since_march() { static MONTH_START: [u16; 12] = [306, 337, 0, 31, 61, 92, 122, 153, 184, 214, 245, 275, ]; static DAYS_IN_MONTHS: [u8; 12] = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; for (m, &s) in MONTH_START.iter().enumerate() { assert_eq!(month_day_of_year_since_march(m as u8 + 1), s); assert_eq!(month_and_day_from_day_of_year_since_march(s as i32), (m as u8 + 1, 1)); assert_eq!(month_and_day_from_day_of_year_since_march(s as i32 + DAYS_IN_MONTHS[m] as i32 - 1), (m as u8 + 1, DAYS_IN_MONTHS[m])); } assert_eq!(month_and_day_from_day_of_year_since_march(365), (2, 29)); } #[inline(always)] fn pos_div_rem(n: i32, pos_div: i32) -> (i32, i32) { // try to avoid overflows debug_assert!(pos_div > 0); let res = n / pos_div; let rem = n % pos_div; debug_assert_eq!(res * pos_div + rem, n); if rem < 0 { let rem = rem + pos_div; let res = res - 1; debug_assert_eq!(res * pos_div + rem, n); debug_assert!(rem >= 0 && rem < pos_div); (res, rem) } else { debug_assert!(rem >= 0 && rem < pos_div); (res, rem) } } #[inline(always)] fn pos_div_rem64(n: i64, pos_div: i64) -> (i64, i64) { // try to avoid overflows debug_assert!(pos_div > 0); let res = n / pos_div; let rem = n % pos_div; debug_assert_eq!(res * pos_div + rem, n); if rem < 0 { let rem = rem + pos_div; let res = res - 1; debug_assert_eq!(res * pos_div + rem, n); debug_assert!(rem >= 0 && rem < pos_div); (res, rem) } else { debug_assert!(rem >= 0 && rem < pos_div); (res, rem) } } // year 0 is era 0, year-of-era 0 // -> (ear, year_of_era) fn era_split_year(year: i32) -> (i32, i32) { pos_div_rem(year, 400) } #[test] fn test_era_split_year() { // -800...-401, -400...-1, 0...399, 400...799 assert_eq!(era_split_year(-801), (-3, 399)); assert_eq!(era_split_year(-800), (-2, 0)); assert_eq!(era_split_year(-799), (-2, 1)); assert_eq!(era_split_year(-401), (-2, 399)); assert_eq!(era_split_year(-400), (-1, 0)); assert_eq!(era_split_year(-399), (-1, 1)); assert_eq!(era_split_year(-1), (-1, 399)); assert_eq!(era_split_year(0), (0, 0)); assert_eq!(era_split_year(1), (0, 1)); assert_eq!(era_split_year(399), (0, 399)); assert_eq!(era_split_year(400), (1, 0)); assert_eq!(era_split_year(401), (1, 1)); assert_eq!(era_split_year(799), (1, 399)); assert_eq!(era_split_year(1970), (4, 370)); } // -> (era, day_of_era) fn split_days_since_march1_y0_into_era(days: i32) -> (i32, i32) { pos_div_rem(days, ERA_DAYS) } fn year_of_era_from_day_of_era(day_of_era: i32) -> i32 { let res = (day_of_era - day_of_era/1460 + day_of_era/36524 - day_of_era/146096) / 365; debug_assert!(res >= 0 && res <= 399); res } #[test] fn test_year_of_era_from_day_of_era() { for year_of_era in 0..399 { let ds = first_day_of_year_in_era_from_year_of_era(year_of_era); assert_eq!(year_of_era_from_day_of_era(ds + 0), year_of_era); assert_eq!(year_of_era_from_day_of_era(ds + 364), year_of_era); // 29. february counts to previous year; so if next ("real") year is leap year, // the current year_of_era has a leap day. if is_leap_year(year_of_era as i16 + 1) { assert_eq!(year_of_era_from_day_of_era(ds + 365), year_of_era); } } } const EPOCH_DAYS_SINCE_MARCH1_Y0: i32 = 719468; // there are 100 - 4 + 1 leap years in 400 years. const ERA_DAYS: i32 = 365 * 400 + 100 - 4 + 1; // 146097 fn first_day_of_year_in_era_from_year_of_era(year_of_era: i32) -> i32 { 365 * year_of_era + year_of_era / 4 - year_of_era / 100 } // days since march 1st in 1 BC (year "0") fn days_since_march1_y0(year: i16, month: u8, day: u8) -> i32 { // group 400 years as one era, but split between february and // march; count jan/feb to previous year, make march 1st start // of year let year = year as i32 - if month <= 2 { 1 } else { 0 }; let (era, year_of_era) = era_split_year(year); let day_of_year = month_day_of_year_since_march(month) + day as u16 - 1; let day_of_era = first_day_of_year_in_era_from_year_of_era(year_of_era) + day_of_year as i32; era as i32 * ERA_DAYS + day_of_era } fn split_days_since_march1_y0(days: i32) -> (i32, u8, u8) { let (era, day_of_era) = split_days_since_march1_y0_into_era(days); let year_of_era = year_of_era_from_day_of_era(day_of_era); let day_of_year = day_of_era - first_day_of_year_in_era_from_year_of_era(year_of_era); let (month, day) = month_and_day_from_day_of_year_since_march(day_of_year); let year = 400 * era + year_of_era + if month <= 2 { 1 } else { 0 }; (year as i32, month, day) } #[test] fn test_days_since_march1_y0() { assert_eq!(days_since_march1_y0(-1, 3, 1), -366); assert_eq!(days_since_march1_y0(0, 3, 1), 0); assert_eq!(days_since_march1_y0(1, 3, 1), 365); assert_eq!(days_since_march1_y0(1970, 1, 1), EPOCH_DAYS_SINCE_MARCH1_Y0); assert_eq!(days_since_march1_y0(1970, 1, 1), /* regular days in years: */ 365 * 1969 /* days from leap years: */ + (1969 / 4 - 15) /* days from march 1st year 0 to january 1st year 1: */ + 306); assert_eq!(split_days_since_march1_y0(-366), (-1, 3, 1)); assert_eq!(split_days_since_march1_y0(0), (0, 3, 1)); assert_eq!(split_days_since_march1_y0(365), (1, 3, 1)); assert_eq!(split_days_since_march1_y0(EPOCH_DAYS_SINCE_MARCH1_Y0), (1970, 1, 1)); } #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] pub struct Tm { pub year: i16, // absolute year: 1 is "1 AD", 0 is "1 BC". pub month: u8, // 01...12 pub day: u8, // 01..31 pub hour: u8, // 00..23 pub minute: u8, // 00..59 pub second: u8, // 00..59 } #[allow(unused)] impl Tm { pub fn from_epoch(epoch: i64) -> Result { let (day, time_of_day) = pos_div_rem64(epoch, 86400); let days_since_march1_y0 = day + EPOCH_DAYS_SINCE_MARCH1_Y0 as i64; ensure!((days_since_march1_y0 as i32) as i64 == days_since_march1_y0, "days in epoch out of range"); let days_since_march1_y0 = days_since_march1_y0 as i32; let (year, month, day) = split_days_since_march1_y0(days_since_march1_y0); ensure!((year as i16) as i32 == year, "year in epoch out of range"); let year = year as i16; let (minute_of_day, second) = pos_div_rem(time_of_day as i32, 60); let (hour, minute) = pos_div_rem(minute_of_day, 60); Ok(Tm{ year, month, day, hour: hour as u8, minute: minute as u8, second: second as u8, }) } fn days_since_epoch(&self) -> i32 { let days = days_since_march1_y0(self.year as i16, self.month, self.day); days - EPOCH_DAYS_SINCE_MARCH1_Y0 } fn day_seconds(&self) -> u32 { self.second as u32 + 60 * self.minute as u32 + 3600 * self.hour as u32 } pub fn epoch(&self) -> i64 { 86400 * self.days_since_epoch() as i64 + self.day_seconds() as i64 } #[allow(non_snake_case)] pub fn parse_YYYYMMDDHHmmSS(s: &str) -> Result { ensure!(s.len() == 14, "Tm string must be exactly 14 digits long"); ensure!(s.as_bytes().iter().all(|&b| b >= b'0' && b <= b'9'), "Tm string must be exactly 14 digits long"); let year = s[0..4].parse::()?; ensure!(year >= 1, "year must be >= 1"); ensure!(year <= 9999, "year must be <= 9999"); fn p(s: &str, min: u8, max: u8, name: &'static str) -> ::errors::Result { let v = s.parse::()?; ensure!(v >= min && v <= max, "{} {} out of range {}-{}", name, v, min, max); Ok(v) } let month = p(&s[4..6], 1, 12, "month")?; let day = p(&s[6..8], 1, 31, "day")?; let hour = p(&s[8..10], 0, 23, "hour")?; let minute = p(&s[10..12], 0, 59, "minute")?; let second = p(&s[12..14], 0, 59, "second")?; if 2 == month { ensure!(day < 30, "day {} out of range in february", day); ensure!(is_leap_year(year) || day < 29, "day {} out of range in february (not a leap year)", day); } else { static DAYS_IN_MONTHS: [u8; 12] = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; let max_days = DAYS_IN_MONTHS[month as usize - 1]; ensure!(day <= max_days, "day {} out of range for month {}", day, month); } Ok(Tm{ year, month, day, hour, minute, second }) } #[allow(non_snake_case)] pub fn format_YYYYMMDDHHmmSS(&self, f: &mut W) -> fmt::Result where W: fmt::Write + ?Sized { if self.year < 0 || self.year > 9999 { return Err(fmt::Error); } write!(f, "{:04}{:02}{:02}{:02}{:02}{:02}", self.year, self.month, self.day, self.hour, self.minute, self.second ) } }