| 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::str::FromStr; | 
|---|
| 8 |  | 
|---|
| 9 | #[ cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] | 
|---|
| 10 | use rkyv::{Archive, Deserialize, Serialize}; | 
|---|
| 11 |  | 
|---|
| 12 | use super::{MappedLocalTime, Offset, TimeZone}; | 
|---|
| 13 | use crate::format::{scan, ParseError, OUT_OF_RANGE}; | 
|---|
| 14 | use crate::naive::{NaiveDate, NaiveDateTime}; | 
|---|
| 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_opt`](#method.east_opt) and | 
|---|
| 21 | /// [`west_opt`](#method.west_opt) methods for examples. | 
|---|
| 22 | #[ derive(PartialEq, Eq, Hash, Copy, Clone)] | 
|---|
| 23 | #[ cfg_attr( | 
|---|
| 24 | any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), | 
|---|
| 25 | derive(Archive, Deserialize, Serialize), | 
|---|
| 26 | archive(compare(PartialEq)), | 
|---|
| 27 | archive_attr(derive(Clone, Copy, PartialEq, Eq, Hash, Debug)) | 
|---|
| 28 | )] | 
|---|
| 29 | #[ cfg_attr(feature = "rkyv-validation", archive(check_bytes))] | 
|---|
| 30 | pub struct FixedOffset { | 
|---|
| 31 | local_minus_utc: i32, | 
|---|
| 32 | } | 
|---|
| 33 |  | 
|---|
| 34 | impl FixedOffset { | 
|---|
| 35 | /// Makes a new `FixedOffset` for the Eastern Hemisphere with given timezone difference. | 
|---|
| 36 | /// The negative `secs` means the Western Hemisphere. | 
|---|
| 37 | /// | 
|---|
| 38 | /// Panics on the out-of-bound `secs`. | 
|---|
| 39 | #[ deprecated(since = "0.4.23", note = "use `east_opt()` instead")] | 
|---|
| 40 | #[ must_use] | 
|---|
| 41 | pub fn east(secs: i32) -> FixedOffset { | 
|---|
| 42 | FixedOffset::east_opt(secs).expect( "FixedOffset::east out of bounds") | 
|---|
| 43 | } | 
|---|
| 44 |  | 
|---|
| 45 | /// Makes a new `FixedOffset` for the Eastern Hemisphere with given timezone difference. | 
|---|
| 46 | /// The negative `secs` means the Western Hemisphere. | 
|---|
| 47 | /// | 
|---|
| 48 | /// Returns `None` on the out-of-bound `secs`. | 
|---|
| 49 | /// | 
|---|
| 50 | /// # Example | 
|---|
| 51 | /// | 
|---|
| 52 | /// ``` | 
|---|
| 53 | /// # #[ cfg(feature = "alloc")] { | 
|---|
| 54 | /// use chrono::{FixedOffset, TimeZone}; | 
|---|
| 55 | /// let hour = 3600; | 
|---|
| 56 | /// let datetime = | 
|---|
| 57 | ///     FixedOffset::east_opt(5 * hour).unwrap().with_ymd_and_hms(2016, 11, 08, 0, 0, 0).unwrap(); | 
|---|
| 58 | /// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00+05:00") | 
|---|
| 59 | /// # } | 
|---|
| 60 | /// ``` | 
|---|
| 61 | #[ must_use] | 
|---|
| 62 | pub const fn east_opt(secs: i32) -> Option<FixedOffset> { | 
|---|
| 63 | if -86_400 < secs && secs < 86_400 { | 
|---|
| 64 | Some(FixedOffset { local_minus_utc: secs }) | 
|---|
| 65 | } else { | 
|---|
| 66 | None | 
|---|
| 67 | } | 
|---|
| 68 | } | 
|---|
| 69 |  | 
|---|
| 70 | /// Makes a new `FixedOffset` for the Western Hemisphere with given timezone difference. | 
|---|
| 71 | /// The negative `secs` means the Eastern Hemisphere. | 
|---|
| 72 | /// | 
|---|
| 73 | /// Panics on the out-of-bound `secs`. | 
|---|
| 74 | #[ deprecated(since = "0.4.23", note = "use `west_opt()` instead")] | 
|---|
| 75 | #[ must_use] | 
|---|
| 76 | pub fn west(secs: i32) -> FixedOffset { | 
|---|
| 77 | FixedOffset::west_opt(secs).expect( "FixedOffset::west out of bounds") | 
|---|
| 78 | } | 
|---|
| 79 |  | 
|---|
| 80 | /// Makes a new `FixedOffset` for the Western Hemisphere with given timezone difference. | 
|---|
| 81 | /// The negative `secs` means the Eastern Hemisphere. | 
|---|
| 82 | /// | 
|---|
| 83 | /// Returns `None` on the out-of-bound `secs`. | 
|---|
| 84 | /// | 
|---|
| 85 | /// # Example | 
|---|
| 86 | /// | 
|---|
| 87 | /// ``` | 
|---|
| 88 | /// # #[ cfg(feature = "alloc")] { | 
|---|
| 89 | /// use chrono::{FixedOffset, TimeZone}; | 
|---|
| 90 | /// let hour = 3600; | 
|---|
| 91 | /// let datetime = | 
|---|
| 92 | ///     FixedOffset::west_opt(5 * hour).unwrap().with_ymd_and_hms(2016, 11, 08, 0, 0, 0).unwrap(); | 
|---|
| 93 | /// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00-05:00") | 
|---|
| 94 | /// # } | 
|---|
| 95 | /// ``` | 
|---|
| 96 | #[ must_use] | 
|---|
| 97 | pub const fn west_opt(secs: i32) -> Option<FixedOffset> { | 
|---|
| 98 | if -86_400 < secs && secs < 86_400 { | 
|---|
| 99 | Some(FixedOffset { local_minus_utc: -secs }) | 
|---|
| 100 | } else { | 
|---|
| 101 | None | 
|---|
| 102 | } | 
|---|
| 103 | } | 
|---|
| 104 |  | 
|---|
| 105 | /// Returns the number of seconds to add to convert from UTC to the local time. | 
|---|
| 106 | #[ inline] | 
|---|
| 107 | pub const fn local_minus_utc(&self) -> i32 { | 
|---|
| 108 | self.local_minus_utc | 
|---|
| 109 | } | 
|---|
| 110 |  | 
|---|
| 111 | /// Returns the number of seconds to add to convert from the local time to UTC. | 
|---|
| 112 | #[ inline] | 
|---|
| 113 | pub const fn utc_minus_local(&self) -> i32 { | 
|---|
| 114 | -self.local_minus_utc | 
|---|
| 115 | } | 
|---|
| 116 | } | 
|---|
| 117 |  | 
|---|
| 118 | /// Parsing a `str` into a `FixedOffset` uses the format [`%z`](crate::format::strftime). | 
|---|
| 119 | impl FromStr for FixedOffset { | 
|---|
| 120 | type Err = ParseError; | 
|---|
| 121 | fn from_str(s: &str) -> Result<Self, Self::Err> { | 
|---|
| 122 | let (_, offset: i32) = scan::timezone_offset(s, consume_colon:scan::colon_or_space, allow_zulu:false, allow_missing_minutes:false, allow_tz_minus_sign:true)?; | 
|---|
| 123 | Self::east_opt(offset).ok_or(OUT_OF_RANGE) | 
|---|
| 124 | } | 
|---|
| 125 | } | 
|---|
| 126 |  | 
|---|
| 127 | impl TimeZone for FixedOffset { | 
|---|
| 128 | type Offset = FixedOffset; | 
|---|
| 129 |  | 
|---|
| 130 | fn from_offset(offset: &FixedOffset) -> FixedOffset { | 
|---|
| 131 | *offset | 
|---|
| 132 | } | 
|---|
| 133 |  | 
|---|
| 134 | fn offset_from_local_date(&self, _local: &NaiveDate) -> MappedLocalTime<FixedOffset> { | 
|---|
| 135 | MappedLocalTime::Single(*self) | 
|---|
| 136 | } | 
|---|
| 137 | fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> MappedLocalTime<FixedOffset> { | 
|---|
| 138 | MappedLocalTime::Single(*self) | 
|---|
| 139 | } | 
|---|
| 140 |  | 
|---|
| 141 | fn offset_from_utc_date(&self, _utc: &NaiveDate) -> FixedOffset { | 
|---|
| 142 | *self | 
|---|
| 143 | } | 
|---|
| 144 | fn offset_from_utc_datetime(&self, _utc: &NaiveDateTime) -> FixedOffset { | 
|---|
| 145 | *self | 
|---|
| 146 | } | 
|---|
| 147 | } | 
|---|
| 148 |  | 
|---|
| 149 | impl Offset for FixedOffset { | 
|---|
| 150 | fn fix(&self) -> FixedOffset { | 
|---|
| 151 | *self | 
|---|
| 152 | } | 
|---|
| 153 | } | 
|---|
| 154 |  | 
|---|
| 155 | impl fmt::Debug for FixedOffset { | 
|---|
| 156 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | 
|---|
| 157 | let offset: i32 = self.local_minus_utc; | 
|---|
| 158 | let (sign: char, offset: i32) = if offset < 0 { ( '-', -offset) } else { ( '+', offset) }; | 
|---|
| 159 | let sec: i32 = offset.rem_euclid(60); | 
|---|
| 160 | let mins: i32 = offset.div_euclid(60); | 
|---|
| 161 | let min: i32 = mins.rem_euclid(60); | 
|---|
| 162 | let hour: i32 = mins.div_euclid(60); | 
|---|
| 163 | if sec == 0 { | 
|---|
| 164 | write!(f, "{}{:02} :{:02} ", sign, hour, min) | 
|---|
| 165 | } else { | 
|---|
| 166 | write!(f, "{}{:02} :{:02} :{:02} ", sign, hour, min, sec) | 
|---|
| 167 | } | 
|---|
| 168 | } | 
|---|
| 169 | } | 
|---|
| 170 |  | 
|---|
| 171 | impl fmt::Display for FixedOffset { | 
|---|
| 172 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | 
|---|
| 173 | fmt::Debug::fmt(self, f) | 
|---|
| 174 | } | 
|---|
| 175 | } | 
|---|
| 176 |  | 
|---|
| 177 | #[ cfg(all(feature = "arbitrary", feature = "std"))] | 
|---|
| 178 | impl arbitrary::Arbitrary<'_> for FixedOffset { | 
|---|
| 179 | fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result<FixedOffset> { | 
|---|
| 180 | let secs = u.int_in_range(-86_399..=86_399)?; | 
|---|
| 181 | let fixed_offset = FixedOffset::east_opt(secs) | 
|---|
| 182 | .expect( "Could not generate a valid chrono::FixedOffset. It looks like implementation of Arbitrary for FixedOffset is erroneous."); | 
|---|
| 183 | Ok(fixed_offset) | 
|---|
| 184 | } | 
|---|
| 185 | } | 
|---|
| 186 |  | 
|---|
| 187 | #[ cfg(test)] | 
|---|
| 188 | mod tests { | 
|---|
| 189 | use super::FixedOffset; | 
|---|
| 190 | use crate::offset::TimeZone; | 
|---|
| 191 | use std::str::FromStr; | 
|---|
| 192 |  | 
|---|
| 193 | #[ test] | 
|---|
| 194 | fn test_date_extreme_offset() { | 
|---|
| 195 | // starting from 0.3 we don't have an offset exceeding one day. | 
|---|
| 196 | // this makes everything easier! | 
|---|
| 197 | let offset = FixedOffset::east_opt(86399).unwrap(); | 
|---|
| 198 | assert_eq!( | 
|---|
| 199 | format!( "{:?}", offset.with_ymd_and_hms(2012, 2, 29, 5, 6, 7).unwrap()), | 
|---|
| 200 | "2012-02-29T05:06:07+23:59:59" | 
|---|
| 201 | ); | 
|---|
| 202 | let offset = FixedOffset::east_opt(-86399).unwrap(); | 
|---|
| 203 | assert_eq!( | 
|---|
| 204 | format!( "{:?}", offset.with_ymd_and_hms(2012, 2, 29, 5, 6, 7).unwrap()), | 
|---|
| 205 | "2012-02-29T05:06:07-23:59:59" | 
|---|
| 206 | ); | 
|---|
| 207 | let offset = FixedOffset::west_opt(86399).unwrap(); | 
|---|
| 208 | assert_eq!( | 
|---|
| 209 | format!( "{:?}", offset.with_ymd_and_hms(2012, 3, 4, 5, 6, 7).unwrap()), | 
|---|
| 210 | "2012-03-04T05:06:07-23:59:59" | 
|---|
| 211 | ); | 
|---|
| 212 | let offset = FixedOffset::west_opt(-86399).unwrap(); | 
|---|
| 213 | assert_eq!( | 
|---|
| 214 | format!( "{:?}", offset.with_ymd_and_hms(2012, 3, 4, 5, 6, 7).unwrap()), | 
|---|
| 215 | "2012-03-04T05:06:07+23:59:59" | 
|---|
| 216 | ); | 
|---|
| 217 | } | 
|---|
| 218 |  | 
|---|
| 219 | #[ test] | 
|---|
| 220 | fn test_parse_offset() { | 
|---|
| 221 | let offset = FixedOffset::from_str( "-0500").unwrap(); | 
|---|
| 222 | assert_eq!(offset.local_minus_utc, -5 * 3600); | 
|---|
| 223 | let offset = FixedOffset::from_str( "-08:00").unwrap(); | 
|---|
| 224 | assert_eq!(offset.local_minus_utc, -8 * 3600); | 
|---|
| 225 | let offset = FixedOffset::from_str( "+06:30").unwrap(); | 
|---|
| 226 | assert_eq!(offset.local_minus_utc, (6 * 3600) + 1800); | 
|---|
| 227 | } | 
|---|
| 228 |  | 
|---|
| 229 | #[ test] | 
|---|
| 230 | #[ cfg(feature = "rkyv-validation")] | 
|---|
| 231 | fn test_rkyv_validation() { | 
|---|
| 232 | let offset = FixedOffset::from_str( "-0500").unwrap(); | 
|---|
| 233 | let bytes = rkyv::to_bytes::<_, 4>(&offset).unwrap(); | 
|---|
| 234 | assert_eq!(rkyv::from_bytes::<FixedOffset>(&bytes).unwrap(), offset); | 
|---|
| 235 | } | 
|---|
| 236 | } | 
|---|
| 237 |  | 
|---|