1#[cfg(windows)]
7use core::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};
14#[allow(deprecated)]
15use crate::Date;
16use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime};
17use crate::offset::utc::Now;
18use crate::{DateTime, Utc};
19
20#[cfg(unix)]
21#[path = "unix.rs"]
22mod inner;
23
24#[cfg(windows)]
25#[path = "windows.rs"]
26mod inner;
27
28#[cfg(all(windows, feature = "clock"))]
29#[allow(unreachable_pub)]
30mod win_bindings;
31
32#[cfg(all(any(target_os = "android", target_env = "ohos", test), feature = "clock"))]
33mod tz_data;
34
35#[cfg(all(
36 not(unix),
37 not(windows),
38 not(all(
39 target_arch = "wasm32",
40 feature = "wasmbind",
41 not(any(target_os = "emscripten", target_os = "wasi"))
42 ))
43))]
44mod inner {
45 use crate::{FixedOffset, MappedLocalTime, NaiveDateTime};
46
47 pub(super) fn offset_from_utc_datetime(
48 _utc_time: &NaiveDateTime,
49 ) -> MappedLocalTime<FixedOffset> {
50 MappedLocalTime::Single(FixedOffset::east_opt(0).unwrap())
51 }
52
53 pub(super) fn offset_from_local_datetime(
54 _local_time: &NaiveDateTime,
55 ) -> MappedLocalTime<FixedOffset> {
56 MappedLocalTime::Single(FixedOffset::east_opt(0).unwrap())
57 }
58}
59
60#[cfg(all(
61 target_arch = "wasm32",
62 feature = "wasmbind",
63 not(any(target_os = "emscripten", target_os = "wasi", target_os = "linux"))
64))]
65mod inner {
66 use crate::{Datelike, FixedOffset, MappedLocalTime, NaiveDateTime, Timelike};
67
68 pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> MappedLocalTime<FixedOffset> {
69 let offset = js_sys::Date::from(utc.and_utc()).get_timezone_offset();
70 MappedLocalTime::Single(FixedOffset::west_opt((offset as i32) * 60).unwrap())
71 }
72
73 pub(super) fn offset_from_local_datetime(
74 local: &NaiveDateTime,
75 ) -> MappedLocalTime<FixedOffset> {
76 let mut year = local.year();
77 if year < 100 {
78 let shift_cycles = (year - 100).div_euclid(400);
82 year -= shift_cycles * 400;
83 }
84 let js_date = js_sys::Date::new_with_year_month_day_hr_min_sec(
85 year as u32,
86 local.month0() as i32,
87 local.day() as i32,
88 local.hour() as i32,
89 local.minute() as i32,
90 local.second() as i32,
91 );
93 let offset = js_date.get_timezone_offset();
94 MappedLocalTime::Single(FixedOffset::west_opt((offset as i32) * 60).unwrap())
96 }
97}
98
99#[cfg(unix)]
100mod tz_info;
101
102#[derive(Copy, Clone, Debug)]
118#[cfg_attr(
119 any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
120 derive(Archive, Deserialize, Serialize),
121 archive(compare(PartialEq)),
122 archive_attr(derive(Clone, Copy, Debug))
123)]
124#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))]
125#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
126#[cfg_attr(feature = "defmt", derive(defmt::Format))]
127pub struct Local;
128
129impl Local {
130 #[deprecated(since = "0.4.23", note = "use `Local::now::<StdNow>()` instead")]
132 #[allow(deprecated)]
133 #[must_use]
134 pub fn today<N: Now>() -> Date<Local> {
135 Local::now::<N>().date()
136 }
137
138 pub fn now<N: Now>() -> DateTime<Local> {
166 Utc::now::<N>().with_timezone(&Local)
167 }
168}
169
170impl TimeZone for Local {
171 type Offset = FixedOffset;
172
173 fn from_offset(_offset: &FixedOffset) -> Local {
174 Local
175 }
176
177 #[allow(deprecated)]
178 fn offset_from_local_date(&self, local: &NaiveDate) -> MappedLocalTime<FixedOffset> {
179 self.offset_from_local_datetime(&local.and_time(NaiveTime::MIN))
181 }
182
183 fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> MappedLocalTime<FixedOffset> {
184 inner::offset_from_local_datetime(local)
185 }
186
187 #[allow(deprecated)]
188 fn offset_from_utc_date(&self, utc: &NaiveDate) -> FixedOffset {
189 self.offset_from_utc_datetime(&utc.and_time(NaiveTime::MIN))
191 }
192
193 fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> FixedOffset {
194 inner::offset_from_utc_datetime(utc).unwrap()
195 }
196}
197
198#[cfg(windows)]
199#[derive(Copy, Clone, Eq, PartialEq)]
200struct Transition {
201 transition_utc: NaiveDateTime,
202 offset_before: FixedOffset,
203 offset_after: FixedOffset,
204}
205
206#[cfg(windows)]
207impl Transition {
208 fn new(
209 transition_local: NaiveDateTime,
210 offset_before: FixedOffset,
211 offset_after: FixedOffset,
212 ) -> Transition {
213 let transition_utc = transition_local.overflowing_sub_offset(offset_before);
217 Transition { transition_utc, offset_before, offset_after }
218 }
219}
220
221#[cfg(windows)]
222impl PartialOrd for Transition {
223 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
224 Some(self.cmp(other))
225 }
226}
227
228#[cfg(windows)]
229impl Ord for Transition {
230 fn cmp(&self, other: &Self) -> Ordering {
231 self.transition_utc.cmp(&other.transition_utc)
232 }
233}
234
235#[cfg(windows)]
238fn lookup_with_dst_transitions(
239 transitions: &[Transition],
240 dt: NaiveDateTime,
241) -> MappedLocalTime<FixedOffset> {
242 for t in transitions.iter() {
243 let (offset_min, offset_max) =
251 match t.offset_after.local_minus_utc() > t.offset_before.local_minus_utc() {
252 true => (t.offset_before, t.offset_after),
253 false => (t.offset_after, t.offset_before),
254 };
255 let wall_earliest = t.transition_utc.overflowing_add_offset(offset_min);
256 let wall_latest = t.transition_utc.overflowing_add_offset(offset_max);
257
258 if dt < wall_earliest {
259 return MappedLocalTime::Single(t.offset_before);
260 } else if dt <= wall_latest {
261 return match t.offset_after.local_minus_utc().cmp(&t.offset_before.local_minus_utc()) {
262 Ordering::Equal => MappedLocalTime::Single(t.offset_before),
263 Ordering::Less => MappedLocalTime::Ambiguous(t.offset_before, t.offset_after),
264 Ordering::Greater => {
265 if dt == wall_earliest {
266 MappedLocalTime::Single(t.offset_before)
267 } else if dt == wall_latest {
268 MappedLocalTime::Single(t.offset_after)
269 } else {
270 MappedLocalTime::None
271 }
272 }
273 };
274 }
275 }
276 MappedLocalTime::Single(transitions.last().unwrap().offset_after)
277}
278
279#[cfg(test)]
280mod tests {
281 use super::Local;
282 use crate::offset::TimeZone;
283 #[cfg(windows)]
284 use crate::offset::local::{Transition, lookup_with_dst_transitions};
285 use crate::{Datelike, Days, StdNow, Utc};
286 #[cfg(windows)]
287 use crate::{FixedOffset, MappedLocalTime, NaiveDate, NaiveDateTime};
288
289 #[test]
290 fn verify_correct_offsets() {
291 let now = Local::now::<StdNow>();
292 let from_local = Local.from_local_datetime(&now.naive_local()).unwrap();
293 let from_utc = Local.from_utc_datetime(&now.naive_utc());
294
295 assert_eq!(now.offset().local_minus_utc(), from_local.offset().local_minus_utc());
296 assert_eq!(now.offset().local_minus_utc(), from_utc.offset().local_minus_utc());
297
298 assert_eq!(now, from_local);
299 assert_eq!(now, from_utc);
300 }
301
302 #[test]
303 fn verify_correct_offsets_distant_past() {
304 let distant_past = Local::now::<StdNow>() - Days::new(365 * 500);
305 let from_local = Local.from_local_datetime(&distant_past.naive_local()).unwrap();
306 let from_utc = Local.from_utc_datetime(&distant_past.naive_utc());
307
308 assert_eq!(distant_past.offset().local_minus_utc(), from_local.offset().local_minus_utc());
309 assert_eq!(distant_past.offset().local_minus_utc(), from_utc.offset().local_minus_utc());
310
311 assert_eq!(distant_past, from_local);
312 assert_eq!(distant_past, from_utc);
313 }
314
315 #[test]
316 fn verify_correct_offsets_distant_future() {
317 let distant_future = Local::now::<StdNow>() + Days::new(365 * 35000);
318 let from_local = Local.from_local_datetime(&distant_future.naive_local()).unwrap();
319 let from_utc = Local.from_utc_datetime(&distant_future.naive_utc());
320
321 assert_eq!(
322 distant_future.offset().local_minus_utc(),
323 from_local.offset().local_minus_utc()
324 );
325 assert_eq!(distant_future.offset().local_minus_utc(), from_utc.offset().local_minus_utc());
326
327 assert_eq!(distant_future, from_local);
328 assert_eq!(distant_future, from_utc);
329 }
330
331 #[test]
332 fn test_local_date_sanity_check() {
333 assert_eq!(Local.with_ymd_and_hms(2999, 12, 28, 0, 0, 0).unwrap().day(), 28);
335 }
336
337 #[test]
338 fn test_leap_second() {
339 let today = Utc::now::<StdNow>().date_naive();
341
342 if let Some(dt) = today.and_hms_milli_opt(15, 2, 59, 1000) {
343 let timestr = dt.time().to_string();
344 assert!(
347 timestr == "15:02:60" || timestr == "15:03:00",
348 "unexpected timestr {timestr:?}"
349 );
350 }
351
352 if let Some(dt) = today.and_hms_milli_opt(15, 2, 3, 1234) {
353 let timestr = dt.time().to_string();
354 assert!(
355 timestr == "15:02:03.234" || timestr == "15:02:04.234",
356 "unexpected timestr {timestr:?}"
357 );
358 }
359 }
360
361 #[test]
362 #[cfg(windows)]
363 fn test_lookup_with_dst_transitions() {
364 let ymdhms = |y, m, d, h, n, s| {
365 NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap()
366 };
367
368 #[track_caller]
369 #[allow(clippy::too_many_arguments)]
370 fn compare_lookup(
371 transitions: &[Transition],
372 y: i32,
373 m: u32,
374 d: u32,
375 h: u32,
376 n: u32,
377 s: u32,
378 result: MappedLocalTime<FixedOffset>,
379 ) {
380 let dt = NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap();
381 assert_eq!(lookup_with_dst_transitions(transitions, dt), result);
382 }
383
384 let std = FixedOffset::east_opt(3 * 60 * 60).unwrap();
387 let dst = FixedOffset::east_opt(4 * 60 * 60).unwrap();
388 let transitions = [
389 Transition::new(ymdhms(2023, 3, 26, 2, 0, 0), std, dst),
390 Transition::new(ymdhms(2023, 10, 29, 3, 0, 0), dst, std),
391 ];
392 compare_lookup(&transitions, 2023, 3, 26, 1, 0, 0, MappedLocalTime::Single(std));
393 compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, MappedLocalTime::Single(std));
394 compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, MappedLocalTime::None);
395 compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, MappedLocalTime::Single(dst));
396 compare_lookup(&transitions, 2023, 3, 26, 4, 0, 0, MappedLocalTime::Single(dst));
397
398 compare_lookup(&transitions, 2023, 10, 29, 1, 0, 0, MappedLocalTime::Single(dst));
399 compare_lookup(&transitions, 2023, 10, 29, 2, 0, 0, MappedLocalTime::Ambiguous(dst, std));
400 compare_lookup(&transitions, 2023, 10, 29, 2, 30, 0, MappedLocalTime::Ambiguous(dst, std));
401 compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, MappedLocalTime::Ambiguous(dst, std));
402 compare_lookup(&transitions, 2023, 10, 29, 4, 0, 0, MappedLocalTime::Single(std));
403
404 let std = FixedOffset::east_opt(-5 * 60 * 60).unwrap();
407 let dst = FixedOffset::east_opt(-4 * 60 * 60).unwrap();
408 let transitions = [
409 Transition::new(ymdhms(2023, 3, 24, 3, 0, 0), dst, std),
410 Transition::new(ymdhms(2023, 10, 27, 2, 0, 0), std, dst),
411 ];
412 compare_lookup(&transitions, 2023, 3, 24, 1, 0, 0, MappedLocalTime::Single(dst));
413 compare_lookup(&transitions, 2023, 3, 24, 2, 0, 0, MappedLocalTime::Ambiguous(dst, std));
414 compare_lookup(&transitions, 2023, 3, 24, 2, 30, 0, MappedLocalTime::Ambiguous(dst, std));
415 compare_lookup(&transitions, 2023, 3, 24, 3, 0, 0, MappedLocalTime::Ambiguous(dst, std));
416 compare_lookup(&transitions, 2023, 3, 24, 4, 0, 0, MappedLocalTime::Single(std));
417
418 compare_lookup(&transitions, 2023, 10, 27, 1, 0, 0, MappedLocalTime::Single(std));
419 compare_lookup(&transitions, 2023, 10, 27, 2, 0, 0, MappedLocalTime::Single(std));
420 compare_lookup(&transitions, 2023, 10, 27, 2, 30, 0, MappedLocalTime::None);
421 compare_lookup(&transitions, 2023, 10, 27, 3, 0, 0, MappedLocalTime::Single(dst));
422 compare_lookup(&transitions, 2023, 10, 27, 4, 0, 0, MappedLocalTime::Single(dst));
423
424 let std = FixedOffset::east_opt(3 * 60 * 60).unwrap();
427 let dst = FixedOffset::east_opt((2 * 60 + 30) * 60).unwrap();
428 let transitions = [
429 Transition::new(ymdhms(2023, 3, 26, 2, 30, 0), std, dst),
430 Transition::new(ymdhms(2023, 10, 29, 2, 0, 0), dst, std),
431 ];
432 compare_lookup(&transitions, 2023, 3, 26, 1, 0, 0, MappedLocalTime::Single(std));
433 compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, MappedLocalTime::Ambiguous(std, dst));
434 compare_lookup(&transitions, 2023, 3, 26, 2, 15, 0, MappedLocalTime::Ambiguous(std, dst));
435 compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, MappedLocalTime::Ambiguous(std, dst));
436 compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, MappedLocalTime::Single(dst));
437
438 compare_lookup(&transitions, 2023, 10, 29, 1, 0, 0, MappedLocalTime::Single(dst));
439 compare_lookup(&transitions, 2023, 10, 29, 2, 0, 0, MappedLocalTime::Single(dst));
440 compare_lookup(&transitions, 2023, 10, 29, 2, 15, 0, MappedLocalTime::None);
441 compare_lookup(&transitions, 2023, 10, 29, 2, 30, 0, MappedLocalTime::Single(std));
442 compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, MappedLocalTime::Single(std));
443
444 let std = FixedOffset::east_opt(-(4 * 60 + 30) * 60).unwrap();
447 let dst = FixedOffset::east_opt(-5 * 60 * 60).unwrap();
448 let transitions = [
449 Transition::new(ymdhms(2023, 3, 24, 2, 0, 0), dst, std),
450 Transition::new(ymdhms(2023, 10, 27, 2, 30, 0), std, dst),
451 ];
452 compare_lookup(&transitions, 2023, 3, 24, 1, 0, 0, MappedLocalTime::Single(dst));
453 compare_lookup(&transitions, 2023, 3, 24, 2, 0, 0, MappedLocalTime::Single(dst));
454 compare_lookup(&transitions, 2023, 3, 24, 2, 15, 0, MappedLocalTime::None);
455 compare_lookup(&transitions, 2023, 3, 24, 2, 30, 0, MappedLocalTime::Single(std));
456 compare_lookup(&transitions, 2023, 3, 24, 3, 0, 0, MappedLocalTime::Single(std));
457
458 compare_lookup(&transitions, 2023, 10, 27, 1, 0, 0, MappedLocalTime::Single(std));
459 compare_lookup(&transitions, 2023, 10, 27, 2, 0, 0, MappedLocalTime::Ambiguous(std, dst));
460 compare_lookup(&transitions, 2023, 10, 27, 2, 15, 0, MappedLocalTime::Ambiguous(std, dst));
461 compare_lookup(&transitions, 2023, 10, 27, 2, 30, 0, MappedLocalTime::Ambiguous(std, dst));
462 compare_lookup(&transitions, 2023, 10, 27, 3, 0, 0, MappedLocalTime::Single(dst));
463
464 let std = FixedOffset::east_opt(3 * 60 * 60).unwrap();
466 let transitions = [
467 Transition::new(ymdhms(2023, 3, 26, 2, 0, 0), std, std),
468 Transition::new(ymdhms(2023, 10, 29, 3, 0, 0), std, std),
469 ];
470 compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, MappedLocalTime::Single(std));
471 compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, MappedLocalTime::Single(std));
472
473 let std = FixedOffset::east_opt(3 * 60 * 60).unwrap();
475 let dst = FixedOffset::east_opt(4 * 60 * 60).unwrap();
476 let transitions = [Transition::new(ymdhms(2023, 3, 26, 2, 0, 0), std, dst)];
477 compare_lookup(&transitions, 2023, 3, 26, 1, 0, 0, MappedLocalTime::Single(std));
478 compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, MappedLocalTime::Single(std));
479 compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, MappedLocalTime::None);
480 compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, MappedLocalTime::Single(dst));
481 compare_lookup(&transitions, 2023, 3, 26, 4, 0, 0, MappedLocalTime::Single(dst));
482 }
483
484 #[test]
485 #[cfg(windows)]
486 fn test_lookup_with_dst_transitions_limits() {
487 let std = FixedOffset::east_opt(3 * 60 * 60).unwrap();
489 let dst = FixedOffset::east_opt(4 * 60 * 60).unwrap();
490 let transitions = [
491 Transition::new(NaiveDateTime::MAX.with_month(7).unwrap(), std, dst),
492 Transition::new(NaiveDateTime::MAX, dst, std),
493 ];
494 assert_eq!(
495 lookup_with_dst_transitions(&transitions, NaiveDateTime::MAX.with_month(3).unwrap()),
496 MappedLocalTime::Single(std)
497 );
498 assert_eq!(
499 lookup_with_dst_transitions(&transitions, NaiveDateTime::MAX.with_month(8).unwrap()),
500 MappedLocalTime::Single(dst)
501 );
502 assert_eq!(
505 lookup_with_dst_transitions(&transitions, NaiveDateTime::MAX),
506 MappedLocalTime::Ambiguous(dst, std)
507 );
508
509 let std = FixedOffset::west_opt(3 * 60 * 60).unwrap();
511 let dst = FixedOffset::west_opt(4 * 60 * 60).unwrap();
512 let transitions = [
513 Transition::new(NaiveDateTime::MIN, std, dst),
514 Transition::new(NaiveDateTime::MIN.with_month(6).unwrap(), dst, std),
515 ];
516 assert_eq!(
517 lookup_with_dst_transitions(&transitions, NaiveDateTime::MIN.with_month(3).unwrap()),
518 MappedLocalTime::Single(dst)
519 );
520 assert_eq!(
521 lookup_with_dst_transitions(&transitions, NaiveDateTime::MIN.with_month(8).unwrap()),
522 MappedLocalTime::Single(std)
523 );
524 assert_eq!(
527 lookup_with_dst_transitions(&transitions, NaiveDateTime::MIN),
528 MappedLocalTime::Ambiguous(std, dst)
529 );
530 }
531
532 #[test]
533 #[cfg(feature = "rkyv-validation")]
534 fn test_rkyv_validation() {
535 let local = Local;
536 let bytes = rkyv::to_bytes::<_, 0>(&local).unwrap();
538 assert_eq!(bytes.len(), 0);
539
540 assert_eq!(rkyv::from_bytes::<Local>(&bytes).unwrap(), super::ArchivedLocal);
543 }
544}