1#![allow(clippy::exhaustive_structs, clippy::exhaustive_enums)]
7
8use crate::zone::{UtcOffset, VariantOffsets, ZoneNameTimestamp};
19#[cfg(feature = "datagen")]
20use icu_provider::prelude::*;
21use zerovec::maps::ZeroMapKV;
22use zerovec::ule::AsULE;
23use zerovec::{ZeroMap2d, ZeroSlice, ZeroVec};
24
25pub use crate::zone::ule::TimeZoneVariantULE;
26pub use crate::zone::TimeZone;
27pub mod iana;
28pub mod windows;
29
30#[cfg(feature = "compiled_data")]
31#[derive(Debug)]
32pub struct Baked;
40
41#[cfg(feature = "compiled_data")]
42#[allow(unused_imports)]
43const _: () = {
44 use icu_time_data::*;
45 pub mod icu {
46 pub use crate as time;
47 }
48 make_provider!(Baked);
49 impl_timezone_identifiers_iana_extended_v1!(Baked);
50 impl_timezone_identifiers_iana_core_v1!(Baked);
51 impl_timezone_identifiers_windows_v1!(Baked);
52 impl_timezone_variants_offsets_v1!(Baked);
53};
54
55#[cfg(feature = "datagen")]
56pub const MARKERS: &[DataMarkerInfo] = &[
58 iana::TimezoneIdentifiersIanaExtendedV1::INFO,
59 iana::TimezoneIdentifiersIanaCoreV1::INFO,
60 windows::TimezoneIdentifiersWindowsV1::INFO,
61 TimezoneVariantsOffsetsV1::INFO,
62];
63
64const SECONDS_TO_EIGHTS_OF_HOURS: i32 = 60 * 60 / 8;
65
66impl AsULE for VariantOffsets {
67 type ULE = [i8; 2];
68
69 fn from_unaligned([std, dst]: Self::ULE) -> Self {
70 fn decode(encoded: i8) -> i32 {
71 encoded as i32 * SECONDS_TO_EIGHTS_OF_HOURS
72 + match encoded % 8 {
73 1 | 5 => 150,
75 -1 | -5 => -150,
76 3 | 7 => -150,
78 -3 | -7 => 150,
79 _ => 0,
81 }
82 }
83
84 Self {
85 standard: UtcOffset::from_seconds_unchecked(decode(std)),
86 daylight: (dst != 0).then(|| UtcOffset::from_seconds_unchecked(decode(std + dst))),
87 }
88 }
89
90 fn to_unaligned(self) -> Self::ULE {
91 fn encode(offset: i32) -> i8 {
92 debug_assert_eq!(offset.abs() % 60, 0);
93 let scaled = match offset.abs() / 60 % 60 {
94 0 | 15 | 30 | 45 => offset / SECONDS_TO_EIGHTS_OF_HOURS,
95 10 | 40 => {
96 offset / SECONDS_TO_EIGHTS_OF_HOURS
98 }
99 20 | 50 => {
100 offset / SECONDS_TO_EIGHTS_OF_HOURS + offset.signum()
102 }
103 _ => {
104 debug_assert!(false, "{offset:?}");
105 offset / SECONDS_TO_EIGHTS_OF_HOURS
106 }
107 };
108 debug_assert!(i8::MIN as i32 <= scaled && scaled <= i8::MAX as i32);
109 scaled as i8
110 }
111 [
112 encode(self.standard.to_seconds()),
113 self.daylight
114 .map(|d| encode(d.to_seconds() - self.standard.to_seconds()))
115 .unwrap_or_default(),
116 ]
117 }
118}
119
120#[test]
121fn offsets_ule() {
122 #[track_caller]
123 fn assert_round_trip(offset: UtcOffset) {
124 let variants = VariantOffsets::from_standard(offset);
125 assert_eq!(
126 variants,
127 VariantOffsets::from_unaligned(VariantOffsets::to_unaligned(variants))
128 );
129 }
130
131 assert_round_trip(UtcOffset::try_from_str("+01:00").unwrap());
132 assert_round_trip(UtcOffset::try_from_str("+01:15").unwrap());
133 assert_round_trip(UtcOffset::try_from_str("+01:30").unwrap());
134 assert_round_trip(UtcOffset::try_from_str("+01:45").unwrap());
135
136 assert_round_trip(UtcOffset::try_from_str("+01:10").unwrap());
137 assert_round_trip(UtcOffset::try_from_str("+01:20").unwrap());
138 assert_round_trip(UtcOffset::try_from_str("+01:40").unwrap());
139 assert_round_trip(UtcOffset::try_from_str("+01:50").unwrap());
140
141 assert_round_trip(UtcOffset::try_from_str("-01:00").unwrap());
142 assert_round_trip(UtcOffset::try_from_str("-01:15").unwrap());
143 assert_round_trip(UtcOffset::try_from_str("-01:30").unwrap());
144 assert_round_trip(UtcOffset::try_from_str("-01:45").unwrap());
145
146 assert_round_trip(UtcOffset::try_from_str("-01:10").unwrap());
147 assert_round_trip(UtcOffset::try_from_str("-01:20").unwrap());
148 assert_round_trip(UtcOffset::try_from_str("-01:40").unwrap());
149 assert_round_trip(UtcOffset::try_from_str("-01:50").unwrap());
150}
151
152impl<'a> ZeroMapKV<'a> for VariantOffsets {
153 type Container = ZeroVec<'a, Self>;
154 type Slice = ZeroSlice<Self>;
155 type GetType = <Self as AsULE>::ULE;
156 type OwnedType = Self;
157}
158
159#[cfg(all(feature = "alloc", feature = "serde"))]
160impl serde::Serialize for VariantOffsets {
161 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
162 where
163 S: serde::Serializer,
164 {
165 if serializer.is_human_readable() {
166 serializer.serialize_str(&if let Some(dst) = self.daylight {
167 alloc::format!(
168 "{:+02}:{:02}/{:+02}:{:02}",
169 self.standard.hours_part(),
170 self.standard.minutes_part(),
171 dst.hours_part(),
172 dst.minutes_part(),
173 )
174 } else {
175 alloc::format!(
176 "{:+02}:{:02}",
177 self.standard.hours_part(),
178 self.standard.minutes_part(),
179 )
180 })
181 } else {
182 self.to_unaligned().serialize(serializer)
183 }
184 }
185}
186
187#[cfg(feature = "serde")]
188impl<'de> serde::Deserialize<'de> for VariantOffsets {
189 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
190 where
191 D: serde::Deserializer<'de>,
192 {
193 use serde::de::Error;
194 if deserializer.is_human_readable() {
195 let raw = <&str>::deserialize(deserializer)?;
196 Ok(if let Some((std, dst)) = raw.split_once('/') {
197 Self {
198 standard: UtcOffset::try_from_str(std)
199 .map_err(|_| D::Error::custom("invalid offset"))?,
200 daylight: Some(
201 UtcOffset::try_from_str(dst)
202 .map_err(|_| D::Error::custom("invalid offset"))?,
203 ),
204 }
205 } else {
206 Self {
207 standard: UtcOffset::try_from_str(raw)
208 .map_err(|_| D::Error::custom("invalid offset"))?,
209 daylight: None,
210 }
211 })
212 } else {
213 <_>::deserialize(deserializer).map(Self::from_unaligned)
214 }
215 }
216}
217
218icu_provider::data_marker!(
219 TimezoneVariantsOffsetsV1,
225 "timezone/variants/offsets/v1",
226 ZeroMap2d<'static, TimeZone, ZoneNameTimestamp, VariantOffsets>,
227 is_singleton = true
228);