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