1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

use crate::provider::{ZoneOffsetPeriodV1Marker, EPOCH};
use crate::{TimeZoneBcp47Id, UtcOffset};
use icu_calendar::Iso;
use icu_calendar::{Date, Time};
use icu_provider::prelude::*;

/// [`ZoneOffsetCalculator`] uses data from the [data provider] to calculate time zone offsets.
///
/// [data provider]: icu_provider
#[derive(Debug)]
pub struct ZoneOffsetCalculator {
    pub(super) offset_period: DataPayload<ZoneOffsetPeriodV1Marker>,
}

#[cfg(feature = "compiled_data")]
impl Default for ZoneOffsetCalculator {
    fn default() -> Self {
        Self::new()
    }
}

impl ZoneOffsetCalculator {
    /// Constructs a `ZoneOffsetCalculator` using compiled data.
    ///
    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
    ///
    /// [📚 Help choosing a constructor](icu_provider::constructors)
    #[cfg(feature = "compiled_data")]
    #[inline]
    pub const fn new() -> Self {
        ZoneOffsetCalculator {
            offset_period: DataPayload::from_static_ref(
                crate::provider::Baked::SINGLETON_ZONE_OFFSET_PERIOD_V1_MARKER,
            ),
        }
    }

    icu_provider::gen_any_buffer_data_constructors!(() -> error: DataError,
        functions: [
            new: skip,
            try_new_with_any_provider,
            try_new_with_buffer_provider,
            try_new_unstable,
            Self,
        ]
    );

    #[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::new)]
    pub fn try_new_unstable(
        provider: &(impl DataProvider<ZoneOffsetPeriodV1Marker> + ?Sized),
    ) -> Result<Self, DataError> {
        let metazone_period = provider.load(Default::default())?.payload;
        Ok(Self {
            offset_period: metazone_period,
        })
    }

    /// Calculate zone offsets from timezone and local datetime.
    ///
    /// # Examples
    ///
    /// ```
    /// use icu::calendar::{Date, Time};
    /// use icu::timezone::TimeZoneBcp47Id;
    /// use icu::timezone::ZoneOffsetCalculator;
    /// use icu::timezone::UtcOffset;
    /// use tinystr::tinystr;
    ///
    /// let zoc = ZoneOffsetCalculator::new();
    ///
    /// // America/Denver observes DST
    /// let offsets = zoc.compute_offsets_from_time_zone(
    ///     TimeZoneBcp47Id(tinystr!(8, "usden")),
    ///     (Date::try_new_iso(2024, 1, 1).unwrap(), Time::midnight()),
    /// ).unwrap();
    /// assert_eq!(offsets.standard, UtcOffset::try_from_seconds(-7 * 3600).unwrap());
    /// assert_eq!(offsets.daylight, Some(UtcOffset::try_from_seconds(-6 * 3600).unwrap()));
    ///
    /// // America/Phoenix does not
    /// let offsets = zoc.compute_offsets_from_time_zone(
    ///     TimeZoneBcp47Id(tinystr!(8, "usphx")),
    ///     (Date::try_new_iso(2024, 1, 1).unwrap(), Time::midnight()),
    /// ).unwrap();
    /// assert_eq!(offsets.standard, UtcOffset::try_from_seconds(-7 * 3600).unwrap());
    /// assert_eq!(offsets.daylight, None);
    /// ```
    pub fn compute_offsets_from_time_zone(
        &self,
        time_zone_id: TimeZoneBcp47Id,
        (date, time): (Date<Iso>, Time),
    ) -> Option<ZoneOffsets> {
        use zerovec::ule::AsULE;
        match self.offset_period.get().0.get0(&time_zone_id) {
            Some(cursor) => {
                let mut offsets = None;
                let minutes_since_epoch_walltime = (date.to_fixed() - EPOCH) as i32 * 24 * 60
                    + (time.hour.number() as i32 * 60 + time.minute.number() as i32);
                for (minutes, id) in cursor.iter1_copied().rev() {
                    if minutes_since_epoch_walltime <= i32::from_unaligned(*minutes) {
                        offsets = Some(id);
                    } else {
                        break;
                    }
                }
                let offsets = offsets?;
                Some(ZoneOffsets {
                    standard: UtcOffset::from_eighths_of_hour(offsets.0),
                    daylight: (offsets.1 != 0)
                        .then_some(UtcOffset::from_eighths_of_hour(offsets.0 + offsets.1)),
                })
            }
            None => None,
        }
    }
}

/// Represents the different offsets in use for a time zone
#[non_exhaustive]
#[derive(Debug, Clone, Copy)]
pub struct ZoneOffsets {
    /// The standard offset.
    pub standard: UtcOffset,
    /// The daylight-saving offset, if used.
    pub daylight: Option<UtcOffset>,
}