// 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) } } }