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