diff --git a/src/date.rs b/src/date.rs index 66af09d..e5cd2b7 100644 --- a/src/date.rs +++ b/src/date.rs @@ -6,11 +6,12 @@ * ISO 8601 calendar date. */ -use std::{fmt, num}; +use std::{fmt, num, hash}; use num::Integer; use duration::Duration; +use offset::{Offset, UTC}; use time::TimeZ; -use datetime::DateTimeZ; +use datetime::{DateTimeZ, DateTime}; use self::internals::{DateImpl, Of, Mdf, YearFlags}; @@ -600,6 +601,264 @@ impl fmt::Show for DateZ { } } +/// ISO 8601 calendar date with timezone. +#[deriving(Clone)] +pub struct Date { + date: DateZ, + offset: Off, +} + +/// The minimum possible `Date`. +pub static MIN: Date = Date { date: MINZ, offset: UTC }; +/// The maximum possible `Date`. +pub static MAX: Date = Date { date: MAXZ, offset: UTC }; + +impl Date { + /// Makes a new `Date` with given *UTC* date and offset. + /// The local date should be constructed via the `Offset` trait. + #[inline] + pub fn from_utc(date: DateZ, offset: Off) -> Date { + Date { date: date, offset: offset } + } + + /// Makes a new `DateTimeZ` from the current date and given `TimeZ`. + /// The offset in the current date is preserved. + /// + /// Fails on invalid datetime. + #[inline] + pub fn and_time(&self, time: TimeZ) -> Option> { + self.offset.from_local_datetime(&self.date.and_time(time)).single() + } + + /// Makes a new `DateTimeZ` from the current date, hour, minute and second. + /// The offset in the current date is preserved. + /// + /// Fails on invalid hour, minute and/or second. + #[inline] + pub fn and_hms(&self, hour: u32, min: u32, sec: u32) -> DateTime { + self.and_hms_opt(hour, min, sec).expect("invalid time") + } + + /// Makes a new `DateTimeZ` from the current date, hour, minute and second. + /// The offset in the current date is preserved. + /// + /// Returns `None` on invalid hour, minute and/or second. + #[inline] + pub fn and_hms_opt(&self, hour: u32, min: u32, sec: u32) -> Option> { + TimeZ::from_hms_opt(hour, min, sec).and_then(|time| self.and_time(time)) + } + + /// Makes a new `DateTimeZ` from the current date, hour, minute, second and millisecond. + /// The millisecond part can exceed 1,000 in order to represent the leap second. + /// The offset in the current date is preserved. + /// + /// Fails on invalid hour, minute, second and/or millisecond. + #[inline] + pub fn and_hms_milli(&self, hour: u32, min: u32, sec: u32, milli: u32) -> DateTime { + self.and_hms_milli_opt(hour, min, sec, milli).expect("invalid time") + } + + /// Makes a new `DateTimeZ` from the current date, hour, minute, second and millisecond. + /// The millisecond part can exceed 1,000 in order to represent the leap second. + /// The offset in the current date is preserved. + /// + /// Returns `None` on invalid hour, minute, second and/or millisecond. + #[inline] + pub fn and_hms_milli_opt(&self, hour: u32, min: u32, sec: u32, + milli: u32) -> Option> { + TimeZ::from_hms_milli_opt(hour, min, sec, milli).and_then(|time| self.and_time(time)) + } + + /// Makes a new `DateTimeZ` from the current date, hour, minute, second and microsecond. + /// The microsecond part can exceed 1,000,000 in order to represent the leap second. + /// The offset in the current date is preserved. + /// + /// Fails on invalid hour, minute, second and/or microsecond. + #[inline] + pub fn and_hms_micro(&self, hour: u32, min: u32, sec: u32, micro: u32) -> DateTime { + self.and_hms_micro_opt(hour, min, sec, micro).expect("invalid time") + } + + /// Makes a new `DateTimeZ` from the current date, hour, minute, second and microsecond. + /// The microsecond part can exceed 1,000,000 in order to represent the leap second. + /// The offset in the current date is preserved. + /// + /// Returns `None` on invalid hour, minute, second and/or microsecond. + #[inline] + pub fn and_hms_micro_opt(&self, hour: u32, min: u32, sec: u32, + micro: u32) -> Option> { + TimeZ::from_hms_micro_opt(hour, min, sec, micro).and_then(|time| self.and_time(time)) + } + + /// Makes a new `DateTimeZ` from the current date, hour, minute, second and nanosecond. + /// The nanosecond part can exceed 1,000,000,000 in order to represent the leap second. + /// The offset in the current date is preserved. + /// + /// Fails on invalid hour, minute, second and/or nanosecond. + #[inline] + pub fn and_hms_nano(&self, hour: u32, min: u32, sec: u32, nano: u32) -> DateTime { + self.and_hms_nano_opt(hour, min, sec, nano).expect("invalid time") + } + + /// Makes a new `DateTimeZ` from the current date, hour, minute, second and nanosecond. + /// The nanosecond part can exceed 1,000,000,000 in order to represent the leap second. + /// The offset in the current date is preserved. + /// + /// Returns `None` on invalid hour, minute, second and/or nanosecond. + #[inline] + pub fn and_hms_nano_opt(&self, hour: u32, min: u32, sec: u32, + nano: u32) -> Option> { + TimeZ::from_hms_nano_opt(hour, min, sec, nano).and_then(|time| self.and_time(time)) + } + + /// Makes a new `Date` for the next date. + /// + /// Fails when `self` is the last representable date. + #[inline] + pub fn succ(&self) -> Date { + self.succ_opt().expect("out of bound") + } + + /// Makes a new `Date` for the next date. + /// + /// Returns `None` when `self` is the last representable date. + #[inline] + pub fn succ_opt(&self) -> Option> { + self.date.succ_opt().map(|date| Date::from_utc(date, self.offset.clone())) + } + + /// Makes a new `Date` for the prior date. + /// + /// Fails when `self` is the first representable date. + #[inline] + pub fn pred(&self) -> Date { + self.pred_opt().expect("out of bound") + } + + /// Makes a new `Date` for the prior date. + /// + /// Returns `None` when `self` is the first representable date. + #[inline] + pub fn pred_opt(&self) -> Option> { + self.date.pred_opt().map(|date| Date::from_utc(date, self.offset.clone())) + } + + /// Returns a view to the local date. + fn local(&self) -> DateZ { + self.offset.to_local_date(&self.date) + } +} + +impl Datelike for Date { + #[inline] fn year(&self) -> i32 { self.local().year() } + #[inline] fn month(&self) -> u32 { self.local().month() } + #[inline] fn month0(&self) -> u32 { self.local().month0() } + #[inline] fn day(&self) -> u32 { self.local().day() } + #[inline] fn day0(&self) -> u32 { self.local().day0() } + #[inline] fn ordinal(&self) -> u32 { self.local().ordinal() } + #[inline] fn ordinal0(&self) -> u32 { self.local().ordinal0() } + #[inline] fn weekday(&self) -> Weekday { self.local().weekday() } + #[inline] fn isoweekdate(&self) -> (i32, u32, Weekday) { self.local().isoweekdate() } + + #[inline] + fn with_year(&self, year: i32) -> Option> { + self.local().with_year(year) + .and_then(|date| self.offset.from_local_date(&date).single()) + } + + #[inline] + fn with_month(&self, month: u32) -> Option> { + self.local().with_month(month) + .and_then(|date| self.offset.from_local_date(&date).single()) + } + + #[inline] + fn with_month0(&self, month0: u32) -> Option> { + self.local().with_month0(month0) + .and_then(|date| self.offset.from_local_date(&date).single()) + } + + #[inline] + fn with_day(&self, day: u32) -> Option> { + self.local().with_day(day) + .and_then(|date| self.offset.from_local_date(&date).single()) + } + + #[inline] + fn with_day0(&self, day0: u32) -> Option> { + self.local().with_day0(day0) + .and_then(|date| self.offset.from_local_date(&date).single()) + } + + #[inline] + fn with_ordinal(&self, ordinal: u32) -> Option> { + self.local().with_ordinal(ordinal) + .and_then(|date| self.offset.from_local_date(&date).single()) + } + + #[inline] + fn with_ordinal0(&self, ordinal0: u32) -> Option> { + self.local().with_ordinal0(ordinal0) + .and_then(|date| self.offset.from_local_date(&date).single()) + } +} + +impl num::Bounded for Date { + #[inline] fn min_value() -> Date { MIN } + #[inline] fn max_value() -> Date { MAX } +} + +impl PartialEq for Date { + fn eq(&self, other: &Date) -> bool { self.date == other.date } +} + +impl Eq for Date { +} + +impl Equiv> for Date { + fn equiv(&self, other: &Date) -> bool { self.date == other.date } +} + +impl PartialOrd for Date { + fn partial_cmp(&self, other: &Date) -> Option { + self.date.partial_cmp(&other.date) + } +} + +impl Ord for Date { + fn cmp(&self, other: &Date) -> Ordering { self.date.cmp(&other.date) } +} + +impl hash::Hash for Date { + fn hash(&self, state: &mut hash::sip::SipState) { self.date.hash(state) } +} + +impl Add> for Date { + fn add(&self, rhs: &Duration) -> Date { + Date { date: self.date + *rhs, offset: self.offset.clone() } + } +} + +/* +// Rust issue #7590, the current coherence checker can't handle multiple Add impls +impl Add,Date> for Duration { + #[inline] + fn add(&self, rhs: &Date) -> Date { rhs.add(self) } +} +*/ + +impl Sub,Duration> for Date { + fn sub(&self, rhs: &Date) -> Duration { + self.date - rhs.date + } +} + +impl fmt::Show for Date { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}{}", self.local(), self.offset) + } +} + #[cfg(test)] mod tests { use super::{Datelike, DateZ, MIN_YEAR, MAX_YEAR}; diff --git a/src/datetime.rs b/src/datetime.rs index b7dba96..35e9e1e 100644 --- a/src/datetime.rs +++ b/src/datetime.rs @@ -6,10 +6,11 @@ * ISO 8601 date and time. */ -use std::fmt; +use std::{fmt, hash}; +use offset::Offset; use duration::Duration; -use time::{Timelike, TimeZ}; -use date::{Datelike, DateZ, Weekday}; +use time::{Timelike, TimeZ, Time}; +use date::{Datelike, DateZ, Date, Weekday}; /// ISO 8601 combined date and time without timezone. #[deriving(PartialEq, Eq, PartialOrd, Ord, Clone, Hash)] @@ -224,6 +225,181 @@ impl fmt::Show for DateTimeZ { } } +/// ISO 8601 combined date and time with timezone. +#[deriving(Clone)] +pub struct DateTime { + datetime: DateTimeZ, + offset: Off, +} + +impl DateTime { + /// Makes a new `DateTime` with given *UTC* datetime and offset. + /// The local datetime should be constructed via the `Offset` trait. + #[inline] + pub fn from_utc(datetime: DateTimeZ, offset: Off) -> DateTime { + DateTime { datetime: datetime, offset: offset } + } + + /// Retrieves a date component. + #[inline] + pub fn date(&self) -> Date { + Date::from_utc(self.datetime.date().clone(), self.offset.clone()) + } + + /// Retrieves a time component. + #[inline] + pub fn time(&self) -> Time { + Time::from_utc(self.datetime.time().clone(), self.offset.clone()) + } + + /// Returns the number of non-leap seconds since January 1, 1970 0:00:00 UTC. + #[inline] + pub fn num_seconds_from_unix_epoch(&self) -> i64 { + self.datetime.num_seconds_from_unix_epoch() + } + + /// Returns a view to the local datetime. + fn local(&self) -> DateTimeZ { + self.offset.to_local_datetime(&self.datetime) + } +} + +impl Datelike for DateTime { + #[inline] fn year(&self) -> i32 { self.local().year() } + #[inline] fn month(&self) -> u32 { self.local().month() } + #[inline] fn month0(&self) -> u32 { self.local().month0() } + #[inline] fn day(&self) -> u32 { self.local().day() } + #[inline] fn day0(&self) -> u32 { self.local().day0() } + #[inline] fn ordinal(&self) -> u32 { self.local().ordinal() } + #[inline] fn ordinal0(&self) -> u32 { self.local().ordinal0() } + #[inline] fn weekday(&self) -> Weekday { self.local().weekday() } + #[inline] fn isoweekdate(&self) -> (i32, u32, Weekday) { self.local().isoweekdate() } + + #[inline] + fn with_year(&self, year: i32) -> Option> { + self.local().with_year(year) + .and_then(|datetime| self.offset.from_local_datetime(&datetime).single()) + } + + #[inline] + fn with_month(&self, month: u32) -> Option> { + self.local().with_month(month) + .and_then(|datetime| self.offset.from_local_datetime(&datetime).single()) + } + + #[inline] + fn with_month0(&self, month0: u32) -> Option> { + self.local().with_month0(month0) + .and_then(|datetime| self.offset.from_local_datetime(&datetime).single()) + } + + #[inline] + fn with_day(&self, day: u32) -> Option> { + self.local().with_day(day) + .and_then(|datetime| self.offset.from_local_datetime(&datetime).single()) + } + + #[inline] + fn with_day0(&self, day0: u32) -> Option> { + self.local().with_day0(day0) + .and_then(|datetime| self.offset.from_local_datetime(&datetime).single()) + } + + #[inline] + fn with_ordinal(&self, ordinal: u32) -> Option> { + self.local().with_ordinal(ordinal) + .and_then(|datetime| self.offset.from_local_datetime(&datetime).single()) + } + + #[inline] + fn with_ordinal0(&self, ordinal0: u32) -> Option> { + self.local().with_ordinal0(ordinal0) + .and_then(|datetime| self.offset.from_local_datetime(&datetime).single()) + } +} + +impl Timelike for DateTime { + #[inline] fn hour(&self) -> u32 { self.local().hour() } + #[inline] fn minute(&self) -> u32 { self.local().minute() } + #[inline] fn second(&self) -> u32 { self.local().second() } + #[inline] fn nanosecond(&self) -> u32 { self.local().nanosecond() } + + #[inline] + fn with_hour(&self, hour: u32) -> Option> { + self.local().with_hour(hour) + .and_then(|datetime| self.offset.from_local_datetime(&datetime).single()) + } + + #[inline] + fn with_minute(&self, min: u32) -> Option> { + self.local().with_minute(min) + .and_then(|datetime| self.offset.from_local_datetime(&datetime).single()) + } + + #[inline] + fn with_second(&self, sec: u32) -> Option> { + self.local().with_second(sec) + .and_then(|datetime| self.offset.from_local_datetime(&datetime).single()) + } + + #[inline] + fn with_nanosecond(&self, nano: u32) -> Option> { + self.local().with_nanosecond(nano) + .and_then(|datetime| self.offset.from_local_datetime(&datetime).single()) + } +} + +impl PartialEq for DateTime { + fn eq(&self, other: &DateTime) -> bool { self.datetime == other.datetime } +} + +impl Eq for DateTime { +} + +impl Equiv> for DateTime { + fn equiv(&self, other: &DateTime) -> bool { self.datetime == other.datetime } +} + +impl PartialOrd for DateTime { + fn partial_cmp(&self, other: &DateTime) -> Option { + self.datetime.partial_cmp(&other.datetime) + } +} + +impl Ord for DateTime { + fn cmp(&self, other: &DateTime) -> Ordering { self.datetime.cmp(&other.datetime) } +} + +impl hash::Hash for DateTime { + fn hash(&self, state: &mut hash::sip::SipState) { self.datetime.hash(state) } +} + +impl Add> for DateTime { + fn add(&self, rhs: &Duration) -> DateTime { + DateTime { datetime: self.datetime + *rhs, offset: self.offset.clone() } + } +} + +/* +// Rust issue #7590, the current coherence checker can't handle multiple Add impls +impl Add,DateTime> for Duration { + #[inline] + fn add(&self, rhs: &DateTime) -> DateTime { rhs.add(self) } +} +*/ + +impl Sub,Duration> for DateTime { + fn sub(&self, rhs: &DateTime) -> Duration { + self.datetime - rhs.datetime + } +} + +impl fmt::Show for DateTime { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}{}", self.local(), self.offset) + } +} + #[cfg(test)] mod tests { use super::DateTimeZ; @@ -268,5 +444,21 @@ mod tests { 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); } + + #[test] + #[allow(uppercase_variables)] + fn test_datetime_offset() { + use offset::{Offset, UTC, FixedOffset}; + let EDT = FixedOffset::east(4*60*60); + assert_eq!(UTC.ymd(2014, 5, 6).and_hms(7, 8, 9).to_string(), + "2014-05-06T07:08:09Z".to_string()); + assert_eq!(EDT.ymd(2014, 5, 6).and_hms(7, 8, 9).to_string(), + "2014-05-06T07:08:09+04:00".to_string()); + assert!(UTC.ymd(2014, 5, 6).and_hms(7, 8, 9).equiv(&EDT.ymd(2014, 5, 6).and_hms(11, 8, 9))); + assert_eq!(UTC.ymd(2014, 5, 6).and_hms(7, 8, 9) + Duration::seconds(3600 + 60 + 1), + UTC.ymd(2014, 5, 6).and_hms(8, 9, 10)); + assert_eq!(UTC.ymd(2014, 5, 6).and_hms(7, 8, 9) - EDT.ymd(2014, 5, 6).and_hms(10, 11, 12), + Duration::seconds(3600 - 3*60 - 3)); + } } diff --git a/src/lib.rs b/src/lib.rs index 45d2713..d0a961f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,7 @@ pub use time::{Timelike, TimeZ}; pub use datetime::DateTimeZ; pub mod duration; +pub mod offset; pub mod date; pub mod time; pub mod datetime; diff --git a/src/offset.rs b/src/offset.rs new file mode 100644 index 0000000..c52c6cc --- /dev/null +++ b/src/offset.rs @@ -0,0 +1,340 @@ +// This is a part of rust-chrono. +// Copyright (c) 2014, Kang Seonghoon. +// See README.md and LICENSE.txt for details. + +/*! + * Offsets from the local time to UTC. + */ + +use std::fmt; +use num::Integer; +use duration::Duration; +use date::{DateZ, Date, Weekday}; +use time::{TimeZ, Time}; +use datetime::{DateTimeZ, DateTime}; + +/// The conversion result from the local time to the timezone-aware datetime types. +pub enum LocalResult { + /// Given local time representation is invalid. + /// This can occur when, for example, the positive timezone transition. + NoResult, + /// Given local time representation has a single unique result. + Single(T), + /// Given local time representation has multiple results and thus ambiguous. + /// This can occur when, for example, the negative timezone transition. + Ambiguous(T /*min*/, T /*max*/), +} + +impl LocalResult { + /// Returns `Some` only when the conversion result is unique, or `None` otherwise. + pub fn single(self) -> Option { + match self { Single(t) => Some(t), _ => None } + } + + /// Returns `Some` for the earliest possible conversion result, or `None` if none. + pub fn earliest(self) -> Option { + match self { Single(t) | Ambiguous(t,_) => Some(t), _ => None } + } + + /// Returns `Some` for the latest possible conversion result, or `None` if none. + pub fn latest(self) -> Option { + match self { Single(t) | Ambiguous(_,t) => Some(t), _ => None } + } +} + +impl LocalResult { + /// Returns the single unique conversion result, or fails accordingly. + pub fn unwrap(self) -> T { + match self { + NoResult => fail!("No such local time"), + Single(t) => t, + Ambiguous(t1,t2) => fail!("Ambiguous local time, ranging from {} to {}", t1, t2), + } + } +} + +/// The offset from the local time to UTC. +pub trait Offset: Clone + fmt::Show { + /// Makes a new `Date` from year, month, day and the current offset. + /// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE. + /// + /// The offset normally does not affect the date (unless it is between UTC-24 and UTC+24), + /// but it will propagate to the `DateTime` values constructed via this date. + /// + /// Fails on the out-of-range date, invalid month and/or day. + fn ymd(&self, year: i32, month: u32, day: u32) -> Date { + self.ymd_opt(year, month, day).unwrap() + } + + /// Makes a new `Date` from year, month, day and the current offset. + /// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE. + /// + /// The offset normally does not affect the date (unless it is between UTC-24 and UTC+24), + /// but it will propagate to the `DateTime` values constructed via this date. + /// + /// Returns `None` on the out-of-range date, invalid month and/or day. + fn ymd_opt(&self, year: i32, month: u32, day: u32) -> LocalResult> { + match DateZ::from_ymd_opt(year, month, day) { + Some(d) => self.from_local_date(&d), + None => NoResult, + } + } + + /// Makes a new `Date` from year, day of year (DOY or "ordinal") and the current offset. + /// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE. + /// + /// The offset normally does not affect the date (unless it is between UTC-24 and UTC+24), + /// but it will propagate to the `DateTime` values constructed via this date. + /// + /// Fails on the out-of-range date and/or invalid DOY. + fn yo(&self, year: i32, ordinal: u32) -> Date { + self.yo_opt(year, ordinal).unwrap() + } + + /// Makes a new `Date` from year, day of year (DOY or "ordinal") and the current offset. + /// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE. + /// + /// The offset normally does not affect the date (unless it is between UTC-24 and UTC+24), + /// but it will propagate to the `DateTime` values constructed via this date. + /// + /// Returns `None` on the out-of-range date and/or invalid DOY. + fn yo_opt(&self, year: i32, ordinal: u32) -> LocalResult> { + match DateZ::from_yo_opt(year, ordinal) { + Some(d) => self.from_local_date(&d), + None => NoResult, + } + } + + /// Makes a new `Date` from ISO week date (year and week number), day of the week (DOW) and + /// the current offset. + /// 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. + /// + /// The offset normally does not affect the date (unless it is between UTC-24 and UTC+24), + /// but it will propagate to the `DateTime` values constructed via this date. + /// + /// Fails on the out-of-range date and/or invalid week number. + fn isoywd(&self, year: i32, week: u32, weekday: Weekday) -> Date { + self.isoywd_opt(year, week, weekday).unwrap() + } + + /// Makes a new `Date` from ISO week date (year and week number), day of the week (DOW) and + /// the current offset. + /// 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. + /// + /// The offset normally does not affect the date (unless it is between UTC-24 and UTC+24), + /// but it will propagate to the `DateTime` values constructed via this date. + /// + /// Returns `None` on the out-of-range date and/or invalid week number. + fn isoywd_opt(&self, year: i32, week: u32, weekday: Weekday) -> LocalResult> { + match DateZ::from_isoywd_opt(year, week, weekday) { + Some(d) => self.from_local_date(&d), + None => NoResult, + } + } + + /// Makes a new `Time` from hour, minute, second and the current offset. + /// + /// Fails on invalid hour, minute and/or second. + fn hms(&self, hour: u32, min: u32, sec: u32) -> Time { + self.hms_opt(hour, min, sec).unwrap() + } + + /// Makes a new `Time` from hour, minute, second and the current offset. + /// + /// Returns `None` on invalid hour, minute and/or second. + fn hms_opt(&self, hour: u32, min: u32, sec: u32) -> LocalResult> { + match TimeZ::from_hms_opt(hour, min, sec) { + Some(t) => self.from_local_time(&t), + None => NoResult, + } + } + + /// Makes a new `Time` from hour, minute, second, millisecond and the current offset. + /// The millisecond part can exceed 1,000 in order to represent the leap second. + /// + /// Fails on invalid hour, minute, second and/or millisecond. + fn hms_milli(&self, hour: u32, min: u32, sec: u32, milli: u32) -> Time { + self.hms_milli_opt(hour, min, sec, milli).unwrap() + } + + /// Makes a new `Time` from hour, minute, second, millisecond and the current offset. + /// The millisecond part can exceed 1,000 in order to represent the leap second. + /// + /// Returns `None` on invalid hour, minute, second and/or millisecond. + fn hms_milli_opt(&self, hour: u32, min: u32, sec: u32, milli: u32) -> LocalResult> { + match TimeZ::from_hms_milli_opt(hour, min, sec, milli) { + Some(t) => self.from_local_time(&t), + None => NoResult, + } + } + + /// Makes a new `Time` from hour, minute, second, microsecond and the current offset. + /// The microsecond part can exceed 1,000,000 in order to represent the leap second. + /// + /// Fails on invalid hour, minute, second and/or microsecond. + fn hms_micro(&self, hour: u32, min: u32, sec: u32, micro: u32) -> Time { + self.hms_micro_opt(hour, min, sec, micro).unwrap() + } + + /// Makes a new `Time` from hour, minute, second, microsecond and the current offset. + /// The microsecond part can exceed 1,000,000 in order to represent the leap second. + /// + /// Returns `None` on invalid hour, minute, second and/or microsecond. + fn hms_micro_opt(&self, hour: u32, min: u32, sec: u32, micro: u32) -> LocalResult> { + match TimeZ::from_hms_micro_opt(hour, min, sec, micro) { + Some(t) => self.from_local_time(&t), + None => NoResult, + } + } + + /// Makes a new `Time` from hour, minute, second, nanosecond and the current offset. + /// The nanosecond part can exceed 1,000,000,000 in order to represent the leap second. + /// + /// Fails on invalid hour, minute, second and/or nanosecond. + fn hms_nano(&self, hour: u32, min: u32, sec: u32, nano: u32) -> Time { + self.hms_nano_opt(hour, min, sec, nano).unwrap() + } + + /// Makes a new `Time` from hour, minute, second, nanosecond and the current offset. + /// 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. + fn hms_nano_opt(&self, hour: u32, min: u32, sec: u32, nano: u32) -> LocalResult> { + match TimeZ::from_hms_nano_opt(hour, min, sec, nano) { + Some(t) => self.from_local_time(&t), + None => NoResult, + } + } + + /// Converts the local `DateZ` to the timezone-aware `Date` if possible. + fn from_local_date(&self, local: &DateZ) -> LocalResult>; + + /// Converts the local `TimeZ` to the timezone-aware `Time` if possible. + fn from_local_time(&self, local: &TimeZ) -> LocalResult>; + + /// Converts the local `DateTimeZ` to the timezone-aware `DateTime` if possible. + fn from_local_datetime(&self, local: &DateTimeZ) -> LocalResult>; + + /// Converts the UTC `DateZ` to the local time. + /// The UTC is continuous and thus this cannot fail (but can give the duplicate local time). + fn to_local_date(&self, utc: &DateZ) -> DateZ; + + /// Converts the UTC `TimeZ` to the local time. + /// The UTC is continuous and thus this cannot fail (but can give the duplicate local time). + fn to_local_time(&self, utc: &TimeZ) -> TimeZ; + + /// Converts the UTC `DateTimeZ` to the local time. + /// The UTC is continuous and thus this cannot fail (but can give the duplicate local time). + fn to_local_datetime(&self, utc: &DateTimeZ) -> DateTimeZ; +} + +/// The UTC timescale. This is the most efficient offset when you don't need the local time. +#[deriving(Clone)] +pub struct UTC; + +impl Offset for UTC { + fn from_local_date(&self, local: &DateZ) -> LocalResult> { + Single(Date::from_utc(local.clone(), UTC)) + } + fn from_local_time(&self, local: &TimeZ) -> LocalResult> { + Single(Time::from_utc(local.clone(), UTC)) + } + fn from_local_datetime(&self, local: &DateTimeZ) -> LocalResult> { + Single(DateTime::from_utc(local.clone(), UTC)) + } + + fn to_local_date(&self, utc: &DateZ) -> DateZ { utc.clone() } + fn to_local_time(&self, utc: &TimeZ) -> TimeZ { utc.clone() } + fn to_local_datetime(&self, utc: &DateTimeZ) -> DateTimeZ { utc.clone() } +} + +impl fmt::Show for UTC { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Z") } +} + +/// The fixed offset, from UTC-23:59:59 to UTC+23:59:59. +#[deriving(Clone)] +pub struct FixedOffset { + local_minus_utc: i32, +} + +impl FixedOffset { + /// Makes a new `FixedOffset` for the Eastern Hemisphere with given timezone difference. + /// The negative `secs` means the Western Hemisphere. + /// + /// Fails on the out-of-bound `secs`. + pub fn east(secs: i32) -> FixedOffset { + FixedOffset::east_opt(secs).expect("FixedOffset::east out of bounds") + } + + /// Makes a new `FixedOffset` for the Eastern Hemisphere with given timezone difference. + /// The negative `secs` means the Western Hemisphere. + /// + /// Returns `None` on the out-of-bound `secs`. + pub fn east_opt(secs: i32) -> Option { + if -86400 < secs && secs < 86400 { + Some(FixedOffset { local_minus_utc: secs }) + } else { + None + } + } + + /// Makes a new `FixedOffset` for the Western Hemisphere with given timezone difference. + /// The negative `secs` means the Eastern Hemisphere. + /// + /// Fails on the out-of-bound `secs`. + pub fn west(secs: i32) -> FixedOffset { + FixedOffset::west_opt(secs).expect("FixedOffset::west out of bounds") + } + + /// Makes a new `FixedOffset` for the Western Hemisphere with given timezone difference. + /// The negative `secs` means the Eastern Hemisphere. + /// + /// Returns `None` on the out-of-bound `secs`. + pub fn west_opt(secs: i32) -> Option { + if -86400 < secs && secs < 86400 { + Some(FixedOffset { local_minus_utc: -secs }) + } else { + None + } + } +} + +impl Offset for FixedOffset { + fn from_local_date(&self, local: &DateZ) -> LocalResult> { + Single(Date::from_utc(local.clone(), self.clone())) + } + fn from_local_time(&self, local: &TimeZ) -> LocalResult> { + Single(Time::from_utc(*local + Duration::seconds(-self.local_minus_utc), self.clone())) + } + fn from_local_datetime(&self, local: &DateTimeZ) -> LocalResult> { + Single(DateTime::from_utc(*local + Duration::seconds(-self.local_minus_utc), self.clone())) + } + + fn to_local_date(&self, utc: &DateZ) -> DateZ { + utc.clone() + } + fn to_local_time(&self, utc: &TimeZ) -> TimeZ { + *utc + Duration::seconds(self.local_minus_utc) + } + fn to_local_datetime(&self, utc: &DateTimeZ) -> DateTimeZ { + *utc + Duration::seconds(self.local_minus_utc) + } +} + +impl fmt::Show for FixedOffset { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let offset = self.local_minus_utc; + let (sign, offset) = if offset < 0 {('-', -offset)} else {('+', offset)}; + let (mins, sec) = offset.div_mod_floor(&60); + let (hour, min) = mins.div_mod_floor(&60); + if sec == 0 { + write!(f, "{}{:02}:{:02}", sign, hour, min) + } else { + write!(f, "{}{:02}:{:02}:{:02}", sign, hour, min, sec) + } + } +} + diff --git a/src/time.rs b/src/time.rs index ea4c2d4..ad4a3fc 100644 --- a/src/time.rs +++ b/src/time.rs @@ -6,8 +6,9 @@ * ISO 8601 time. */ -use std::fmt; +use std::{fmt, hash}; use num::Integer; +use offset::Offset; use duration::Duration; /// The common set of methods for time component. @@ -254,6 +255,112 @@ impl fmt::Show for TimeZ { } } +/// ISO 8601 time with timezone. +#[deriving(Clone)] +pub struct Time { + time: TimeZ, + offset: Off, +} + +impl Time { + /// Makes a new `Time` with given *UTC* time and offset. + /// The local time should be constructed via the `Offset` trait. + #[inline] + pub fn from_utc(time: TimeZ, offset: Off) -> Time { + Time { time: time, offset: offset } + } + + /// Returns a view to the local time. + fn local(&self) -> TimeZ { + self.offset.to_local_time(&self.time) + } +} + +impl Timelike for Time { + #[inline] fn hour(&self) -> u32 { self.local().hour() } + #[inline] fn minute(&self) -> u32 { self.local().minute() } + #[inline] fn second(&self) -> u32 { self.local().second() } + #[inline] fn nanosecond(&self) -> u32 { self.local().nanosecond() } + + #[inline] + fn with_hour(&self, hour: u32) -> Option> { + self.local().with_hour(hour) + .and_then(|time| self.offset.from_local_time(&time).single()) + } + + #[inline] + fn with_minute(&self, min: u32) -> Option> { + self.local().with_minute(min) + .and_then(|time| self.offset.from_local_time(&time).single()) + } + + #[inline] + fn with_second(&self, sec: u32) -> Option> { + self.local().with_second(sec) + .and_then(|time| self.offset.from_local_time(&time).single()) + } + + #[inline] + fn with_nanosecond(&self, nano: u32) -> Option> { + self.local().with_nanosecond(nano) + .and_then(|time| self.offset.from_local_time(&time).single()) + } + + #[inline] + fn num_seconds_from_midnight(&self) -> u32 { self.local().num_seconds_from_midnight() } +} + +impl PartialEq for Time { + fn eq(&self, other: &Time) -> bool { self.time == other.time } +} + +impl Eq for Time { +} + +impl Equiv> for Time { + fn equiv(&self, other: &Time) -> bool { self.time == other.time } +} + +impl PartialOrd for Time { + fn partial_cmp(&self, other: &Time) -> Option { + self.time.partial_cmp(&other.time) + } +} + +impl Ord for Time { + fn cmp(&self, other: &Time) -> Ordering { self.time.cmp(&other.time) } +} + +impl hash::Hash for Time { + fn hash(&self, state: &mut hash::sip::SipState) { self.time.hash(state) } +} + +impl Add> for Time { + fn add(&self, rhs: &Duration) -> Time { + Time { time: self.time + *rhs, offset: self.offset.clone() } + } +} + +/* +// Rust issue #7590, the current coherence checker can't handle multiple Add impls +impl Add,Time> for Duration { + #[inline] + fn add(&self, rhs: &Time) -> Time { rhs.add(self) } +} +*/ + +impl Sub,Duration> for Time { + fn sub(&self, rhs: &Time) -> Duration { + self.time - rhs.time + } +} + +impl fmt::Show for Time { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}{}", self.local(), self.offset) + } +} + #[cfg(test)] mod tests { use super::{Timelike, TimeZ};