• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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::fmt;
7 use core::ops::{Add, Sub};
8 use oldtime::Duration as OldDuration;
9 
10 use super::{LocalResult, Offset, TimeZone};
11 use div::div_mod_floor;
12 use naive::{NaiveDate, NaiveDateTime, NaiveTime};
13 use DateTime;
14 use Timelike;
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 {
105         *offset
106     }
107 
offset_from_local_date(&self, _local: &NaiveDate) -> LocalResult<FixedOffset>108     fn offset_from_local_date(&self, _local: &NaiveDate) -> LocalResult<FixedOffset> {
109         LocalResult::Single(*self)
110     }
offset_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult<FixedOffset>111     fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult<FixedOffset> {
112         LocalResult::Single(*self)
113     }
114 
offset_from_utc_date(&self, _utc: &NaiveDate) -> FixedOffset115     fn offset_from_utc_date(&self, _utc: &NaiveDate) -> FixedOffset {
116         *self
117     }
offset_from_utc_datetime(&self, _utc: &NaiveDateTime) -> FixedOffset118     fn offset_from_utc_datetime(&self, _utc: &NaiveDateTime) -> FixedOffset {
119         *self
120     }
121 }
122 
123 impl Offset for FixedOffset {
fix(&self) -> FixedOffset124     fn fix(&self) -> FixedOffset {
125         *self
126     }
127 }
128 
129 impl fmt::Debug for FixedOffset {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result130     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
131         let offset = self.local_minus_utc;
132         let (sign, offset) = if offset < 0 { ('-', -offset) } else { ('+', offset) };
133         let (mins, sec) = div_mod_floor(offset, 60);
134         let (hour, min) = div_mod_floor(mins, 60);
135         if sec == 0 {
136             write!(f, "{}{:02}:{:02}", sign, hour, min)
137         } else {
138             write!(f, "{}{:02}:{:02}:{:02}", sign, hour, min, sec)
139         }
140     }
141 }
142 
143 impl fmt::Display for FixedOffset {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result144     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
145         fmt::Debug::fmt(self, f)
146     }
147 }
148 
149 // addition or subtraction of FixedOffset to/from Timelike values is the same as
150 // adding or subtracting the offset's local_minus_utc value
151 // but keep keeps the leap second information.
152 // this should be implemented more efficiently, but for the time being, this is generic right now.
153 
add_with_leapsecond<T>(lhs: &T, rhs: i32) -> T where T: Timelike + Add<OldDuration, Output = T>,154 fn add_with_leapsecond<T>(lhs: &T, rhs: i32) -> T
155 where
156     T: Timelike + Add<OldDuration, Output = T>,
157 {
158     // extract and temporarily remove the fractional part and later recover it
159     let nanos = lhs.nanosecond();
160     let lhs = lhs.with_nanosecond(0).unwrap();
161     (lhs + OldDuration::seconds(i64::from(rhs))).with_nanosecond(nanos).unwrap()
162 }
163 
164 impl Add<FixedOffset> for NaiveTime {
165     type Output = NaiveTime;
166 
167     #[inline]
add(self, rhs: FixedOffset) -> NaiveTime168     fn add(self, rhs: FixedOffset) -> NaiveTime {
169         add_with_leapsecond(&self, rhs.local_minus_utc)
170     }
171 }
172 
173 impl Sub<FixedOffset> for NaiveTime {
174     type Output = NaiveTime;
175 
176     #[inline]
sub(self, rhs: FixedOffset) -> NaiveTime177     fn sub(self, rhs: FixedOffset) -> NaiveTime {
178         add_with_leapsecond(&self, -rhs.local_minus_utc)
179     }
180 }
181 
182 impl Add<FixedOffset> for NaiveDateTime {
183     type Output = NaiveDateTime;
184 
185     #[inline]
add(self, rhs: FixedOffset) -> NaiveDateTime186     fn add(self, rhs: FixedOffset) -> NaiveDateTime {
187         add_with_leapsecond(&self, rhs.local_minus_utc)
188     }
189 }
190 
191 impl Sub<FixedOffset> for NaiveDateTime {
192     type Output = NaiveDateTime;
193 
194     #[inline]
sub(self, rhs: FixedOffset) -> NaiveDateTime195     fn sub(self, rhs: FixedOffset) -> NaiveDateTime {
196         add_with_leapsecond(&self, -rhs.local_minus_utc)
197     }
198 }
199 
200 impl<Tz: TimeZone> Add<FixedOffset> for DateTime<Tz> {
201     type Output = DateTime<Tz>;
202 
203     #[inline]
add(self, rhs: FixedOffset) -> DateTime<Tz>204     fn add(self, rhs: FixedOffset) -> DateTime<Tz> {
205         add_with_leapsecond(&self, rhs.local_minus_utc)
206     }
207 }
208 
209 impl<Tz: TimeZone> Sub<FixedOffset> for DateTime<Tz> {
210     type Output = DateTime<Tz>;
211 
212     #[inline]
sub(self, rhs: FixedOffset) -> DateTime<Tz>213     fn sub(self, rhs: FixedOffset) -> DateTime<Tz> {
214         add_with_leapsecond(&self, -rhs.local_minus_utc)
215     }
216 }
217 
218 #[cfg(test)]
219 mod tests {
220     use super::FixedOffset;
221     use offset::TimeZone;
222 
223     #[test]
test_date_extreme_offset()224     fn test_date_extreme_offset() {
225         // starting from 0.3 we don't have an offset exceeding one day.
226         // this makes everything easier!
227         assert_eq!(
228             format!("{:?}", FixedOffset::east(86399).ymd(2012, 2, 29)),
229             "2012-02-29+23:59:59".to_string()
230         );
231         assert_eq!(
232             format!("{:?}", FixedOffset::east(86399).ymd(2012, 2, 29).and_hms(5, 6, 7)),
233             "2012-02-29T05:06:07+23:59:59".to_string()
234         );
235         assert_eq!(
236             format!("{:?}", FixedOffset::west(86399).ymd(2012, 3, 4)),
237             "2012-03-04-23:59:59".to_string()
238         );
239         assert_eq!(
240             format!("{:?}", FixedOffset::west(86399).ymd(2012, 3, 4).and_hms(5, 6, 7)),
241             "2012-03-04T05:06:07-23:59:59".to_string()
242         );
243     }
244 }
245