From d2d59561e26822618852002e2300e284f3750532 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 24 Apr 2023 15:22:48 +0200 Subject: [PATCH] Encode offset of `-00:00` --- src/datetime/tests.rs | 2 +- src/format/mod.rs | 10 +++--- src/format/parse.rs | 44 +++++++++++++----------- src/format/parsed.rs | 23 ++++++++++--- src/format/scan.rs | 50 ++++++++++++++------------- src/lib.rs | 2 +- src/offset/fixed.rs | 79 ++++++++++++++++++++++++++++++++++++------- 7 files changed, 142 insertions(+), 68 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index aff9d93763..f522368d3f 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -407,7 +407,7 @@ fn test_datetime_rfc2822_and_rfc3339() { ); assert_eq!( DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 -0000"), - Ok(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap()) + Ok(FixedOffset::OFFSET_UNKNOWN.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap()) ); assert_eq!( DateTime::parse_from_rfc3339("2015-02-18T23:16:09Z"), diff --git a/src/format/mod.rs b/src/format/mod.rs index 768ed76815..7dc32b3b94 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -57,7 +57,7 @@ use crate::{Month, ParseMonthError, ParseWeekdayError, Weekday}; pub(crate) mod locales; pub use parse::parse; -pub use parsed::Parsed; +pub use parsed::{Parsed, NO_OFFSET_INFO}; /// L10n locales. #[cfg(feature = "unstable-locales")] pub use pure_rust_locales::Locale; @@ -714,16 +714,16 @@ fn format_inner( #[cfg(any(feature = "alloc", feature = "std", test))] fn write_local_minus_utc( result: &mut String, - off: FixedOffset, + offset: FixedOffset, allow_zulu: bool, colon_type: Colons, ) -> fmt::Result { - let off = off.local_minus_utc(); - if allow_zulu && off == 0 { + let off = offset.local_minus_utc(); + if allow_zulu && off == 0 && !offset.no_offset_info() { result.push('Z'); return Ok(()); } - let (sign, off) = if off < 0 { ('-', -off) } else { ('+', off) }; + let (sign, off) = if off < 0 || offset.no_offset_info() { ('-', -off) } else { ('+', off) }; result.push(sign); write_hundreds(result, (off / 3600) as u8)?; diff --git a/src/format/parse.rs b/src/format/parse.rs index e894f710b9..84f6ccd2d4 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -14,6 +14,7 @@ use super::scan; use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad, Parsed}; use super::{ParseError, ParseErrorKind, ParseResult}; use super::{BAD_FORMAT, INVALID, NOT_ENOUGH, OUT_OF_RANGE, TOO_LONG, TOO_SHORT}; +use crate::format::parsed::NO_OFFSET_INFO; use crate::{DateTime, FixedOffset, Weekday}; fn set_weekday_with_num_days_from_sunday(p: &mut Parsed, v: i64) -> ParseResult<()> { @@ -144,10 +145,8 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st } s = scan::space(s)?; // mandatory - if let Some(offset) = try_consume!(scan::timezone_offset_2822(s)) { - // only set the offset when it is definitely known (i.e. not `-0000`) - parsed.set_offset(i64::from(offset))?; - } + let offset = try_consume!(scan::timezone_offset_2822(s)); + parsed.set_offset(i64::from(offset.unwrap_or(NO_OFFSET_INFO)))?; // optional comments while let Ok((s_out, ())) = scan::comment_2822(s) { @@ -216,10 +215,7 @@ fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st } let offset = try_consume!(scan::timezone_offset_zulu(s, |s| scan::char(s, b':'))); - if offset <= -86_400 || offset >= 86_400 { - return Err(OUT_OF_RANGE); - } - parsed.set_offset(i64::from(offset))?; + parsed.set_offset(i64::from(offset.unwrap_or(NO_OFFSET_INFO)))?; Ok((s, ())) } @@ -428,7 +424,9 @@ where s.trim_left(), scan::colon_or_space )); - parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; + parsed + .set_offset(i64::from(offset.unwrap_or(NO_OFFSET_INFO))) + .map_err(|e| (s, e))?; } &TimezoneOffsetColonZ | &TimezoneOffsetZ => { @@ -436,7 +434,9 @@ where s.trim_left(), scan::colon_or_space )); - parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; + parsed + .set_offset(i64::from(offset.unwrap_or(NO_OFFSET_INFO))) + .map_err(|e| (s, e))?; } &Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive, @@ -445,7 +445,9 @@ where s.trim_left(), scan::colon_or_space )); - parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; + parsed + .set_offset(i64::from(offset.unwrap_or(NO_OFFSET_INFO))) + .map_err(|e| (s, e))?; } &RFC2822 => try_consume!(parse_rfc2822(parsed, s)), @@ -737,7 +739,7 @@ fn test_parse() { // fixed: timezone offsets check!("+00:00", [fix!(TimezoneOffset)]; offset: 0); - check!("-00:00", [fix!(TimezoneOffset)]; offset: 0); + check!("-00:00", [fix!(TimezoneOffset)]; offset: NO_OFFSET_INFO); check!("+00:01", [fix!(TimezoneOffset)]; offset: 60); check!("-00:01", [fix!(TimezoneOffset)]; offset: -60); check!("+00:30", [fix!(TimezoneOffset)]; offset: 30 * 60); @@ -819,7 +821,6 @@ fn test_parse() { #[cfg(test)] #[test] fn test_rfc2822() { - use super::NOT_ENOUGH; use super::*; use crate::offset::FixedOffset; use crate::DateTime; @@ -843,6 +844,7 @@ fn test_rfc2822() { ("20 Jan 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // no day of week ("20 JAN 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // upper case month ("Tue, 20 Jan 2015 17:35 -0800", Ok("Tue, 20 Jan 2015 17:35:00 -0800")), // no second + ("20 Jan 2015 17:35:20 -0000", Ok("Tue, 20 Jan 2015 17:35:20 -0000")), // -0000 offset ("11 Sep 2001 09:45:00 EST", Ok("Tue, 11 Sep 2001 09:45:00 -0500")), ("30 Feb 2015 17:35:20 -0800", Err(OUT_OF_RANGE)), // bad day of month ("Tue, 20 Jan 2015", Err(TOO_SHORT)), // omitted fields @@ -853,7 +855,7 @@ fn test_rfc2822() { ("Tue, 20 Jan 2015 17:35:90 -0800", Err(OUT_OF_RANGE)), // bad second ("Tue, 20 Jan 2015 17:35:20 -0890", Err(OUT_OF_RANGE)), // bad offset ("6 Jun 1944 04:00:00Z", Err(INVALID)), // bad offset (zulu not allowed) - ("Tue, 20 Jan 2015 17:35:20 HAS", Err(NOT_ENOUGH)), // bad named time zone + ("Tue, 20 Jan 2015 17:35:20 HAS", Err(INVALID)), // bad named time zone // named timezones that have specific timezone offsets // see https://www.rfc-editor.org/rfc/rfc2822#section-4.3 ("Tue, 20 Jan 2015 17:35:20 GMT", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), @@ -868,14 +870,15 @@ fn test_rfc2822() { ("Tue, 20 Jan 2015 17:35:20 PDT", Ok("Tue, 20 Jan 2015 17:35:20 -0700")), ("Tue, 20 Jan 2015 17:35:20 PST", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), ("Tue, 20 Jan 2015 17:35:20 pst", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), - // named single-letter military timezones must fallback to +0000 + // Z is the only single-letter military timezones that maps to +0000 ("Tue, 20 Jan 2015 17:35:20 Z", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 A", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 a", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 K", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 k", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), + // other named single-letter military timezones must fallback to -0000 + ("Tue, 20 Jan 2015 17:35:20 A", Ok("Tue, 20 Jan 2015 17:35:20 -0000")), + ("Tue, 20 Jan 2015 17:35:20 a", Ok("Tue, 20 Jan 2015 17:35:20 -0000")), + ("Tue, 20 Jan 2015 17:35:20 K", Ok("Tue, 20 Jan 2015 17:35:20 -0000")), + ("Tue, 20 Jan 2015 17:35:20 k", Ok("Tue, 20 Jan 2015 17:35:20 -0000")), // named single-letter timezone "J" is specifically not valid - ("Tue, 20 Jan 2015 17:35:20 J", Err(NOT_ENOUGH)), + ("Tue, 20 Jan 2015 17:35:20 J", Err(INVALID)), ]; fn rfc2822_to_datetime(date: &str) -> ParseResult> { @@ -958,6 +961,7 @@ fn test_rfc3339() { let testdates = [ ("2015-01-20T17:35:20-08:00", Ok("2015-01-20T17:35:20-08:00")), // normal case ("1944-06-06T04:04:00Z", Ok("1944-06-06T04:04:00+00:00")), // D-day + ("2015-01-20T17:35:20-00:00", Ok("2015-01-20T17:35:20-00:00")), // offset -00:00 ("2001-09-11T09:45:00-08:00", Ok("2001-09-11T09:45:00-08:00")), ("2015-01-20T17:35:20.001-08:00", Ok("2015-01-20T17:35:20.001-08:00")), ("2015-01-20T17:35:20.000031-08:00", Ok("2015-01-20T17:35:20.000031-08:00")), diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 1ed5dcbf1d..14b47ae65c 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -110,6 +110,10 @@ pub struct Parsed { _dummy: (), } +/// Constant to be used with [`Parsed::set_offset`] to indicate there is an offset of `00:00`, but +/// that is because the relation to local time is unknown (`-00:00` in RFC 3339 and 2822). +pub const NO_OFFSET_INFO: i32 = i32::MIN; + /// Checks if `old` is either empty or has the same value as `new` (i.e. "consistent"), /// and if it is empty, set `old` to `new` as well. #[inline] @@ -614,7 +618,12 @@ impl Parsed { /// Returns a parsed fixed time zone offset out of given fields. pub fn to_fixed_offset(&self) -> ParseResult { - self.offset.and_then(FixedOffset::east_opt).ok_or(OUT_OF_RANGE) + let offset = self.offset.ok_or(NOT_ENOUGH)?; + if offset == NO_OFFSET_INFO { + Ok(FixedOffset::OFFSET_UNKNOWN) + } else { + FixedOffset::east_opt(offset).ok_or(OUT_OF_RANGE) + } } /// Returns a parsed timezone-aware date and time out of given fields. @@ -625,15 +634,19 @@ impl Parsed { /// Either way those fields have to be consistent to each other. pub fn to_datetime(&self) -> ParseResult> { let offset = self.offset.ok_or(NOT_ENOUGH)?; - let datetime = self.to_naive_datetime_with_offset(offset)?; - let offset = FixedOffset::east_opt(offset).ok_or(OUT_OF_RANGE)?; + let (offset_i32, fixed_offset) = if offset == NO_OFFSET_INFO { + (0, FixedOffset::OFFSET_UNKNOWN) + } else { + (offset, FixedOffset::east_opt(offset).ok_or(OUT_OF_RANGE)?) + }; + let datetime = self.to_naive_datetime_with_offset(offset_i32)?; // this is used to prevent an overflow when calling FixedOffset::from_local_datetime datetime - .checked_sub_signed(OldDuration::seconds(i64::from(offset.local_minus_utc()))) + .checked_sub_signed(OldDuration::seconds(i64::from(offset_i32))) .ok_or(OUT_OF_RANGE)?; - match offset.from_local_datetime(&datetime) { + match fixed_offset.from_local_datetime(&datetime) { LocalResult::None => Err(IMPOSSIBLE), LocalResult::Single(t) => Ok(t), LocalResult::Ambiguous(..) => Err(NOT_ENOUGH), diff --git a/src/format/scan.rs b/src/format/scan.rs index 399a50c30f..5b2bf2e5fe 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -207,7 +207,9 @@ pub(super) fn colon_or_space(s: &str) -> ParseResult<&str> { /// /// The additional `colon` may be used to parse a mandatory or optional `:` /// between hours and minutes, and should return either a new suffix or `Err` when parsing fails. -pub(super) fn timezone_offset(s: &str, consume_colon: F) -> ParseResult<(&str, i32)> +/// +/// May return `None` which indicates no offset data is available (i.e. `-0000`). +pub(super) fn timezone_offset(s: &str, consume_colon: F) -> ParseResult<(&str, Option)> where F: FnMut(&str) -> ParseResult<&str>, { @@ -218,7 +220,7 @@ fn timezone_offset_internal( mut s: &str, mut consume_colon: F, allow_missing_minutes: bool, -) -> ParseResult<(&str, i32)> +) -> ParseResult<(&str, Option)> where F: FnMut(&str) -> ParseResult<&str>, { @@ -268,22 +270,27 @@ where }; let seconds = hours * 3600 + minutes * 60; - Ok((s, if negative { -seconds } else { seconds })) + + if seconds == 0 && negative { + return Ok((s, None)); + } + Ok((s, Some(if negative { -seconds } else { seconds }))) } /// Same as `timezone_offset` but also allows for `z`/`Z` which is the same as `+00:00`. -pub(super) fn timezone_offset_zulu(s: &str, colon: F) -> ParseResult<(&str, i32)> +/// May return `None` which indicates no offset data is available (i.e. `-0000`). +pub(super) fn timezone_offset_zulu(s: &str, colon: F) -> ParseResult<(&str, Option)> where F: FnMut(&str) -> ParseResult<&str>, { let bytes = s.as_bytes(); match bytes.first() { - Some(&b'z') | Some(&b'Z') => Ok((&s[1..], 0)), + Some(&b'z') | Some(&b'Z') => Ok((&s[1..], Some(0))), Some(&b'u') | Some(&b'U') => { if bytes.len() >= 3 { let (b, c) = (bytes[1], bytes[2]); match (b | 32, c | 32) { - (b't', b'c') => Ok((&s[3..], 0)), + (b't', b'c') => Ok((&s[3..], Some(0))), _ => Err(INVALID), } } else { @@ -296,18 +303,18 @@ where /// Same as `timezone_offset` but also allows for `z`/`Z` which is the same as /// `+00:00`, and allows missing minutes entirely. -pub(super) fn timezone_offset_permissive(s: &str, colon: F) -> ParseResult<(&str, i32)> +pub(super) fn timezone_offset_permissive(s: &str, colon: F) -> ParseResult<(&str, Option)> where F: FnMut(&str) -> ParseResult<&str>, { match s.as_bytes().first() { - Some(&b'z') | Some(&b'Z') => Ok((&s[1..], 0)), + Some(&b'z') | Some(&b'Z') => Ok((&s[1..], Some(0))), _ => timezone_offset_internal(s, colon, true), } } /// Same as `timezone_offset` but also allows for RFC 2822 legacy timezones. -/// May return `None` which indicates an insufficient offset data (i.e. `-0000`). +/// May return `None` which indicates no offset data is available (i.e. `-0000`). /// See [RFC 2822 Section 4.3]. /// /// [RFC 2822 Section 4.3]: https://tools.ietf.org/html/rfc2822#section-4.3 @@ -325,30 +332,27 @@ pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option)> let name = &s.as_bytes()[..upto]; let s = &s[upto..]; let offset_hours = |o| Ok((s, Some(o * 3600))); - if equals(name, "gmt") || equals(name, "ut") { - offset_hours(0) + if equals(name, "gmt") || equals(name, "ut") || equals(name, "z") || equals(name, "Z") { + return offset_hours(0); } else if equals(name, "edt") { - offset_hours(-4) + return offset_hours(-4); } else if equals(name, "est") || equals(name, "cdt") { - offset_hours(-5) + return offset_hours(-5); } else if equals(name, "cst") || equals(name, "mdt") { - offset_hours(-6) + return offset_hours(-6); } else if equals(name, "mst") || equals(name, "pdt") { - offset_hours(-7) + return offset_hours(-7); } else if equals(name, "pst") { - offset_hours(-8) + return offset_hours(-8); } else if name.len() == 1 { - match name[0] { + if let b'a'..=b'i' | b'k'..=b'y' | b'A'..=b'I' | b'K'..=b'Y' = name[0] { // recommended by RFC 2822: consume but treat it as -0000 - b'a'..=b'i' | b'k'..=b'z' | b'A'..=b'I' | b'K'..=b'Z' => offset_hours(0), - _ => Ok((s, None)), + return Ok((s, None)); } - } else { - Ok((s, None)) } + Err(INVALID) } else { - let (s_, offset) = timezone_offset(s, |s| Ok(s))?; - Ok((s_, Some(offset))) + timezone_offset(s, |s| Ok(s)) } } diff --git a/src/lib.rs b/src/lib.rs index 3a047f0d9b..3317c19237 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -537,7 +537,7 @@ fn test_type_sizes() { assert_eq!(size_of::>(), 12); assert_eq!(size_of::>(), 16); assert_eq!(size_of::>(), 16); - assert_eq!(size_of::>>(), 20); + assert_eq!(size_of::>>(), 16); assert_eq!(size_of::>(), 4); assert_eq!(size_of::>(), 8); diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 04ffa3b989..13a87fc293 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -4,6 +4,7 @@ //! The time zone which has a fixed offset from UTC. use core::fmt; +use core::num::NonZeroI32; use core::ops::{Add, Sub}; use num_integer::div_mod_floor; @@ -16,16 +17,50 @@ use crate::oldtime::Duration as OldDuration; use crate::DateTime; use crate::Timelike; +const OFFSET_NORMAL: i32 = 1; +const OFFSET_UNKNOWN: i32 = 2; + /// The time zone with fixed offset, from UTC-23:59:59 to UTC+23:59:59. /// /// Using the [`TimeZone`](./trait.TimeZone.html) methods /// on a `FixedOffset` struct is the preferred way to construct /// `DateTime` instances. See the [`east_opt`](#method.east_opt) and /// [`west_opt`](#method.west_opt) methods for examples. -#[derive(PartialEq, Eq, Hash, Copy, Clone)] +#[derive(Eq, Copy, Clone)] #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] pub struct FixedOffset { - local_minus_utc: i32, + // Encodes an offset in seconds, with the value shifted two bits to the left. + // The remaining bits encode flags: + // - `OFFSET_NORMAL` sets one flag to a non-zero value, so we can encode it in a `NonZeroI32` + // and get niche optimization. + // - `OFFSET_UNKNOWN` to preserve the difference RFC 2822 and RFC 3339 make between an offset + // of +0:00 and -0:00. + // Advantage of this encoding is that it only takes a single shift right to get offset in + // seconds. + local_minus_utc: NonZeroI32, +} + +// Compare with only the offset. `-00:00` compares equal to `+00:00` +impl PartialEq for FixedOffset { + fn eq(&self, other: &Self) -> bool { + self.local_minus_utc() == other.local_minus_utc() + } +} + +// Hash only the offset. `-00:00` hashes equal to `+00:00` +impl core::hash::Hash for FixedOffset { + fn hash(&self, state: &mut H) { + self.local_minus_utc().hash(state); + } +} + +// Workaround because `unwrap` in `NonZeroI32::new(val).unwrap()` is not const. +#[allow(unconditional_panic)] +const fn nonzero_i32_new(val: i32) -> NonZeroI32 { + match NonZeroI32::new(val) { + Some(v) => v, + None => [][0], + } } impl FixedOffset { @@ -56,7 +91,7 @@ impl FixedOffset { #[must_use] pub fn east_opt(secs: i32) -> Option { if -86_400 < secs && secs < 86_400 { - Some(FixedOffset { local_minus_utc: secs }) + Some(FixedOffset { local_minus_utc: nonzero_i32_new(secs << 2 | OFFSET_NORMAL) }) } else { None } @@ -89,7 +124,7 @@ impl FixedOffset { #[must_use] pub fn west_opt(secs: i32) -> Option { if -86_400 < secs && secs < 86_400 { - Some(FixedOffset { local_minus_utc: -secs }) + Some(FixedOffset { local_minus_utc: nonzero_i32_new(-secs << 2 | OFFSET_NORMAL) }) } else { None } @@ -98,14 +133,29 @@ impl FixedOffset { /// Returns the number of seconds to add to convert from UTC to the local time. #[inline] pub const fn local_minus_utc(&self) -> i32 { - self.local_minus_utc + self.local_minus_utc.get() >> 2 } /// Returns the number of seconds to add to convert from the local time to UTC. #[inline] pub const fn utc_minus_local(&self) -> i32 { - -self.local_minus_utc + -self.local_minus_utc() + } + + /// Returns true if this `FixedOffset` contains no offset data (in some formats encoded as + /// `-00:00`). + /// + /// In some formats, such as RFC 2822 and RFC 3339, `-00:00` can represent timestamps for which + /// only the time in UTC is known, and the relation to local time is not avaliable (anymore). + #[inline] + pub const fn no_offset_info(&self) -> bool { + self.local_minus_utc.get() == OFFSET_UNKNOWN } + + /// A special value to indicate no offset information is available. + /// The created offset will have the value `-00:00`. + pub const OFFSET_UNKNOWN: FixedOffset = + FixedOffset { local_minus_utc: nonzero_i32_new(OFFSET_UNKNOWN) }; } impl TimeZone for FixedOffset { @@ -138,7 +188,10 @@ impl Offset for FixedOffset { impl fmt::Debug for FixedOffset { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let offset = self.local_minus_utc; + if self.no_offset_info() { + return write!(f, "-00:00"); + } + let offset = self.local_minus_utc(); let (sign, offset) = if offset < 0 { ('-', -offset) } else { ('+', offset) }; let (mins, sec) = div_mod_floor(offset, 60); let (hour, min) = div_mod_floor(mins, 60); @@ -186,7 +239,7 @@ impl Add for NaiveTime { #[inline] fn add(self, rhs: FixedOffset) -> NaiveTime { - add_with_leapsecond(&self, rhs.local_minus_utc) + add_with_leapsecond(&self, rhs.local_minus_utc()) } } @@ -195,7 +248,7 @@ impl Sub for NaiveTime { #[inline] fn sub(self, rhs: FixedOffset) -> NaiveTime { - add_with_leapsecond(&self, -rhs.local_minus_utc) + add_with_leapsecond(&self, -rhs.local_minus_utc()) } } @@ -204,7 +257,7 @@ impl Add for NaiveDateTime { #[inline] fn add(self, rhs: FixedOffset) -> NaiveDateTime { - add_with_leapsecond(&self, rhs.local_minus_utc) + add_with_leapsecond(&self, rhs.local_minus_utc()) } } @@ -213,7 +266,7 @@ impl Sub for NaiveDateTime { #[inline] fn sub(self, rhs: FixedOffset) -> NaiveDateTime { - add_with_leapsecond(&self, -rhs.local_minus_utc) + add_with_leapsecond(&self, -rhs.local_minus_utc()) } } @@ -222,7 +275,7 @@ impl Add for DateTime { #[inline] fn add(self, rhs: FixedOffset) -> DateTime { - add_with_leapsecond(&self, rhs.local_minus_utc) + add_with_leapsecond(&self, rhs.local_minus_utc()) } } @@ -231,7 +284,7 @@ impl Sub for DateTime { #[inline] fn sub(self, rhs: FixedOffset) -> DateTime { - add_with_leapsecond(&self, -rhs.local_minus_utc) + add_with_leapsecond(&self, -rhs.local_minus_utc()) } }