chrono/offset/
fixed.rs

1// This is a part of Chrono.
2// See README.md and LICENSE.txt for details.
3
4//! The time zone which has a fixed offset from UTC.
5
6use core::ops::{Add, Sub};
7use core::fmt;
8use oldtime::Duration as OldDuration;
9
10use Timelike;
11use div::div_mod_floor;
12use naive::{NaiveTime, NaiveDate, NaiveDateTime};
13use DateTime;
14use super::{TimeZone, Offset, LocalResult};
15
16/// The time zone with fixed offset, from UTC-23:59:59 to UTC+23:59:59.
17///
18/// Using the [`TimeZone`](./trait.TimeZone.html) methods
19/// on a `FixedOffset` struct is the preferred way to construct
20/// `DateTime<FixedOffset>` instances. See the [`east`](#method.east) and
21/// [`west`](#method.west) methods for examples.
22#[derive(PartialEq, Eq, Hash, Copy, Clone)]
23pub struct FixedOffset {
24    local_minus_utc: i32,
25}
26
27impl FixedOffset {
28    /// Makes a new `FixedOffset` for the Eastern Hemisphere with given timezone difference.
29    /// The negative `secs` means the Western Hemisphere.
30    ///
31    /// Panics on the out-of-bound `secs`.
32    ///
33    /// # Example
34    ///
35    /// ~~~~
36    /// use chrono::{FixedOffset, TimeZone};
37    /// let hour = 3600;
38    /// let datetime = FixedOffset::east(5 * hour).ymd(2016, 11, 08)
39    ///                                           .and_hms(0, 0, 0);
40    /// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00+05:00")
41    /// ~~~~
42    pub fn east(secs: i32) -> FixedOffset {
43        FixedOffset::east_opt(secs).expect("FixedOffset::east out of bounds")
44    }
45
46    /// Makes a new `FixedOffset` for the Eastern Hemisphere with given timezone difference.
47    /// The negative `secs` means the Western Hemisphere.
48    ///
49    /// Returns `None` on the out-of-bound `secs`.
50    pub fn east_opt(secs: i32) -> Option<FixedOffset> {
51        if -86_400 < secs && secs < 86_400 {
52            Some(FixedOffset { local_minus_utc: secs })
53        } else {
54            None
55        }
56    }
57
58    /// Makes a new `FixedOffset` for the Western Hemisphere with given timezone difference.
59    /// The negative `secs` means the Eastern Hemisphere.
60    ///
61    /// Panics on the out-of-bound `secs`.
62    ///
63    /// # Example
64    ///
65    /// ~~~~
66    /// use chrono::{FixedOffset, TimeZone};
67    /// let hour = 3600;
68    /// let datetime = FixedOffset::west(5 * hour).ymd(2016, 11, 08)
69    ///                                           .and_hms(0, 0, 0);
70    /// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00-05:00")
71    /// ~~~~
72    pub fn west(secs: i32) -> FixedOffset {
73        FixedOffset::west_opt(secs).expect("FixedOffset::west out of bounds")
74    }
75
76    /// Makes a new `FixedOffset` for the Western Hemisphere with given timezone difference.
77    /// The negative `secs` means the Eastern Hemisphere.
78    ///
79    /// Returns `None` on the out-of-bound `secs`.
80    pub fn west_opt(secs: i32) -> Option<FixedOffset> {
81        if -86_400 < secs && secs < 86_400 {
82            Some(FixedOffset { local_minus_utc: -secs })
83        } else {
84            None
85        }
86    }
87
88    /// Returns the number of seconds to add to convert from UTC to the local time.
89    #[inline]
90    pub fn local_minus_utc(&self) -> i32 {
91        self.local_minus_utc
92    }
93
94    /// Returns the number of seconds to add to convert from the local time to UTC.
95    #[inline]
96    pub fn utc_minus_local(&self) -> i32 {
97        -self.local_minus_utc
98    }
99}
100
101impl TimeZone for FixedOffset {
102    type Offset = FixedOffset;
103
104    fn from_offset(offset: &FixedOffset) -> FixedOffset { *offset }
105
106    fn offset_from_local_date(&self, _local: &NaiveDate) -> LocalResult<FixedOffset> {
107        LocalResult::Single(*self)
108    }
109    fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult<FixedOffset> {
110        LocalResult::Single(*self)
111    }
112
113    fn offset_from_utc_date(&self, _utc: &NaiveDate) -> FixedOffset { *self }
114    fn offset_from_utc_datetime(&self, _utc: &NaiveDateTime) -> FixedOffset { *self }
115}
116
117impl Offset for FixedOffset {
118    fn fix(&self) -> FixedOffset { *self }
119}
120
121impl fmt::Debug for FixedOffset {
122    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
123        let offset = self.local_minus_utc;
124        let (sign, offset) = if offset < 0 {('-', -offset)} else {('+', offset)};
125        let (mins, sec) = div_mod_floor(offset, 60);
126        let (hour, min) = div_mod_floor(mins, 60);
127        if sec == 0 {
128            write!(f, "{}{:02}:{:02}", sign, hour, min)
129        } else {
130            write!(f, "{}{:02}:{:02}:{:02}", sign, hour, min, sec)
131        }
132    }
133}
134
135impl fmt::Display for FixedOffset {
136    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(self, f) }
137}
138
139// addition or subtraction of FixedOffset to/from Timelike values is same to
140// adding or subtracting the offset's local_minus_utc value
141// but keep keeps the leap second information.
142// this should be implemented more efficiently, but for the time being, this is generic right now.
143
144fn add_with_leapsecond<T>(lhs: &T, rhs: i32) -> T
145    where T: Timelike + Add<OldDuration, Output=T>
146{
147    // extract and temporarily remove the fractional part and later recover it
148    let nanos = lhs.nanosecond();
149    let lhs = lhs.with_nanosecond(0).unwrap();
150    (lhs + OldDuration::seconds(i64::from(rhs))).with_nanosecond(nanos).unwrap()
151}
152
153impl Add<FixedOffset> for NaiveTime {
154    type Output = NaiveTime;
155
156    #[inline]
157    fn add(self, rhs: FixedOffset) -> NaiveTime {
158        add_with_leapsecond(&self, rhs.local_minus_utc)
159    }
160}
161
162impl Sub<FixedOffset> for NaiveTime {
163    type Output = NaiveTime;
164
165    #[inline]
166    fn sub(self, rhs: FixedOffset) -> NaiveTime {
167        add_with_leapsecond(&self, -rhs.local_minus_utc)
168    }
169}
170
171impl Add<FixedOffset> for NaiveDateTime {
172    type Output = NaiveDateTime;
173
174    #[inline]
175    fn add(self, rhs: FixedOffset) -> NaiveDateTime {
176        add_with_leapsecond(&self, rhs.local_minus_utc)
177    }
178}
179
180impl Sub<FixedOffset> for NaiveDateTime {
181    type Output = NaiveDateTime;
182
183    #[inline]
184    fn sub(self, rhs: FixedOffset) -> NaiveDateTime {
185        add_with_leapsecond(&self, -rhs.local_minus_utc)
186    }
187}
188
189impl<Tz: TimeZone> Add<FixedOffset> for DateTime<Tz> {
190    type Output = DateTime<Tz>;
191
192    #[inline]
193    fn add(self, rhs: FixedOffset) -> DateTime<Tz> {
194        add_with_leapsecond(&self, rhs.local_minus_utc)
195    }
196}
197
198impl<Tz: TimeZone> Sub<FixedOffset> for DateTime<Tz> {
199    type Output = DateTime<Tz>;
200
201    #[inline]
202    fn sub(self, rhs: FixedOffset) -> DateTime<Tz> {
203        add_with_leapsecond(&self, -rhs.local_minus_utc)
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    use offset::TimeZone;
210    use super::FixedOffset;
211
212    #[test]
213    fn test_date_extreme_offset() {
214        // starting from 0.3 we don't have an offset exceeding one day.
215        // this makes everything easier!
216        assert_eq!(format!("{:?}", FixedOffset::east(86399).ymd(2012, 2, 29)),
217                   "2012-02-29+23:59:59".to_string());
218        assert_eq!(format!("{:?}", FixedOffset::east(86399).ymd(2012, 2, 29).and_hms(5, 6, 7)),
219                   "2012-02-29T05:06:07+23:59:59".to_string());
220        assert_eq!(format!("{:?}", FixedOffset::west(86399).ymd(2012, 3, 4)),
221                   "2012-03-04-23:59:59".to_string());
222        assert_eq!(format!("{:?}", FixedOffset::west(86399).ymd(2012, 3, 4).and_hms(5, 6, 7)),
223                   "2012-03-04T05:06:07-23:59:59".to_string());
224    }
225}
226