diff --git a/src/duration.rs b/src/duration.rs index ec9f46a..4e46245 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -442,12 +442,13 @@ mod tests { assert_eq!(Duration::zero(), Duration::zero()); assert!(Duration::zero() != Duration::seconds(1)); assert_eq!(Duration::seconds(1) + Duration::seconds(2), Duration::seconds(3)); - assert_eq!(Duration::seconds(86399) + Duration::seconds(4), - Duration::days(1) + Duration::seconds(3)); + assert_eq!(Duration::milliseconds(997) + Duration::milliseconds(15), + Duration::new(0, 1, 12_000_000)); + assert_eq!(Duration::seconds(86399) + Duration::seconds(4), Duration::new(1, 3, 0)); assert_eq!(Duration::days(10) - Duration::seconds(1000), Duration::seconds(863000)); assert_eq!(Duration::days(10) - Duration::seconds(1000000), Duration::seconds(-136000)); assert_eq!(Duration::days(2) + Duration::seconds(86399) + Duration::nanoseconds(1234567890), - Duration::days(3) + Duration::nanoseconds(234567890)); + Duration::new(3, 0, 234567890)); assert_eq!(-Duration::days(3), Duration::days(-3)); assert_eq!(-(Duration::days(3) + Duration::seconds(70)), Duration::days(-4) + Duration::seconds(86400-70)); diff --git a/src/lib.rs b/src/lib.rs index fe1e5f3..6f2be24 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,7 @@ Experimental date and time handling for Rust. #![deny(missing_doc)] extern crate num; +extern crate stdtime = "time"; pub use duration::Duration; pub use offset::{Offset, LocalResult}; diff --git a/src/naive/date.rs b/src/naive/date.rs index 4d13fda..7c34727 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -131,6 +131,28 @@ impl NaiveDate { } } + /// Makes a new `NaiveDate` from the number of days since January 1, 1 (Day 1) + /// in the proleptic Gregorian calendar. + /// + /// Fails on the out-of-range date. + #[inline] + pub fn from_num_days_from_ce(days: i32) -> NaiveDate { + NaiveDate::from_num_days_from_ce_opt(days).expect("out-of-range date") + } + + /// Makes a new `NaiveDate` from the number of days since January 1, 1 (Day 1) + /// in the proleptic Gregorian calendar. + /// + /// Returns `None` on the out-of-range date. + pub fn from_num_days_from_ce_opt(days: i32) -> Option { + let days = days + 365; // make January 1, 1 BCE equal to day 0 + let (year_div_400, cycle) = days.div_mod_floor(&146097); + let (year_mod_400, ordinal) = internals::cycle_to_yo(cycle as u32); + let flags = unsafe { YearFlags::from_year_mod_400(year_mod_400 as i32) }; + NaiveDate::from_of(year_div_400 * 400 + year_mod_400 as i32, + Of::new(ordinal, flags)) + } + /// Makes a new `NaiveDateTime` from the current date and given `NaiveTime`. #[inline] pub fn and_time(&self, time: NaiveTime) -> NaiveDateTime { @@ -409,7 +431,7 @@ mod tests { use {Sun, Mon, Tue, Wed, Thu, Fri, Sat}; use duration::Duration; use std::{i32, u32}; - use std::iter::range_inclusive; + use std::iter::{range_inclusive, range_step_inclusive}; #[test] fn test_date_from_ymd() { @@ -519,6 +541,37 @@ mod tests { } } + #[test] + fn test_date_from_num_days_from_ce() { + let from_ndays_from_ce = |days| NaiveDate::from_num_days_from_ce_opt(days); + assert_eq!(from_ndays_from_ce(1), Some(NaiveDate::from_ymd(1, 1, 1))); + assert_eq!(from_ndays_from_ce(2), Some(NaiveDate::from_ymd(1, 1, 2))); + assert_eq!(from_ndays_from_ce(31), Some(NaiveDate::from_ymd(1, 1, 31))); + assert_eq!(from_ndays_from_ce(32), Some(NaiveDate::from_ymd(1, 2, 1))); + assert_eq!(from_ndays_from_ce(59), Some(NaiveDate::from_ymd(1, 2, 28))); + assert_eq!(from_ndays_from_ce(60), Some(NaiveDate::from_ymd(1, 3, 1))); + assert_eq!(from_ndays_from_ce(365), Some(NaiveDate::from_ymd(1, 12, 31))); + assert_eq!(from_ndays_from_ce(365*1 + 1), Some(NaiveDate::from_ymd(2, 1, 1))); + assert_eq!(from_ndays_from_ce(365*2 + 1), Some(NaiveDate::from_ymd(3, 1, 1))); + assert_eq!(from_ndays_from_ce(365*3 + 1), Some(NaiveDate::from_ymd(4, 1, 1))); + assert_eq!(from_ndays_from_ce(365*4 + 2), Some(NaiveDate::from_ymd(5, 1, 1))); + assert_eq!(from_ndays_from_ce(146097 + 1), Some(NaiveDate::from_ymd(401, 1, 1))); + assert_eq!(from_ndays_from_ce(146097*5 + 1), Some(NaiveDate::from_ymd(2001, 1, 1))); + assert_eq!(from_ndays_from_ce(719163), Some(NaiveDate::from_ymd(1970, 1, 1))); + assert_eq!(from_ndays_from_ce(0), Some(NaiveDate::from_ymd(0, 12, 31))); // 1 BCE + assert_eq!(from_ndays_from_ce(-365), Some(NaiveDate::from_ymd(0, 1, 1))); + assert_eq!(from_ndays_from_ce(-366), Some(NaiveDate::from_ymd(-1, 12, 31))); // 2 BCE + + for days in range_step_inclusive(-999900i32, 1000000, 100) { + assert_eq!(from_ndays_from_ce(days).map(|d| d.num_days_from_ce()), Some(days)); + } + + assert_eq!(from_ndays_from_ce(MIN.num_days_from_ce()), Some(MIN)); + assert_eq!(from_ndays_from_ce(MIN.num_days_from_ce() - 1), None); + assert_eq!(from_ndays_from_ce(MAX.num_days_from_ce()), Some(MAX)); + assert_eq!(from_ndays_from_ce(MAX.num_days_from_ce() + 1), None); + } + #[test] fn test_date_fields() { fn check(year: i32, month: u32, day: u32, ordinal: u32) { diff --git a/src/naive/datetime.rs b/src/naive/datetime.rs index b0e8899..b6726f5 100644 --- a/src/naive/datetime.rs +++ b/src/naive/datetime.rs @@ -7,6 +7,7 @@ */ use std::fmt; +use num::Integer; use {Weekday, Timelike, Datelike}; use duration::Duration; @@ -28,6 +29,34 @@ impl NaiveDateTime { NaiveDateTime { date: date, time: time } } + /// Makes a new `NaiveDateTime` from the number of non-leap seconds + /// since January 1, 1970 0:00:00 UTC and the number of nanoseconds + /// since the last whole non-leap second. + /// + /// Fails on the out-of-range number of seconds and/or invalid nanosecond. + #[inline] + pub fn from_num_seconds_from_unix_epoch(secs: i64, nsecs: u32) -> NaiveDateTime { + let datetime = NaiveDateTime::from_num_seconds_from_unix_epoch_opt(secs, nsecs); + datetime.expect("invalid or out-of-range datetime") + } + + /// Makes a new `NaiveDateTime` from the number of non-leap seconds + /// since January 1, 1970 0:00:00 UTC and the number of nanoseconds + /// since the last whole non-leap second. + /// + /// Returns `None` on the out-of-range number of seconds and/or invalid nanosecond. + #[inline] + pub fn from_num_seconds_from_unix_epoch_opt(secs: i64, nsecs: u32) -> Option { + let (days, secs) = secs.div_mod_floor(&86400); + let date = days.to_i32().and_then(|days| days.checked_add(&719163)) + .and_then(|days_ce| NaiveDate::from_num_days_from_ce_opt(days_ce)); + let time = NaiveTime::from_num_seconds_from_midnight_opt(secs as u32, nsecs); + match (date, time) { + (Some(date), Some(time)) => Some(NaiveDateTime { date: date, time: time }), + (_, _) => None, + } + } + /// Retrieves a date component. #[inline] pub fn date(&self) -> NaiveDate { @@ -161,8 +190,23 @@ impl fmt::Show for NaiveDateTime { #[cfg(test)] mod tests { + use super::NaiveDateTime; use duration::Duration; use naive::date::NaiveDate; + use std::i64; + + #[test] + fn test_datetime_from_num_seconds_from_unix_epoch() { + let from_timestamp = |secs| NaiveDateTime::from_num_seconds_from_unix_epoch_opt(secs, 0); + let ymdhms = |y,m,d,h,n,s| NaiveDate::from_ymd(y,m,d).and_hms(h,n,s); + assert_eq!(from_timestamp(-1), Some(ymdhms(1969, 12, 31, 23, 59, 59))); + assert_eq!(from_timestamp(0), Some(ymdhms(1970, 1, 1, 0, 0, 0))); + assert_eq!(from_timestamp(1), Some(ymdhms(1970, 1, 1, 0, 0, 1))); + assert_eq!(from_timestamp(1_000_000_000), Some(ymdhms(2001, 9, 9, 1, 46, 40))); + assert_eq!(from_timestamp(0x7fffffff), Some(ymdhms(2038, 1, 19, 3, 14, 7))); + assert_eq!(from_timestamp(i64::MIN), None); + assert_eq!(from_timestamp(i64::MAX), None); + } #[test] fn test_datetime_add() { diff --git a/src/naive/time.rs b/src/naive/time.rs index bfc9d6a..a0c2939 100644 --- a/src/naive/time.rs +++ b/src/naive/time.rs @@ -89,12 +89,32 @@ impl NaiveTime { /// The nanosecond part can exceed 1,000,000,000 in order to represent the leap second. /// /// Returns `None` on invalid hour, minute, second and/or nanosecond. + #[inline] pub fn from_hms_nano_opt(hour: u32, min: u32, sec: u32, nano: u32) -> Option { if hour >= 24 || min >= 60 || sec >= 60 || nano >= 2_000_000_000 { return None; } let secs = hour * 3600 + min * 60 + sec; Some(NaiveTime { secs: secs, frac: nano }) } + /// Makes a new `NaiveTime` from the number of seconds since midnight and nanosecond. + /// The nanosecond part can exceed 1,000,000,000 in order to represent the leap second. + /// + /// Fails on invalid number of seconds and/or nanosecond. + #[inline] + pub fn from_num_seconds_from_midnight(secs: u32, nano: u32) -> NaiveTime { + NaiveTime::from_num_seconds_from_midnight_opt(secs, nano).expect("invalid time") + } + + /// Makes a new `NaiveTime` from the number of seconds since midnight and nanosecond. + /// The nanosecond part can exceed 1,000,000,000 in order to represent the leap second. + /// + /// Returns `None` on invalid number of seconds and/or nanosecond. + #[inline] + pub fn from_num_seconds_from_midnight_opt(secs: u32, nano: u32) -> Option { + if secs >= 86400 || nano >= 2_000_000_000 { return None; } + Some(NaiveTime { secs: secs, frac: nano }) + } + /// Returns a triple of the hour, minute and second numbers. fn hms(&self) -> (u32, u32, u32) { let (mins, sec) = self.secs.div_mod_floor(&60); diff --git a/src/offset.rs b/src/offset.rs index 4374e0f..f95e7e6 100644 --- a/src/offset.rs +++ b/src/offset.rs @@ -7,6 +7,7 @@ */ use std::fmt; +use stdtime; use num::Integer; use Weekday; @@ -239,6 +240,18 @@ pub trait Offset: Clone + fmt::Show { #[deriving(Clone)] pub struct UTC; +impl UTC { + /// Returns a `Date` which corresponds to the current date. + pub fn today() -> Date { UTC::now().date() } + + /// Returns a `DateTime` which corresponds to the current date. + pub fn now() -> DateTime { + let spec = stdtime::get_time(); + let naive = NaiveDateTime::from_num_seconds_from_unix_epoch(spec.sec, spec.nsec as u32); + DateTime::from_utc(naive, UTC) + } +} + impl Offset for UTC { fn from_local_date(&self, local: &NaiveDate) -> LocalResult> { Single(Date::from_utc(local.clone(), UTC))