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 
6 use core::ops::{Add, Sub};
7 use core::fmt;
8 use oldtime::Duration as OldDuration;
9 
10 use Timelike;
11 use div::div_mod_floor;
12 use naive::{NaiveTime, NaiveDate, NaiveDateTime};
13 use DateTime;
14 use 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)]
23 pub struct FixedOffset {
24     local_minus_utc: i32,
25 }
26 
27 impl 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     /// ~~~~
east(secs: i32) -> FixedOffset42     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`.
east_opt(secs: i32) -> Option<FixedOffset>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     /// ~~~~
west(secs: i32) -> FixedOffset72     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`.
west_opt(secs: i32) -> Option<FixedOffset>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]
local_minus_utc(&self) -> i3290     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]
utc_minus_local(&self) -> i3296     pub fn utc_minus_local(&self) -> i32 {
97         -self.local_minus_utc
98     }
99 }
100 
101 impl TimeZone for FixedOffset {
102     type Offset = FixedOffset;
103 
from_offset(offset: &FixedOffset) -> FixedOffset104     fn from_offset(offset: &FixedOffset) -> FixedOffset { *offset }
105 
offset_from_local_date(&self, _local: &NaiveDate) -> LocalResult<FixedOffset>106     fn offset_from_local_date(&self, _local: &NaiveDate) -> LocalResult<FixedOffset> {
107         LocalResult::Single(*self)
108     }
offset_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult<FixedOffset>109     fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult<FixedOffset> {
110         LocalResult::Single(*self)
111     }
112 
offset_from_utc_date(&self, _utc: &NaiveDate) -> FixedOffset113     fn offset_from_utc_date(&self, _utc: &NaiveDate) -> FixedOffset { *self }
offset_from_utc_datetime(&self, _utc: &NaiveDateTime) -> FixedOffset114     fn offset_from_utc_datetime(&self, _utc: &NaiveDateTime) -> FixedOffset { *self }
115 }
116 
117 impl Offset for FixedOffset {
fix(&self) -> FixedOffset118     fn fix(&self) -> FixedOffset { *self }
119 }
120 
121 impl fmt::Debug for FixedOffset {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result122     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 
135 impl fmt::Display for FixedOffset {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result136     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 the same as
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 
add_with_leapsecond<T>(lhs: &T, rhs: i32) -> T where T: Timelike + Add<OldDuration, Output=T>144 fn 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 
153 impl Add<FixedOffset> for NaiveTime {
154     type Output = NaiveTime;
155 
156     #[inline]
add(self, rhs: FixedOffset) -> NaiveTime157     fn add(self, rhs: FixedOffset) -> NaiveTime {
158         add_with_leapsecond(&self, rhs.local_minus_utc)
159     }
160 }
161 
162 impl Sub<FixedOffset> for NaiveTime {
163     type Output = NaiveTime;
164 
165     #[inline]
sub(self, rhs: FixedOffset) -> NaiveTime166     fn sub(self, rhs: FixedOffset) -> NaiveTime {
167         add_with_leapsecond(&self, -rhs.local_minus_utc)
168     }
169 }
170 
171 impl Add<FixedOffset> for NaiveDateTime {
172     type Output = NaiveDateTime;
173 
174     #[inline]
add(self, rhs: FixedOffset) -> NaiveDateTime175     fn add(self, rhs: FixedOffset) -> NaiveDateTime {
176         add_with_leapsecond(&self, rhs.local_minus_utc)
177     }
178 }
179 
180 impl Sub<FixedOffset> for NaiveDateTime {
181     type Output = NaiveDateTime;
182 
183     #[inline]
sub(self, rhs: FixedOffset) -> NaiveDateTime184     fn sub(self, rhs: FixedOffset) -> NaiveDateTime {
185         add_with_leapsecond(&self, -rhs.local_minus_utc)
186     }
187 }
188 
189 impl<Tz: TimeZone> Add<FixedOffset> for DateTime<Tz> {
190     type Output = DateTime<Tz>;
191 
192     #[inline]
add(self, rhs: FixedOffset) -> DateTime<Tz>193     fn add(self, rhs: FixedOffset) -> DateTime<Tz> {
194         add_with_leapsecond(&self, rhs.local_minus_utc)
195     }
196 }
197 
198 impl<Tz: TimeZone> Sub<FixedOffset> for DateTime<Tz> {
199     type Output = DateTime<Tz>;
200 
201     #[inline]
sub(self, rhs: FixedOffset) -> DateTime<Tz>202     fn sub(self, rhs: FixedOffset) -> DateTime<Tz> {
203         add_with_leapsecond(&self, -rhs.local_minus_utc)
204     }
205 }
206 
207 #[cfg(test)]
208 mod tests {
209     use offset::TimeZone;
210     use super::FixedOffset;
211 
212     #[test]
test_date_extreme_offset()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 
227