// 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 stdtime; use num::Integer; use Weekday; use duration::Duration; use naive::date::NaiveDate; use naive::time::NaiveTime; use naive::datetime::NaiveDateTime; use date::Date; use time::Time; use datetime::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 NaiveDate::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 NaiveDate::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 NaiveDate::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 NaiveTime::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 NaiveTime::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 NaiveTime::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 NaiveTime::from_hms_nano_opt(hour, min, sec, nano) { Some(t) => self.from_local_time(&t), None => NoResult, } } /// Converts the local `NaiveDate` to the timezone-aware `Date` if possible. fn from_local_date(&self, local: &NaiveDate) -> LocalResult>; /// Converts the local `NaiveTime` to the timezone-aware `Time` if possible. fn from_local_time(&self, local: &NaiveTime) -> LocalResult>; /// Converts the local `NaiveDateTime` to the timezone-aware `DateTime` if possible. fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult>; /// Converts the UTC `NaiveDate` 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: &NaiveDate) -> NaiveDate; /// Converts the UTC `NaiveTime` 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: &NaiveTime) -> NaiveTime; /// Converts the UTC `NaiveDateTime` 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: &NaiveDateTime) -> NaiveDateTime; } /// The UTC timescale. This is the most efficient offset when you don't need the local time. #[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)) } fn from_local_time(&self, local: &NaiveTime) -> LocalResult> { Single(Time::from_utc(local.clone(), UTC)) } fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { Single(DateTime::from_utc(local.clone(), UTC)) } fn to_local_date(&self, utc: &NaiveDate) -> NaiveDate { utc.clone() } fn to_local_time(&self, utc: &NaiveTime) -> NaiveTime { utc.clone() } fn to_local_datetime(&self, utc: &NaiveDateTime) -> NaiveDateTime { 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: &NaiveDate) -> LocalResult> { Single(Date::from_utc(local.clone(), self.clone())) } fn from_local_time(&self, local: &NaiveTime) -> LocalResult> { Single(Time::from_utc(*local + Duration::seconds(-self.local_minus_utc), self.clone())) } fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { Single(DateTime::from_utc(*local + Duration::seconds(-self.local_minus_utc), self.clone())) } fn to_local_date(&self, utc: &NaiveDate) -> NaiveDate { utc.clone() } fn to_local_time(&self, utc: &NaiveTime) -> NaiveTime { *utc + Duration::seconds(self.local_minus_utc) } fn to_local_datetime(&self, utc: &NaiveDateTime) -> NaiveDateTime { *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) } } }