chrono/offset/local/
mod.rs

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)]
7use std::cmp::Ordering;
8
9#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
10use rkyv::{Archive, Deserialize, Serialize};
11
12use super::fixed::FixedOffset;
13use super::{MappedLocalTime, TimeZone};
14use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime};
15#[allow(deprecated)]
16use crate::Date;
17use crate::{DateTime, Utc};
18
19#[cfg(unix)]
20#[path = "unix.rs"]
21mod inner;
22
23#[cfg(windows)]
24#[path = "windows.rs"]
25mod inner;
26
27#[cfg(all(windows, feature = "clock"))]
28#[allow(unreachable_pub)]
29mod 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))]
40mod inner {
41    use crate::{FixedOffset, MappedLocalTime, NaiveDateTime};
42
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
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))]
61mod inner {
62    use crate::{Datelike, FixedOffset, MappedLocalTime, NaiveDateTime, Timelike};
63
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
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)]
96mod 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))]
122pub struct Local;
123
124impl 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]
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    /// ```
160    pub fn now() -> DateTime<Local> {
161        Utc::now().with_timezone(&Local)
162    }
163}
164
165impl TimeZone for Local {
166    type Offset = FixedOffset;
167
168    fn from_offset(_offset: &FixedOffset) -> Local {
169        Local
170    }
171
172    #[allow(deprecated)]
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
178    fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> MappedLocalTime<FixedOffset> {
179        inner::offset_from_local_datetime(local)
180    }
181
182    #[allow(deprecated)]
183    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
188    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)]
195struct Transition {
196    transition_utc: NaiveDateTime,
197    offset_before: FixedOffset,
198    offset_after: FixedOffset,
199}
200
201#[cfg(windows)]
202impl Transition {
203    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)]
217impl PartialOrd for Transition {
218    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
219        Some(self.transition_utc.cmp(&other.transition_utc))
220    }
221}
222
223#[cfg(windows)]
224impl Ord for Transition {
225    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)]
233fn 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)]
275mod 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]
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]
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]
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]
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]
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)]
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)]
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")]
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}