• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // This is a part of Chrono.
2 // See README.md and LICENSE.txt for details.
3 
4 //! The local (system) time zone.
5 
6 #[cfg(windows)]
7 use std::cmp::Ordering;
8 
9 #[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
10 use rkyv::{Archive, Deserialize, Serialize};
11 
12 use super::fixed::FixedOffset;
13 use super::{MappedLocalTime, TimeZone};
14 use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime};
15 #[allow(deprecated)]
16 use crate::Date;
17 use crate::{DateTime, Utc};
18 
19 #[cfg(unix)]
20 #[path = "unix.rs"]
21 mod inner;
22 
23 #[cfg(windows)]
24 #[path = "windows.rs"]
25 mod inner;
26 
27 #[cfg(all(windows, feature = "clock"))]
28 #[allow(unreachable_pub)]
29 mod win_bindings;
30 
31 #[cfg(all(
32     not(unix),
33     not(windows),
34     not(all(
35         target_arch = "wasm32",
36         feature = "wasmbind",
37         not(any(target_os = "emscripten", target_os = "wasi"))
38     ))
39 ))]
40 mod inner {
41     use crate::{FixedOffset, MappedLocalTime, NaiveDateTime};
42 
offset_from_utc_datetime( _utc_time: &NaiveDateTime, ) -> MappedLocalTime<FixedOffset>43     pub(super) fn offset_from_utc_datetime(
44         _utc_time: &NaiveDateTime,
45     ) -> MappedLocalTime<FixedOffset> {
46         MappedLocalTime::Single(FixedOffset::east_opt(0).unwrap())
47     }
48 
offset_from_local_datetime( _local_time: &NaiveDateTime, ) -> MappedLocalTime<FixedOffset>49     pub(super) fn offset_from_local_datetime(
50         _local_time: &NaiveDateTime,
51     ) -> MappedLocalTime<FixedOffset> {
52         MappedLocalTime::Single(FixedOffset::east_opt(0).unwrap())
53     }
54 }
55 
56 #[cfg(all(
57     target_arch = "wasm32",
58     feature = "wasmbind",
59     not(any(target_os = "emscripten", target_os = "wasi"))
60 ))]
61 mod inner {
62     use crate::{Datelike, FixedOffset, MappedLocalTime, NaiveDateTime, Timelike};
63 
offset_from_utc_datetime(utc: &NaiveDateTime) -> MappedLocalTime<FixedOffset>64     pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> MappedLocalTime<FixedOffset> {
65         let offset = js_sys::Date::from(utc.and_utc()).get_timezone_offset();
66         MappedLocalTime::Single(FixedOffset::west_opt((offset as i32) * 60).unwrap())
67     }
68 
offset_from_local_datetime( local: &NaiveDateTime, ) -> MappedLocalTime<FixedOffset>69     pub(super) fn offset_from_local_datetime(
70         local: &NaiveDateTime,
71     ) -> MappedLocalTime<FixedOffset> {
72         let mut year = local.year();
73         if year < 100 {
74             // The API in `js_sys` does not let us create a `Date` with negative years.
75             // And values for years from `0` to `99` map to the years `1900` to `1999`.
76             // Shift the value by a multiple of 400 years until it is `>= 100`.
77             let shift_cycles = (year - 100).div_euclid(400);
78             year -= shift_cycles * 400;
79         }
80         let js_date = js_sys::Date::new_with_year_month_day_hr_min_sec(
81             year as u32,
82             local.month0() as i32,
83             local.day() as i32,
84             local.hour() as i32,
85             local.minute() as i32,
86             local.second() as i32,
87             // ignore milliseconds, our representation of leap seconds may be problematic
88         );
89         let offset = js_date.get_timezone_offset();
90         // We always get a result, even if this time does not exist or is ambiguous.
91         MappedLocalTime::Single(FixedOffset::west_opt((offset as i32) * 60).unwrap())
92     }
93 }
94 
95 #[cfg(unix)]
96 mod tz_info;
97 
98 /// The local timescale.
99 ///
100 /// Using the [`TimeZone`](./trait.TimeZone.html) methods
101 /// on the Local struct is the preferred way to construct `DateTime<Local>`
102 /// instances.
103 ///
104 /// # Example
105 ///
106 /// ```
107 /// use chrono::{DateTime, Local, TimeZone};
108 ///
109 /// let dt1: DateTime<Local> = Local::now();
110 /// let dt2: DateTime<Local> = Local.timestamp_opt(0, 0).unwrap();
111 /// assert!(dt1 >= dt2);
112 /// ```
113 #[derive(Copy, Clone, Debug)]
114 #[cfg_attr(
115     any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
116     derive(Archive, Deserialize, Serialize),
117     archive(compare(PartialEq)),
118     archive_attr(derive(Clone, Copy, Debug))
119 )]
120 #[cfg_attr(feature = "rkyv-validation", archive(check_bytes))]
121 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
122 pub struct Local;
123 
124 impl Local {
125     /// Returns a `Date` which corresponds to the current date.
126     #[deprecated(since = "0.4.23", note = "use `Local::now()` instead")]
127     #[allow(deprecated)]
128     #[must_use]
today() -> Date<Local>129     pub fn today() -> Date<Local> {
130         Local::now().date()
131     }
132 
133     /// Returns a `DateTime<Local>` which corresponds to the current date, time and offset from
134     /// UTC.
135     ///
136     /// See also the similar [`Utc::now()`] which returns `DateTime<Utc>`, i.e. without the local
137     /// offset.
138     ///
139     /// # Example
140     ///
141     /// ```
142     /// # #![allow(unused_variables)]
143     /// # use chrono::{DateTime, FixedOffset, Local};
144     /// // Current local time
145     /// let now = Local::now();
146     ///
147     /// // Current local date
148     /// let today = now.date_naive();
149     ///
150     /// // Current local time, converted to `DateTime<FixedOffset>`
151     /// let now_fixed_offset = Local::now().fixed_offset();
152     /// // or
153     /// let now_fixed_offset: DateTime<FixedOffset> = Local::now().into();
154     ///
155     /// // Current time in some timezone (let's use +05:00)
156     /// // Note that it is usually more efficient to use `Utc::now` for this use case.
157     /// let offset = FixedOffset::east_opt(5 * 60 * 60).unwrap();
158     /// let now_with_offset = Local::now().with_timezone(&offset);
159     /// ```
now() -> DateTime<Local>160     pub fn now() -> DateTime<Local> {
161         Utc::now().with_timezone(&Local)
162     }
163 }
164 
165 impl TimeZone for Local {
166     type Offset = FixedOffset;
167 
from_offset(_offset: &FixedOffset) -> Local168     fn from_offset(_offset: &FixedOffset) -> Local {
169         Local
170     }
171 
172     #[allow(deprecated)]
offset_from_local_date(&self, local: &NaiveDate) -> MappedLocalTime<FixedOffset>173     fn offset_from_local_date(&self, local: &NaiveDate) -> MappedLocalTime<FixedOffset> {
174         // Get the offset at local midnight.
175         self.offset_from_local_datetime(&local.and_time(NaiveTime::MIN))
176     }
177 
offset_from_local_datetime(&self, local: &NaiveDateTime) -> MappedLocalTime<FixedOffset>178     fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> MappedLocalTime<FixedOffset> {
179         inner::offset_from_local_datetime(local)
180     }
181 
182     #[allow(deprecated)]
offset_from_utc_date(&self, utc: &NaiveDate) -> FixedOffset183     fn offset_from_utc_date(&self, utc: &NaiveDate) -> FixedOffset {
184         // Get the offset at midnight.
185         self.offset_from_utc_datetime(&utc.and_time(NaiveTime::MIN))
186     }
187 
offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> FixedOffset188     fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> FixedOffset {
189         inner::offset_from_utc_datetime(utc).unwrap()
190     }
191 }
192 
193 #[cfg(windows)]
194 #[derive(Copy, Clone, Eq, PartialEq)]
195 struct Transition {
196     transition_utc: NaiveDateTime,
197     offset_before: FixedOffset,
198     offset_after: FixedOffset,
199 }
200 
201 #[cfg(windows)]
202 impl Transition {
new( transition_local: NaiveDateTime, offset_before: FixedOffset, offset_after: FixedOffset, ) -> Transition203     fn new(
204         transition_local: NaiveDateTime,
205         offset_before: FixedOffset,
206         offset_after: FixedOffset,
207     ) -> Transition {
208         // It is no problem if the transition time in UTC falls a couple of hours inside the buffer
209         // space around the `NaiveDateTime` range (although it is very theoretical to have a
210         // transition at midnight around `NaiveDate::(MIN|MAX)`.
211         let transition_utc = transition_local.overflowing_sub_offset(offset_before);
212         Transition { transition_utc, offset_before, offset_after }
213     }
214 }
215 
216 #[cfg(windows)]
217 impl PartialOrd for Transition {
partial_cmp(&self, other: &Self) -> Option<Ordering>218     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
219         Some(self.transition_utc.cmp(&other.transition_utc))
220     }
221 }
222 
223 #[cfg(windows)]
224 impl Ord for Transition {
cmp(&self, other: &Self) -> Ordering225     fn cmp(&self, other: &Self) -> Ordering {
226         self.transition_utc.cmp(&other.transition_utc)
227     }
228 }
229 
230 // Calculate the time in UTC given a local time and transitions.
231 // `transitions` must be sorted.
232 #[cfg(windows)]
lookup_with_dst_transitions( transitions: &[Transition], dt: NaiveDateTime, ) -> MappedLocalTime<FixedOffset>233 fn lookup_with_dst_transitions(
234     transitions: &[Transition],
235     dt: NaiveDateTime,
236 ) -> MappedLocalTime<FixedOffset> {
237     for t in transitions.iter() {
238         // A transition can result in the wall clock time going forward (creating a gap) or going
239         // backward (creating a fold). We are interested in the earliest and latest wall time of the
240         // transition, as this are the times between which `dt` does may not exist or is ambiguous.
241         //
242         // It is no problem if the transition times falls a couple of hours inside the buffer
243         // space around the `NaiveDateTime` range (although it is very theoretical to have a
244         // transition at midnight around `NaiveDate::(MIN|MAX)`.
245         let (offset_min, offset_max) =
246             match t.offset_after.local_minus_utc() > t.offset_before.local_minus_utc() {
247                 true => (t.offset_before, t.offset_after),
248                 false => (t.offset_after, t.offset_before),
249             };
250         let wall_earliest = t.transition_utc.overflowing_add_offset(offset_min);
251         let wall_latest = t.transition_utc.overflowing_add_offset(offset_max);
252 
253         if dt < wall_earliest {
254             return MappedLocalTime::Single(t.offset_before);
255         } else if dt <= wall_latest {
256             return match t.offset_after.local_minus_utc().cmp(&t.offset_before.local_minus_utc()) {
257                 Ordering::Equal => MappedLocalTime::Single(t.offset_before),
258                 Ordering::Less => MappedLocalTime::Ambiguous(t.offset_before, t.offset_after),
259                 Ordering::Greater => {
260                     if dt == wall_earliest {
261                         MappedLocalTime::Single(t.offset_before)
262                     } else if dt == wall_latest {
263                         MappedLocalTime::Single(t.offset_after)
264                     } else {
265                         MappedLocalTime::None
266                     }
267                 }
268             };
269         }
270     }
271     MappedLocalTime::Single(transitions.last().unwrap().offset_after)
272 }
273 
274 #[cfg(test)]
275 mod tests {
276     use super::Local;
277     #[cfg(windows)]
278     use crate::offset::local::{lookup_with_dst_transitions, Transition};
279     use crate::offset::TimeZone;
280     use crate::{Datelike, Days, Utc};
281     #[cfg(windows)]
282     use crate::{FixedOffset, MappedLocalTime, NaiveDate, NaiveDateTime};
283 
284     #[test]
verify_correct_offsets()285     fn verify_correct_offsets() {
286         let now = Local::now();
287         let from_local = Local.from_local_datetime(&now.naive_local()).unwrap();
288         let from_utc = Local.from_utc_datetime(&now.naive_utc());
289 
290         assert_eq!(now.offset().local_minus_utc(), from_local.offset().local_minus_utc());
291         assert_eq!(now.offset().local_minus_utc(), from_utc.offset().local_minus_utc());
292 
293         assert_eq!(now, from_local);
294         assert_eq!(now, from_utc);
295     }
296 
297     #[test]
verify_correct_offsets_distant_past()298     fn verify_correct_offsets_distant_past() {
299         let distant_past = Local::now() - Days::new(365 * 500);
300         let from_local = Local.from_local_datetime(&distant_past.naive_local()).unwrap();
301         let from_utc = Local.from_utc_datetime(&distant_past.naive_utc());
302 
303         assert_eq!(distant_past.offset().local_minus_utc(), from_local.offset().local_minus_utc());
304         assert_eq!(distant_past.offset().local_minus_utc(), from_utc.offset().local_minus_utc());
305 
306         assert_eq!(distant_past, from_local);
307         assert_eq!(distant_past, from_utc);
308     }
309 
310     #[test]
verify_correct_offsets_distant_future()311     fn verify_correct_offsets_distant_future() {
312         let distant_future = Local::now() + Days::new(365 * 35000);
313         let from_local = Local.from_local_datetime(&distant_future.naive_local()).unwrap();
314         let from_utc = Local.from_utc_datetime(&distant_future.naive_utc());
315 
316         assert_eq!(
317             distant_future.offset().local_minus_utc(),
318             from_local.offset().local_minus_utc()
319         );
320         assert_eq!(distant_future.offset().local_minus_utc(), from_utc.offset().local_minus_utc());
321 
322         assert_eq!(distant_future, from_local);
323         assert_eq!(distant_future, from_utc);
324     }
325 
326     #[test]
test_local_date_sanity_check()327     fn test_local_date_sanity_check() {
328         // issue #27
329         assert_eq!(Local.with_ymd_and_hms(2999, 12, 28, 0, 0, 0).unwrap().day(), 28);
330     }
331 
332     #[test]
test_leap_second()333     fn test_leap_second() {
334         // issue #123
335         let today = Utc::now().date_naive();
336 
337         if let Some(dt) = today.and_hms_milli_opt(15, 2, 59, 1000) {
338             let timestr = dt.time().to_string();
339             // the OS API may or may not support the leap second,
340             // but there are only two sensible options.
341             assert!(
342                 timestr == "15:02:60" || timestr == "15:03:00",
343                 "unexpected timestr {:?}",
344                 timestr
345             );
346         }
347 
348         if let Some(dt) = today.and_hms_milli_opt(15, 2, 3, 1234) {
349             let timestr = dt.time().to_string();
350             assert!(
351                 timestr == "15:02:03.234" || timestr == "15:02:04.234",
352                 "unexpected timestr {:?}",
353                 timestr
354             );
355         }
356     }
357 
358     #[test]
359     #[cfg(windows)]
test_lookup_with_dst_transitions()360     fn test_lookup_with_dst_transitions() {
361         let ymdhms = |y, m, d, h, n, s| {
362             NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap()
363         };
364 
365         #[track_caller]
366         #[allow(clippy::too_many_arguments)]
367         fn compare_lookup(
368             transitions: &[Transition],
369             y: i32,
370             m: u32,
371             d: u32,
372             h: u32,
373             n: u32,
374             s: u32,
375             result: MappedLocalTime<FixedOffset>,
376         ) {
377             let dt = NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap();
378             assert_eq!(lookup_with_dst_transitions(transitions, dt), result);
379         }
380 
381         // dst transition before std transition
382         // dst offset > std offset
383         let std = FixedOffset::east_opt(3 * 60 * 60).unwrap();
384         let dst = FixedOffset::east_opt(4 * 60 * 60).unwrap();
385         let transitions = [
386             Transition::new(ymdhms(2023, 3, 26, 2, 0, 0), std, dst),
387             Transition::new(ymdhms(2023, 10, 29, 3, 0, 0), dst, std),
388         ];
389         compare_lookup(&transitions, 2023, 3, 26, 1, 0, 0, MappedLocalTime::Single(std));
390         compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, MappedLocalTime::Single(std));
391         compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, MappedLocalTime::None);
392         compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, MappedLocalTime::Single(dst));
393         compare_lookup(&transitions, 2023, 3, 26, 4, 0, 0, MappedLocalTime::Single(dst));
394 
395         compare_lookup(&transitions, 2023, 10, 29, 1, 0, 0, MappedLocalTime::Single(dst));
396         compare_lookup(&transitions, 2023, 10, 29, 2, 0, 0, MappedLocalTime::Ambiguous(dst, std));
397         compare_lookup(&transitions, 2023, 10, 29, 2, 30, 0, MappedLocalTime::Ambiguous(dst, std));
398         compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, MappedLocalTime::Ambiguous(dst, std));
399         compare_lookup(&transitions, 2023, 10, 29, 4, 0, 0, MappedLocalTime::Single(std));
400 
401         // std transition before dst transition
402         // dst offset > std offset
403         let std = FixedOffset::east_opt(-5 * 60 * 60).unwrap();
404         let dst = FixedOffset::east_opt(-4 * 60 * 60).unwrap();
405         let transitions = [
406             Transition::new(ymdhms(2023, 3, 24, 3, 0, 0), dst, std),
407             Transition::new(ymdhms(2023, 10, 27, 2, 0, 0), std, dst),
408         ];
409         compare_lookup(&transitions, 2023, 3, 24, 1, 0, 0, MappedLocalTime::Single(dst));
410         compare_lookup(&transitions, 2023, 3, 24, 2, 0, 0, MappedLocalTime::Ambiguous(dst, std));
411         compare_lookup(&transitions, 2023, 3, 24, 2, 30, 0, MappedLocalTime::Ambiguous(dst, std));
412         compare_lookup(&transitions, 2023, 3, 24, 3, 0, 0, MappedLocalTime::Ambiguous(dst, std));
413         compare_lookup(&transitions, 2023, 3, 24, 4, 0, 0, MappedLocalTime::Single(std));
414 
415         compare_lookup(&transitions, 2023, 10, 27, 1, 0, 0, MappedLocalTime::Single(std));
416         compare_lookup(&transitions, 2023, 10, 27, 2, 0, 0, MappedLocalTime::Single(std));
417         compare_lookup(&transitions, 2023, 10, 27, 2, 30, 0, MappedLocalTime::None);
418         compare_lookup(&transitions, 2023, 10, 27, 3, 0, 0, MappedLocalTime::Single(dst));
419         compare_lookup(&transitions, 2023, 10, 27, 4, 0, 0, MappedLocalTime::Single(dst));
420 
421         // dst transition before std transition
422         // dst offset < std offset
423         let std = FixedOffset::east_opt(3 * 60 * 60).unwrap();
424         let dst = FixedOffset::east_opt((2 * 60 + 30) * 60).unwrap();
425         let transitions = [
426             Transition::new(ymdhms(2023, 3, 26, 2, 30, 0), std, dst),
427             Transition::new(ymdhms(2023, 10, 29, 2, 0, 0), dst, std),
428         ];
429         compare_lookup(&transitions, 2023, 3, 26, 1, 0, 0, MappedLocalTime::Single(std));
430         compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, MappedLocalTime::Ambiguous(std, dst));
431         compare_lookup(&transitions, 2023, 3, 26, 2, 15, 0, MappedLocalTime::Ambiguous(std, dst));
432         compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, MappedLocalTime::Ambiguous(std, dst));
433         compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, MappedLocalTime::Single(dst));
434 
435         compare_lookup(&transitions, 2023, 10, 29, 1, 0, 0, MappedLocalTime::Single(dst));
436         compare_lookup(&transitions, 2023, 10, 29, 2, 0, 0, MappedLocalTime::Single(dst));
437         compare_lookup(&transitions, 2023, 10, 29, 2, 15, 0, MappedLocalTime::None);
438         compare_lookup(&transitions, 2023, 10, 29, 2, 30, 0, MappedLocalTime::Single(std));
439         compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, MappedLocalTime::Single(std));
440 
441         // std transition before dst transition
442         // dst offset < std offset
443         let std = FixedOffset::east_opt(-(4 * 60 + 30) * 60).unwrap();
444         let dst = FixedOffset::east_opt(-5 * 60 * 60).unwrap();
445         let transitions = [
446             Transition::new(ymdhms(2023, 3, 24, 2, 0, 0), dst, std),
447             Transition::new(ymdhms(2023, 10, 27, 2, 30, 0), std, dst),
448         ];
449         compare_lookup(&transitions, 2023, 3, 24, 1, 0, 0, MappedLocalTime::Single(dst));
450         compare_lookup(&transitions, 2023, 3, 24, 2, 0, 0, MappedLocalTime::Single(dst));
451         compare_lookup(&transitions, 2023, 3, 24, 2, 15, 0, MappedLocalTime::None);
452         compare_lookup(&transitions, 2023, 3, 24, 2, 30, 0, MappedLocalTime::Single(std));
453         compare_lookup(&transitions, 2023, 3, 24, 3, 0, 0, MappedLocalTime::Single(std));
454 
455         compare_lookup(&transitions, 2023, 10, 27, 1, 0, 0, MappedLocalTime::Single(std));
456         compare_lookup(&transitions, 2023, 10, 27, 2, 0, 0, MappedLocalTime::Ambiguous(std, dst));
457         compare_lookup(&transitions, 2023, 10, 27, 2, 15, 0, MappedLocalTime::Ambiguous(std, dst));
458         compare_lookup(&transitions, 2023, 10, 27, 2, 30, 0, MappedLocalTime::Ambiguous(std, dst));
459         compare_lookup(&transitions, 2023, 10, 27, 3, 0, 0, MappedLocalTime::Single(dst));
460 
461         // offset stays the same
462         let std = FixedOffset::east_opt(3 * 60 * 60).unwrap();
463         let transitions = [
464             Transition::new(ymdhms(2023, 3, 26, 2, 0, 0), std, std),
465             Transition::new(ymdhms(2023, 10, 29, 3, 0, 0), std, std),
466         ];
467         compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, MappedLocalTime::Single(std));
468         compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, MappedLocalTime::Single(std));
469 
470         // single transition
471         let std = FixedOffset::east_opt(3 * 60 * 60).unwrap();
472         let dst = FixedOffset::east_opt(4 * 60 * 60).unwrap();
473         let transitions = [Transition::new(ymdhms(2023, 3, 26, 2, 0, 0), std, dst)];
474         compare_lookup(&transitions, 2023, 3, 26, 1, 0, 0, MappedLocalTime::Single(std));
475         compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, MappedLocalTime::Single(std));
476         compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, MappedLocalTime::None);
477         compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, MappedLocalTime::Single(dst));
478         compare_lookup(&transitions, 2023, 3, 26, 4, 0, 0, MappedLocalTime::Single(dst));
479     }
480 
481     #[test]
482     #[cfg(windows)]
test_lookup_with_dst_transitions_limits()483     fn test_lookup_with_dst_transitions_limits() {
484         // Transition beyond UTC year end doesn't panic in year of `NaiveDate::MAX`
485         let std = FixedOffset::east_opt(3 * 60 * 60).unwrap();
486         let dst = FixedOffset::east_opt(4 * 60 * 60).unwrap();
487         let transitions = [
488             Transition::new(NaiveDateTime::MAX.with_month(7).unwrap(), std, dst),
489             Transition::new(NaiveDateTime::MAX, dst, std),
490         ];
491         assert_eq!(
492             lookup_with_dst_transitions(&transitions, NaiveDateTime::MAX.with_month(3).unwrap()),
493             MappedLocalTime::Single(std)
494         );
495         assert_eq!(
496             lookup_with_dst_transitions(&transitions, NaiveDateTime::MAX.with_month(8).unwrap()),
497             MappedLocalTime::Single(dst)
498         );
499         // Doesn't panic with `NaiveDateTime::MAX` as argument (which would be out of range when
500         // converted to UTC).
501         assert_eq!(
502             lookup_with_dst_transitions(&transitions, NaiveDateTime::MAX),
503             MappedLocalTime::Ambiguous(dst, std)
504         );
505 
506         // Transition before UTC year end doesn't panic in year of `NaiveDate::MIN`
507         let std = FixedOffset::west_opt(3 * 60 * 60).unwrap();
508         let dst = FixedOffset::west_opt(4 * 60 * 60).unwrap();
509         let transitions = [
510             Transition::new(NaiveDateTime::MIN, std, dst),
511             Transition::new(NaiveDateTime::MIN.with_month(6).unwrap(), dst, std),
512         ];
513         assert_eq!(
514             lookup_with_dst_transitions(&transitions, NaiveDateTime::MIN.with_month(3).unwrap()),
515             MappedLocalTime::Single(dst)
516         );
517         assert_eq!(
518             lookup_with_dst_transitions(&transitions, NaiveDateTime::MIN.with_month(8).unwrap()),
519             MappedLocalTime::Single(std)
520         );
521         // Doesn't panic with `NaiveDateTime::MIN` as argument (which would be out of range when
522         // converted to UTC).
523         assert_eq!(
524             lookup_with_dst_transitions(&transitions, NaiveDateTime::MIN),
525             MappedLocalTime::Ambiguous(std, dst)
526         );
527     }
528 
529     #[test]
530     #[cfg(feature = "rkyv-validation")]
test_rkyv_validation()531     fn test_rkyv_validation() {
532         let local = Local;
533         // Local is a ZST and serializes to 0 bytes
534         let bytes = rkyv::to_bytes::<_, 0>(&local).unwrap();
535         assert_eq!(bytes.len(), 0);
536 
537         // but is deserialized to an archived variant without a
538         // wrapping object
539         assert_eq!(rkyv::from_bytes::<Local>(&bytes).unwrap(), super::ArchivedLocal);
540     }
541 }
542