Skip to main content

chrono_node/core/
reference.rs

1//! upstream `ReferenceWithTimezone` and `ParsingReference`.
2
3use super::js_date::JsDate;
4use super::timezone::{to_timezone_offset, TimezoneAbbrMap, TimezoneInput};
5
6/// upstream `ParsingReference`.
7#[derive(Clone, Debug, Default, PartialEq, Eq)]
8pub struct ParsingReference {
9    pub instant: Option<JsDate>,
10    pub timezone: Option<TimezoneInput>,
11}
12
13/// The `refDate` argument of `parse`: nothing ("now"), a `Date`, or a `ParsingReference`.
14#[derive(Clone, Debug, Default, PartialEq, Eq)]
15pub enum ReferenceInput {
16    #[default]
17    Now,
18    Date(JsDate),
19    Reference(ParsingReference),
20}
21
22impl From<JsDate> for ReferenceInput {
23    fn from(date: JsDate) -> Self {
24        ReferenceInput::Date(date)
25    }
26}
27
28impl From<ParsingReference> for ReferenceInput {
29    fn from(reference: ParsingReference) -> Self {
30        ReferenceInput::Reference(reference)
31    }
32}
33
34/// upstream `ReferenceWithTimezone`.
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub struct ReferenceWithTimezone {
37    pub instant: JsDate,
38    /// Minutes east of UTC, or `None` (use the system time zone, which is UTC).
39    pub timezone_offset: Option<i64>,
40}
41
42impl ReferenceWithTimezone {
43    pub fn new(instant: JsDate, timezone_offset: Option<i64>) -> ReferenceWithTimezone {
44        ReferenceWithTimezone {
45            instant,
46            timezone_offset,
47        }
48    }
49
50    /// `ReferenceWithTimezone.fromInput(input, timezoneOverrides)`. `now` is used where upstream calls `new Date()`.
51    pub fn from_input(input: &ReferenceInput, overrides: &TimezoneAbbrMap, now: JsDate) -> ReferenceWithTimezone {
52        match input {
53            ReferenceInput::Date(date) => ReferenceWithTimezone::new(*date, None),
54            ReferenceInput::Now => ReferenceWithTimezone::new(now, to_timezone_offset(None, Some(now), overrides)),
55            ReferenceInput::Reference(r) => {
56                let instant = r.instant.unwrap_or(now);
57                let offset = to_timezone_offset(r.timezone.as_ref(), Some(instant), overrides);
58                ReferenceWithTimezone::new(instant, offset)
59            }
60        }
61    }
62
63    /// `getDateWithAdjustedTimezone()`: a date whose (system-local = UTC) fields equal the
64    /// reference's wall-clock fields in the reference time zone.
65    pub fn get_date_with_adjusted_timezone(&self) -> JsDate {
66        let mut date = self.instant;
67        if self.timezone_offset.is_some() {
68            date.set_minutes(date.get_minutes() - self.get_system_timezone_adjustment_minute(Some(self.instant), None));
69        }
70        date
71    }
72
73    /// `getSystemTimezoneAdjustmentMinute(date, overrideTimezoneOffset)`.
74    pub fn get_system_timezone_adjustment_minute(&self, date: Option<JsDate>, override_offset: Option<i64>) -> i64 {
75        let date = date.unwrap_or_else(JsDate::now);
76        let current = -date.get_timezone_offset();
77        let target = override_offset.or(self.timezone_offset).unwrap_or(current);
78        current - target
79    }
80
81    /// `getTimezoneOffset()`.
82    pub fn get_timezone_offset(&self) -> i64 {
83        self.timezone_offset.unwrap_or(-self.instant.get_timezone_offset())
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn adjusted_date_shifts_into_reference_zone() {
93        let instant = JsDate::parse_iso("2021-06-09T12:00:00.000Z").unwrap();
94        let r = ReferenceWithTimezone::from_input(
95            &ReferenceInput::Reference(ParsingReference {
96                instant: Some(instant),
97                timezone: Some(TimezoneInput::Abbr("CDT".into())),
98            }),
99            &TimezoneAbbrMap::new(),
100            JsDate::INVALID,
101        );
102        assert_eq!(r.timezone_offset, Some(-300));
103        assert_eq!(
104            r.get_date_with_adjusted_timezone().to_iso_string().unwrap(),
105            "2021-06-09T07:00:00.000Z"
106        );
107        assert_eq!(r.get_timezone_offset(), -300);
108    }
109
110    #[test]
111    fn plain_date_has_no_offset() {
112        let instant = JsDate::parse_iso("2021-06-09T12:00:00.000Z").unwrap();
113        let r = ReferenceWithTimezone::from_input(&instant.into(), &TimezoneAbbrMap::new(), JsDate::INVALID);
114        assert_eq!(r.timezone_offset, None);
115        assert_eq!(r.get_date_with_adjusted_timezone(), instant);
116        assert_eq!(r.get_timezone_offset(), 0);
117    }
118}