1use std::cmp::Ordering;
12use std::mem::MaybeUninit;
13use std::ptr;
14
15use super::win_bindings::{GetTimeZoneInformationForYear, SYSTEMTIME, TIME_ZONE_INFORMATION};
16
17use crate::offset::local::{Transition, lookup_with_dst_transitions};
18use crate::{Datelike, FixedOffset, MappedLocalTime, NaiveDate, NaiveDateTime, NaiveTime, Weekday};
19
20pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> MappedLocalTime<FixedOffset> {
28 let tz_info = match TzInfo::for_year(utc.year()) {
32 Some(tz_info) => tz_info,
33 None => return MappedLocalTime::None,
34 };
35 let offset = match (tz_info.std_transition, tz_info.dst_transition) {
36 (Some(std_transition), Some(dst_transition)) => {
37 let std_transition_utc = std_transition.overflowing_sub_offset(tz_info.dst_offset);
38 let dst_transition_utc = dst_transition.overflowing_sub_offset(tz_info.std_offset);
39 if dst_transition_utc < std_transition_utc {
40 match utc >= &dst_transition_utc && utc < &std_transition_utc {
41 true => tz_info.dst_offset,
42 false => tz_info.std_offset,
43 }
44 } else {
45 match utc >= &std_transition_utc && utc < &dst_transition_utc {
46 true => tz_info.std_offset,
47 false => tz_info.dst_offset,
48 }
49 }
50 }
51 (Some(std_transition), None) => {
52 let std_transition_utc = std_transition.overflowing_sub_offset(tz_info.dst_offset);
53 match utc < &std_transition_utc {
54 true => tz_info.dst_offset,
55 false => tz_info.std_offset,
56 }
57 }
58 (None, Some(dst_transition)) => {
59 let dst_transition_utc = dst_transition.overflowing_sub_offset(tz_info.std_offset);
60 match utc < &dst_transition_utc {
61 true => tz_info.std_offset,
62 false => tz_info.dst_offset,
63 }
64 }
65 (None, None) => tz_info.std_offset,
66 };
67 MappedLocalTime::Single(offset)
68}
69
70pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> MappedLocalTime<FixedOffset> {
74 let tz_info = match TzInfo::for_year(local.year()) {
75 Some(tz_info) => tz_info,
76 None => return MappedLocalTime::None,
77 };
78 match (tz_info.std_transition, tz_info.dst_transition) {
80 (Some(std_transition), Some(dst_transition)) => {
81 let std_transition =
82 Transition::new(std_transition, tz_info.dst_offset, tz_info.std_offset);
83 let dst_transition =
84 Transition::new(dst_transition, tz_info.std_offset, tz_info.dst_offset);
85 let transitions = match std_transition.cmp(&dst_transition) {
86 Ordering::Less => [std_transition, dst_transition],
87 Ordering::Greater => [dst_transition, std_transition],
88 Ordering::Equal => {
89 return MappedLocalTime::Single(tz_info.std_offset);
91 }
92 };
93 lookup_with_dst_transitions(&transitions, *local)
94 }
95 (Some(std_transition), None) => {
96 let transitions =
97 [Transition::new(std_transition, tz_info.dst_offset, tz_info.std_offset)];
98 lookup_with_dst_transitions(&transitions, *local)
99 }
100 (None, Some(dst_transition)) => {
101 let transitions =
102 [Transition::new(dst_transition, tz_info.std_offset, tz_info.dst_offset)];
103 lookup_with_dst_transitions(&transitions, *local)
104 }
105 (None, None) => MappedLocalTime::Single(tz_info.std_offset),
106 }
107}
108
109struct TzInfo {
119 std_offset: FixedOffset,
121 dst_offset: FixedOffset,
123 std_transition: Option<NaiveDateTime>,
125 dst_transition: Option<NaiveDateTime>,
127}
128
129impl TzInfo {
130 fn for_year(year: i32) -> Option<TzInfo> {
131 let ref_year = year.clamp(1601, 30827) as u16;
136 let tz_info = unsafe {
137 let mut tz_info = MaybeUninit::<TIME_ZONE_INFORMATION>::uninit();
138 if GetTimeZoneInformationForYear(ref_year, ptr::null_mut(), tz_info.as_mut_ptr()) == 0 {
139 return None;
140 }
141 tz_info.assume_init()
142 };
143 let std_offset = (tz_info.Bias)
144 .checked_add(tz_info.StandardBias)
145 .and_then(|o| o.checked_mul(60))
146 .and_then(FixedOffset::west_opt)?;
147 let dst_offset = (tz_info.Bias)
148 .checked_add(tz_info.DaylightBias)
149 .and_then(|o| o.checked_mul(60))
150 .and_then(FixedOffset::west_opt)?;
151 Some(TzInfo {
152 std_offset,
153 dst_offset,
154 std_transition: naive_date_time_from_system_time(tz_info.StandardDate, year).ok()?,
155 dst_transition: naive_date_time_from_system_time(tz_info.DaylightDate, year).ok()?,
156 })
157 }
158}
159
160fn naive_date_time_from_system_time(
170 st: SYSTEMTIME,
171 year: i32,
172) -> Result<Option<NaiveDateTime>, ()> {
173 if st.wYear == 0 && st.wMonth == 0 {
174 return Ok(None);
175 }
176 let time = NaiveTime::from_hms_milli_opt(
177 st.wHour as u32,
178 st.wMinute as u32,
179 st.wSecond as u32,
180 st.wMilliseconds as u32,
181 )
182 .ok_or(())?;
183
184 if st.wYear != 0 {
185 let date =
187 NaiveDate::from_ymd_opt(st.wYear as i32, st.wMonth as u32, st.wDay as u32).ok_or(())?;
188 return Ok(Some(date.and_time(time)));
189 }
190
191 let weekday = match st.wDayOfWeek {
194 0 => Weekday::Sun,
195 1 => Weekday::Mon,
196 2 => Weekday::Tue,
197 3 => Weekday::Wed,
198 4 => Weekday::Thu,
199 5 => Weekday::Fri,
200 6 => Weekday::Sat,
201 _ => return Err(()),
202 };
203 let nth_day = match st.wDay {
204 1..=5 => st.wDay as u8,
205 _ => return Err(()),
206 };
207 let date = NaiveDate::from_weekday_of_month_opt(year, st.wMonth as u32, weekday, nth_day)
208 .or_else(|| NaiveDate::from_weekday_of_month_opt(year, st.wMonth as u32, weekday, 4))
209 .ok_or(())?; Ok(Some(date.and_time(time)))
211}
212
213#[cfg(test)]
214mod tests {
215 use crate::offset::local::win_bindings::{
216 FILETIME, SYSTEMTIME, SystemTimeToFileTime, TzSpecificLocalTimeToSystemTime,
217 };
218 use crate::{DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, TimeDelta};
219 use crate::{Datelike, TimeZone, Timelike};
220 use std::mem::MaybeUninit;
221 use std::ptr;
222
223 #[test]
224 fn verify_against_tz_specific_local_time_to_system_time() {
225 fn from_local_time(dt: &NaiveDateTime) -> DateTime<Local> {
231 let st = system_time_from_naive_date_time(dt);
232 let utc_time = local_to_utc_time(&st);
233 let utc_secs = system_time_as_unix_seconds(&utc_time);
234 let local_secs = system_time_as_unix_seconds(&st);
235 let offset = (local_secs - utc_secs) as i32;
236 let offset = FixedOffset::east_opt(offset).unwrap();
237 DateTime::from_naive_utc_and_offset(*dt - offset, offset)
238 }
239 fn system_time_from_naive_date_time(dt: &NaiveDateTime) -> SYSTEMTIME {
240 SYSTEMTIME {
241 wYear: dt.year() as u16,
243 wMonth: dt.month() as u16,
245 wDayOfWeek: dt.weekday() as u16,
249 wDay: dt.day() as u16,
251 wHour: dt.hour() as u16,
253 wMinute: dt.minute() as u16,
255 wSecond: dt.second() as u16,
257 wMilliseconds: 0,
259 }
260 }
261 fn local_to_utc_time(local: &SYSTEMTIME) -> SYSTEMTIME {
262 let mut sys_time = MaybeUninit::<SYSTEMTIME>::uninit();
263 unsafe { TzSpecificLocalTimeToSystemTime(ptr::null(), local, sys_time.as_mut_ptr()) };
264 unsafe { sys_time.assume_init() }
267 }
268 const HECTONANOSECS_IN_SEC: i64 = 10_000_000;
269 const HECTONANOSEC_TO_UNIX_EPOCH: i64 = 11_644_473_600 * HECTONANOSECS_IN_SEC;
270 fn system_time_as_unix_seconds(st: &SYSTEMTIME) -> i64 {
271 let mut init = MaybeUninit::<FILETIME>::uninit();
272 unsafe {
273 SystemTimeToFileTime(st, init.as_mut_ptr());
274 }
275 let filetime = unsafe { init.assume_init() };
278 let bit_shift =
279 ((filetime.dwHighDateTime as u64) << 32) | (filetime.dwLowDateTime as u64);
280 (bit_shift as i64 - HECTONANOSEC_TO_UNIX_EPOCH) / HECTONANOSECS_IN_SEC
281 }
282
283 let mut date = NaiveDate::from_ymd_opt(1975, 1, 1).unwrap().and_hms_opt(0, 30, 0).unwrap();
284
285 while date.year() < 2078 {
286 if let Some(our_result) = Local.from_local_datetime(&date).earliest() {
288 assert_eq!(from_local_time(&date), our_result);
289 }
290 date += TimeDelta::try_hours(1).unwrap();
291 }
292 }
293}