1use crate::constants::*;
4use crate::datetime::{UtcDateTime, days_since_unix_epoch, is_leap_year};
5use crate::error::TzError;
6use crate::error::timezone::TransitionRuleError;
7use crate::timezone::LocalTimeType;
8use crate::utils::{binary_search_i64, cmp};
9
10use core::cmp::Ordering;
11
12#[derive(Debug, PartialEq, Eq)]
14struct JulianDayCheckInfos {
15 start_normal_year_offset: i64,
17 end_normal_year_offset: i64,
19 start_leap_year_offset: i64,
21 end_leap_year_offset: i64,
23}
24
25#[derive(Debug, PartialEq, Eq)]
27struct MonthWeekDayCheckInfos {
28 start_normal_year_offset_range: (i64, i64),
30 end_normal_year_offset_range: (i64, i64),
32 start_leap_year_offset_range: (i64, i64),
34 end_leap_year_offset_range: (i64, i64),
36}
37
38#[derive(Debug, Copy, Clone, Eq, PartialEq)]
40pub struct Julian1WithoutLeap(u16);
41
42impl Julian1WithoutLeap {
43 #[inline]
45 pub const fn new(julian_day_1: u16) -> Result<Self, TransitionRuleError> {
46 if !(1 <= julian_day_1 && julian_day_1 <= 365) {
47 return Err(TransitionRuleError::InvalidRuleDayJulianDay);
48 }
49
50 Ok(Self(julian_day_1))
51 }
52
53 #[inline]
55 pub const fn get(&self) -> u16 {
56 self.0
57 }
58
59 const fn transition_date(&self) -> (usize, i64) {
67 let year_day = self.0 as i64;
68
69 let month = match binary_search_i64(&CUMUL_DAYS_IN_MONTHS_NORMAL_YEAR, year_day - 1) {
70 Ok(x) => x + 1,
71 Err(x) => x,
72 };
73
74 let month_day = year_day - CUMUL_DAYS_IN_MONTHS_NORMAL_YEAR[month - 1];
75
76 (month, month_day)
77 }
78
79 const fn compute_check_infos(&self, utc_day_time: i64) -> JulianDayCheckInfos {
81 let start_normal_year_offset = (self.0 as i64 - 1) * SECONDS_PER_DAY + utc_day_time;
82 let start_leap_year_offset = if self.0 <= 59 { start_normal_year_offset } else { start_normal_year_offset + SECONDS_PER_DAY };
83
84 JulianDayCheckInfos {
85 start_normal_year_offset,
86 end_normal_year_offset: start_normal_year_offset - SECONDS_PER_NORMAL_YEAR,
87 start_leap_year_offset,
88 end_leap_year_offset: start_leap_year_offset - SECONDS_PER_LEAP_YEAR,
89 }
90 }
91}
92
93#[derive(Debug, Copy, Clone, Eq, PartialEq)]
95pub struct Julian0WithLeap(u16);
96
97impl Julian0WithLeap {
98 #[inline]
100 pub const fn new(julian_day_0: u16) -> Result<Self, TransitionRuleError> {
101 if julian_day_0 > 365 {
102 return Err(TransitionRuleError::InvalidRuleDayJulianDay);
103 }
104
105 Ok(Self(julian_day_0))
106 }
107
108 #[inline]
110 pub const fn get(&self) -> u16 {
111 self.0
112 }
113
114 const fn transition_date(&self, leap_year: bool) -> (usize, i64) {
124 let cumul_day_in_months = if leap_year { &CUMUL_DAYS_IN_MONTHS_LEAP_YEAR } else { &CUMUL_DAYS_IN_MONTHS_NORMAL_YEAR };
125
126 let year_day = self.0 as i64;
127
128 let month = match binary_search_i64(cumul_day_in_months, year_day) {
129 Ok(x) => x + 1,
130 Err(x) => x,
131 };
132
133 let month_day = 1 + year_day - cumul_day_in_months[month - 1];
134
135 (month, month_day)
136 }
137
138 const fn compute_check_infos(&self, utc_day_time: i64) -> JulianDayCheckInfos {
140 let start_year_offset = self.0 as i64 * SECONDS_PER_DAY + utc_day_time;
141
142 JulianDayCheckInfos {
143 start_normal_year_offset: start_year_offset,
144 end_normal_year_offset: start_year_offset - SECONDS_PER_NORMAL_YEAR,
145 start_leap_year_offset: start_year_offset,
146 end_leap_year_offset: start_year_offset - SECONDS_PER_LEAP_YEAR,
147 }
148 }
149}
150
151#[derive(Debug, Copy, Clone, Eq, PartialEq)]
153pub struct MonthWeekDay {
154 month: u8,
156 week: u8,
158 week_day: u8,
160}
161
162impl MonthWeekDay {
163 #[inline]
165 pub const fn new(month: u8, week: u8, week_day: u8) -> Result<Self, TransitionRuleError> {
166 if !(1 <= month && month <= 12) {
167 return Err(TransitionRuleError::InvalidRuleDayMonth);
168 }
169
170 if !(1 <= week && week <= 5) {
171 return Err(TransitionRuleError::InvalidRuleDayWeek);
172 }
173
174 if week_day > 6 {
175 return Err(TransitionRuleError::InvalidRuleDayWeekDay);
176 }
177
178 Ok(Self { month, week, week_day })
179 }
180
181 #[inline]
183 pub const fn month(&self) -> u8 {
184 self.month
185 }
186
187 #[inline]
189 pub const fn week(&self) -> u8 {
190 self.week
191 }
192
193 #[inline]
195 pub const fn week_day(&self) -> u8 {
196 self.week_day
197 }
198
199 const fn transition_date(&self, year: i32) -> (usize, i64) {
207 let month = self.month as usize;
208 let week = self.week as i64;
209 let week_day = self.week_day as i64;
210
211 let mut days_in_month = DAYS_IN_MONTHS_NORMAL_YEAR[month - 1];
212 if month == 2 {
213 days_in_month += is_leap_year(year) as i64;
214 }
215
216 let week_day_of_first_month_day = (4 + days_since_unix_epoch(year, month, 1)).rem_euclid(DAYS_PER_WEEK);
217 let first_week_day_occurence_in_month = 1 + (week_day - week_day_of_first_month_day).rem_euclid(DAYS_PER_WEEK);
218
219 let mut month_day = first_week_day_occurence_in_month + (week - 1) * DAYS_PER_WEEK;
220 if month_day > days_in_month {
221 month_day -= DAYS_PER_WEEK
222 }
223
224 (month, month_day)
225 }
226
227 const fn compute_check_infos(&self, utc_day_time: i64) -> MonthWeekDayCheckInfos {
229 let month = self.month as usize;
230 let week = self.week as i64;
231
232 let (normal_year_month_day_range, leap_year_month_day_range) = {
233 if week == 5 {
234 let normal_year_days_in_month = DAYS_IN_MONTHS_NORMAL_YEAR[month - 1];
235 let leap_year_days_in_month = if month == 2 { normal_year_days_in_month + 1 } else { normal_year_days_in_month };
236
237 let normal_year_month_day_range = (normal_year_days_in_month - 6, normal_year_days_in_month);
238 let leap_year_month_day_range = (leap_year_days_in_month - 6, leap_year_days_in_month);
239
240 (normal_year_month_day_range, leap_year_month_day_range)
241 } else {
242 let month_day_range = (week * DAYS_PER_WEEK - 6, week * DAYS_PER_WEEK);
243 (month_day_range, month_day_range)
244 }
245 };
246
247 let start_normal_year_offset_range = (
248 (CUMUL_DAYS_IN_MONTHS_NORMAL_YEAR[month - 1] + normal_year_month_day_range.0 - 1) * SECONDS_PER_DAY + utc_day_time,
249 (CUMUL_DAYS_IN_MONTHS_NORMAL_YEAR[month - 1] + normal_year_month_day_range.1 - 1) * SECONDS_PER_DAY + utc_day_time,
250 );
251
252 let start_leap_year_offset_range = (
253 (CUMUL_DAYS_IN_MONTHS_LEAP_YEAR[month - 1] + leap_year_month_day_range.0 - 1) * SECONDS_PER_DAY + utc_day_time,
254 (CUMUL_DAYS_IN_MONTHS_LEAP_YEAR[month - 1] + leap_year_month_day_range.1 - 1) * SECONDS_PER_DAY + utc_day_time,
255 );
256
257 MonthWeekDayCheckInfos {
258 start_normal_year_offset_range,
259 end_normal_year_offset_range: (
260 start_normal_year_offset_range.0 - SECONDS_PER_NORMAL_YEAR,
261 start_normal_year_offset_range.1 - SECONDS_PER_NORMAL_YEAR,
262 ),
263 start_leap_year_offset_range,
264 end_leap_year_offset_range: (start_leap_year_offset_range.0 - SECONDS_PER_LEAP_YEAR, start_leap_year_offset_range.1 - SECONDS_PER_LEAP_YEAR),
265 }
266 }
267}
268
269#[derive(Debug, Copy, Clone, Eq, PartialEq)]
271pub enum RuleDay {
272 Julian1WithoutLeap(Julian1WithoutLeap),
274 Julian0WithLeap(Julian0WithLeap),
276 MonthWeekDay(MonthWeekDay),
278}
279
280impl RuleDay {
281 const fn transition_date(&self, year: i32) -> (usize, i64) {
291 match self {
292 Self::Julian1WithoutLeap(rule_day) => rule_day.transition_date(),
293 Self::Julian0WithLeap(rule_day) => rule_day.transition_date(is_leap_year(year)),
294 Self::MonthWeekDay(rule_day) => rule_day.transition_date(year),
295 }
296 }
297
298 pub(crate) const fn unix_time(&self, year: i32, day_time_in_utc: i64) -> i64 {
300 let (month, month_day) = self.transition_date(year);
301 days_since_unix_epoch(year, month, month_day) * SECONDS_PER_DAY + day_time_in_utc
302 }
303}
304
305#[derive(Debug, Copy, Clone, Eq, PartialEq)]
307pub struct AlternateTime {
308 std: LocalTimeType,
310 dst: LocalTimeType,
312 dst_start: RuleDay,
314 dst_start_time: i32,
316 dst_end: RuleDay,
318 dst_end_time: i32,
320}
321
322impl AlternateTime {
323 pub const fn new(
325 std: LocalTimeType,
326 dst: LocalTimeType,
327 dst_start: RuleDay,
328 dst_start_time: i32,
329 dst_end: RuleDay,
330 dst_end_time: i32,
331 ) -> Result<Self, TransitionRuleError> {
332 let std_ut_offset = std.ut_offset as i64;
333 let dst_ut_offset = dst.ut_offset as i64;
334
335 if !(-25 * SECONDS_PER_HOUR < std_ut_offset && std_ut_offset < 26 * SECONDS_PER_HOUR) {
337 return Err(TransitionRuleError::InvalidStdUtcOffset);
338 }
339
340 if !(-25 * SECONDS_PER_HOUR < dst_ut_offset && dst_ut_offset < 26 * SECONDS_PER_HOUR) {
341 return Err(TransitionRuleError::InvalidDstUtcOffset);
342 }
343
344 if !((dst_start_time as i64).abs() < SECONDS_PER_WEEK && (dst_end_time as i64).abs() < SECONDS_PER_WEEK) {
346 return Err(TransitionRuleError::InvalidDstStartEndTime);
347 }
348
349 if !check_dst_transition_rules_consistency(&std, &dst, dst_start, dst_start_time, dst_end, dst_end_time) {
351 return Err(TransitionRuleError::InconsistentRule);
352 }
353
354 Ok(Self { std, dst, dst_start, dst_start_time, dst_end, dst_end_time })
355 }
356
357 #[inline]
359 pub const fn std(&self) -> &LocalTimeType {
360 &self.std
361 }
362
363 #[inline]
365 pub const fn dst(&self) -> &LocalTimeType {
366 &self.dst
367 }
368
369 #[inline]
371 pub const fn dst_start(&self) -> &RuleDay {
372 &self.dst_start
373 }
374
375 #[inline]
377 pub const fn dst_start_time(&self) -> i32 {
378 self.dst_start_time
379 }
380
381 #[inline]
383 pub const fn dst_end(&self) -> &RuleDay {
384 &self.dst_end
385 }
386
387 #[inline]
389 pub const fn dst_end_time(&self) -> i32 {
390 self.dst_end_time
391 }
392
393 const fn find_local_time_type(&self, unix_time: i64) -> Result<&LocalTimeType, TzError> {
395 let dst_start_time_in_utc = self.dst_start_time as i64 - self.std.ut_offset as i64;
397 let dst_end_time_in_utc = self.dst_end_time as i64 - self.dst.ut_offset as i64;
398
399 let current_year = match UtcDateTime::from_timespec(unix_time, 0) {
400 Ok(utc_date_time) => utc_date_time.year(),
401 Err(error) => return Err(error),
402 };
403
404 if !(i32::MIN + 2 <= current_year && current_year <= i32::MAX - 2) {
406 return Err(TzError::OutOfRange);
407 }
408
409 let current_year_dst_start_unix_time = self.dst_start.unix_time(current_year, dst_start_time_in_utc);
410 let current_year_dst_end_unix_time = self.dst_end.unix_time(current_year, dst_end_time_in_utc);
411
412 let is_dst = match cmp(current_year_dst_start_unix_time, current_year_dst_end_unix_time) {
416 Ordering::Less | Ordering::Equal => {
417 if unix_time < current_year_dst_start_unix_time {
418 let previous_year_dst_end_unix_time = self.dst_end.unix_time(current_year - 1, dst_end_time_in_utc);
419 if unix_time < previous_year_dst_end_unix_time {
420 let previous_year_dst_start_unix_time = self.dst_start.unix_time(current_year - 1, dst_start_time_in_utc);
421 previous_year_dst_start_unix_time <= unix_time
422 } else {
423 false
424 }
425 } else if unix_time < current_year_dst_end_unix_time {
426 true
427 } else {
428 let next_year_dst_start_unix_time = self.dst_start.unix_time(current_year + 1, dst_start_time_in_utc);
429 if next_year_dst_start_unix_time <= unix_time {
430 let next_year_dst_end_unix_time = self.dst_end.unix_time(current_year + 1, dst_end_time_in_utc);
431 unix_time < next_year_dst_end_unix_time
432 } else {
433 false
434 }
435 }
436 }
437 Ordering::Greater => {
438 if unix_time < current_year_dst_end_unix_time {
439 let previous_year_dst_start_unix_time = self.dst_start.unix_time(current_year - 1, dst_start_time_in_utc);
440 if unix_time < previous_year_dst_start_unix_time {
441 let previous_year_dst_end_unix_time = self.dst_end.unix_time(current_year - 1, dst_end_time_in_utc);
442 unix_time < previous_year_dst_end_unix_time
443 } else {
444 true
445 }
446 } else if unix_time < current_year_dst_start_unix_time {
447 false
448 } else {
449 let next_year_dst_end_unix_time = self.dst_end.unix_time(current_year + 1, dst_end_time_in_utc);
450 if next_year_dst_end_unix_time <= unix_time {
451 let next_year_dst_start_unix_time = self.dst_start.unix_time(current_year + 1, dst_start_time_in_utc);
452 next_year_dst_start_unix_time <= unix_time
453 } else {
454 true
455 }
456 }
457 }
458 };
459
460 if is_dst { Ok(&self.dst) } else { Ok(&self.std) }
461 }
462}
463
464#[derive(Debug, Copy, Clone, Eq, PartialEq)]
466pub enum TransitionRule {
467 Fixed(LocalTimeType),
469 Alternate(AlternateTime),
471}
472
473impl TransitionRule {
474 pub(super) const fn find_local_time_type(&self, unix_time: i64) -> Result<&LocalTimeType, TzError> {
476 match self {
477 Self::Fixed(local_time_type) => Ok(local_time_type),
478 Self::Alternate(alternate_time) => alternate_time.find_local_time_type(unix_time),
479 }
480 }
481}
482
483const fn check_dst_transition_rules_consistency(
488 std: &LocalTimeType,
489 dst: &LocalTimeType,
490 dst_start: RuleDay,
491 dst_start_time: i32,
492 dst_end: RuleDay,
493 dst_end_time: i32,
494) -> bool {
495 let dst_start_time_in_utc = dst_start_time as i64 - std.ut_offset as i64;
497 let dst_end_time_in_utc = dst_end_time as i64 - dst.ut_offset as i64;
498
499 match (dst_start, dst_end) {
500 (RuleDay::Julian1WithoutLeap(start_day), RuleDay::Julian1WithoutLeap(end_day)) => {
501 check_two_julian_days(start_day.compute_check_infos(dst_start_time_in_utc), end_day.compute_check_infos(dst_end_time_in_utc))
502 }
503 (RuleDay::Julian1WithoutLeap(start_day), RuleDay::Julian0WithLeap(end_day)) => {
504 check_two_julian_days(start_day.compute_check_infos(dst_start_time_in_utc), end_day.compute_check_infos(dst_end_time_in_utc))
505 }
506 (RuleDay::Julian0WithLeap(start_day), RuleDay::Julian1WithoutLeap(end_day)) => {
507 check_two_julian_days(start_day.compute_check_infos(dst_start_time_in_utc), end_day.compute_check_infos(dst_end_time_in_utc))
508 }
509 (RuleDay::Julian0WithLeap(start_day), RuleDay::Julian0WithLeap(end_day)) => {
510 check_two_julian_days(start_day.compute_check_infos(dst_start_time_in_utc), end_day.compute_check_infos(dst_end_time_in_utc))
511 }
512 (RuleDay::Julian1WithoutLeap(start_day), RuleDay::MonthWeekDay(end_day)) => {
513 check_month_week_day_and_julian_day(end_day.compute_check_infos(dst_end_time_in_utc), start_day.compute_check_infos(dst_start_time_in_utc))
514 }
515 (RuleDay::Julian0WithLeap(start_day), RuleDay::MonthWeekDay(end_day)) => {
516 check_month_week_day_and_julian_day(end_day.compute_check_infos(dst_end_time_in_utc), start_day.compute_check_infos(dst_start_time_in_utc))
517 }
518 (RuleDay::MonthWeekDay(start_day), RuleDay::Julian1WithoutLeap(end_day)) => {
519 check_month_week_day_and_julian_day(start_day.compute_check_infos(dst_start_time_in_utc), end_day.compute_check_infos(dst_end_time_in_utc))
520 }
521 (RuleDay::MonthWeekDay(start_day), RuleDay::Julian0WithLeap(end_day)) => {
522 check_month_week_day_and_julian_day(start_day.compute_check_infos(dst_start_time_in_utc), end_day.compute_check_infos(dst_end_time_in_utc))
523 }
524 (RuleDay::MonthWeekDay(start_day), RuleDay::MonthWeekDay(end_day)) => {
525 check_two_month_week_days(start_day, dst_start_time_in_utc, end_day, dst_end_time_in_utc)
526 }
527 }
528}
529
530const fn check_two_julian_days(check_infos_1: JulianDayCheckInfos, check_infos_2: JulianDayCheckInfos) -> bool {
532 let (before, after) = if check_infos_1.start_normal_year_offset <= check_infos_2.start_normal_year_offset
534 && check_infos_1.start_leap_year_offset <= check_infos_2.start_leap_year_offset
535 {
536 (&check_infos_1, &check_infos_2)
537 } else if check_infos_2.start_normal_year_offset <= check_infos_1.start_normal_year_offset
538 && check_infos_2.start_leap_year_offset <= check_infos_1.start_leap_year_offset
539 {
540 (&check_infos_2, &check_infos_1)
541 } else {
542 return false;
543 };
544
545 if after.end_normal_year_offset <= before.start_normal_year_offset
547 && after.end_normal_year_offset <= before.start_leap_year_offset
548 && after.end_leap_year_offset <= before.start_normal_year_offset
549 {
550 return true;
551 }
552
553 if before.start_normal_year_offset <= after.end_normal_year_offset
554 && before.start_leap_year_offset <= after.end_normal_year_offset
555 && before.start_normal_year_offset <= after.end_leap_year_offset
556 {
557 return true;
558 }
559
560 false
561}
562
563const fn check_month_week_day_and_julian_day(check_infos_1: MonthWeekDayCheckInfos, check_infos_2: JulianDayCheckInfos) -> bool {
565 if check_infos_2.start_normal_year_offset <= check_infos_1.start_normal_year_offset_range.0
567 && check_infos_2.start_leap_year_offset <= check_infos_1.start_leap_year_offset_range.0
568 {
569 let (before, after) = (&check_infos_2, &check_infos_1);
570
571 if after.end_normal_year_offset_range.1 <= before.start_normal_year_offset
572 && after.end_normal_year_offset_range.1 <= before.start_leap_year_offset
573 && after.end_leap_year_offset_range.1 <= before.start_normal_year_offset
574 {
575 return true;
576 };
577
578 if before.start_normal_year_offset <= after.end_normal_year_offset_range.0
579 && before.start_leap_year_offset <= after.end_normal_year_offset_range.0
580 && before.start_normal_year_offset <= after.end_leap_year_offset_range.0
581 {
582 return true;
583 };
584
585 return false;
586 }
587
588 if check_infos_1.start_normal_year_offset_range.1 <= check_infos_2.start_normal_year_offset
589 && check_infos_1.start_leap_year_offset_range.1 <= check_infos_2.start_leap_year_offset
590 {
591 let (before, after) = (&check_infos_1, &check_infos_2);
592
593 if after.end_normal_year_offset <= before.start_normal_year_offset_range.0
594 && after.end_normal_year_offset <= before.start_leap_year_offset_range.0
595 && after.end_leap_year_offset <= before.start_normal_year_offset_range.0
596 {
597 return true;
598 }
599
600 if before.start_normal_year_offset_range.1 <= after.end_normal_year_offset
601 && before.start_leap_year_offset_range.1 <= after.end_normal_year_offset
602 && before.start_normal_year_offset_range.1 <= after.end_leap_year_offset
603 {
604 return true;
605 }
606
607 return false;
608 }
609
610 false
611}
612
613const fn check_two_month_week_days(month_week_day_1: MonthWeekDay, utc_day_time_1: i64, month_week_day_2: MonthWeekDay, utc_day_time_2: i64) -> bool {
615 let (month_week_day_before, utc_day_time_before, month_week_day_after, utc_day_time_after) = {
617 let rem = (month_week_day_2.month as i64 - month_week_day_1.month as i64).rem_euclid(MONTHS_PER_YEAR);
618
619 if rem == 0 {
620 if month_week_day_1.week <= month_week_day_2.week {
621 (month_week_day_1, utc_day_time_1, month_week_day_2, utc_day_time_2)
622 } else {
623 (month_week_day_2, utc_day_time_2, month_week_day_1, utc_day_time_1)
624 }
625 } else if rem == 1 {
626 (month_week_day_1, utc_day_time_1, month_week_day_2, utc_day_time_2)
627 } else if rem == MONTHS_PER_YEAR - 1 {
628 (month_week_day_2, utc_day_time_2, month_week_day_1, utc_day_time_1)
629 } else {
630 return true;
632 }
633 };
634
635 let month_before = month_week_day_before.month as usize;
636 let week_before = month_week_day_before.week as i64;
637 let week_day_before = month_week_day_before.week_day as i64;
638
639 let month_after = month_week_day_after.month as usize;
640 let week_after = month_week_day_after.week as i64;
641 let week_day_after = month_week_day_after.week_day as i64;
642
643 let (diff_days_min, diff_days_max) = if week_day_before == week_day_after {
644 let (diff_week_min, diff_week_max) = match (week_before, week_after) {
646 (1..=4, 5) if month_before == month_after => (4 - week_before, 5 - week_before),
648 (1..=4, 1..=4) if month_before != month_after => (4 - week_before + week_after, 5 - week_before + week_after),
649 _ => return true, };
651
652 (diff_week_min * DAYS_PER_WEEK, diff_week_max * DAYS_PER_WEEK)
653 } else {
654 let n = (week_day_after - week_day_before).rem_euclid(DAYS_PER_WEEK); if month_before == month_after {
658 match (week_before, week_after) {
659 (5, 5) => (n - DAYS_PER_WEEK, n),
660 (1..=4, 1..=4) => (n + DAYS_PER_WEEK * (week_after - week_before - 1), n + DAYS_PER_WEEK * (week_after - week_before)),
661 (1..=4, 5) => {
662 let days_in_month = DAYS_IN_MONTHS_NORMAL_YEAR[month_before - 1];
668
669 match cmp(n, days_in_month % DAYS_PER_WEEK) {
670 Ordering::Less => (n + DAYS_PER_WEEK * (4 - week_before), n + DAYS_PER_WEEK * (5 - week_before)),
671 Ordering::Equal => return true, Ordering::Greater => (n + DAYS_PER_WEEK * (3 - week_before), n + DAYS_PER_WEEK * (4 - week_before)),
673 }
674 }
675 _ => unreachable!(),
676 }
677 } else {
678 match (week_before, week_after) {
680 (1..=4, 1..=4) => {
681 let days_in_month = DAYS_IN_MONTHS_NORMAL_YEAR[month_before - 1];
683
684 match cmp(n, days_in_month % DAYS_PER_WEEK) {
685 Ordering::Less => (n + DAYS_PER_WEEK * (4 - week_before + week_after), n + DAYS_PER_WEEK * (5 - week_before + week_after)),
686 Ordering::Equal => return true, Ordering::Greater => (n + DAYS_PER_WEEK * (3 - week_before + week_after), n + DAYS_PER_WEEK * (4 - week_before + week_after)),
688 }
689 }
690 (5, 1..=4) => (n + DAYS_PER_WEEK * (week_after - 1), n + DAYS_PER_WEEK * week_after),
691 _ => return true, }
693 }
694 };
695
696 let diff_days_seconds_min = diff_days_min * SECONDS_PER_DAY;
697 let diff_days_seconds_max = diff_days_max * SECONDS_PER_DAY;
698
699 utc_day_time_before <= diff_days_seconds_min + utc_day_time_after || diff_days_seconds_max + utc_day_time_after <= utc_day_time_before
701}
702
703#[cfg(test)]
704mod tests {
705 use super::*;
706 use crate::TzError;
707
708 #[test]
709 fn test_compute_check_infos() -> Result<(), TzError> {
710 let check_julian = |check_infos: JulianDayCheckInfos, start_normal, end_normal, start_leap, end_leap| {
711 assert_eq!(check_infos.start_normal_year_offset, start_normal);
712 assert_eq!(check_infos.end_normal_year_offset, end_normal);
713 assert_eq!(check_infos.start_leap_year_offset, start_leap);
714 assert_eq!(check_infos.end_leap_year_offset, end_leap);
715 };
716
717 let check_mwd = |check_infos: MonthWeekDayCheckInfos, start_normal, end_normal, start_leap, end_leap| {
718 assert_eq!(check_infos.start_normal_year_offset_range, start_normal);
719 assert_eq!(check_infos.end_normal_year_offset_range, end_normal);
720 assert_eq!(check_infos.start_leap_year_offset_range, start_leap);
721 assert_eq!(check_infos.end_leap_year_offset_range, end_leap);
722 };
723
724 check_julian(Julian1WithoutLeap::new(1)?.compute_check_infos(1), 1, -31535999, 1, -31622399);
725 check_julian(Julian1WithoutLeap::new(365)?.compute_check_infos(1), 31449601, -86399, 31536001, -86399);
726
727 check_julian(Julian0WithLeap::new(0)?.compute_check_infos(1), 1, -31535999, 1, -31622399);
728 check_julian(Julian0WithLeap::new(365)?.compute_check_infos(1), 31536001, 1, 31536001, -86399);
729
730 check_mwd(MonthWeekDay::new(1, 1, 0)?.compute_check_infos(1), (1, 518401), (-31535999, -31017599), (1, 518401), (-31622399, -31103999));
731 check_mwd(MonthWeekDay::new(1, 5, 0)?.compute_check_infos(1), (2073601, 2592001), (-29462399, -28943999), (2073601, 2592001), (-29548799, -29030399));
732 check_mwd(MonthWeekDay::new(2, 4, 0)?.compute_check_infos(1), (4492801, 5011201), (-27043199, -26524799), (4492801, 5011201), (-27129599, -26611199));
733 check_mwd(MonthWeekDay::new(2, 5, 0)?.compute_check_infos(1), (4492801, 5011201), (-27043199, -26524799), (4579201, 5097601), (-27043199, -26524799));
734 check_mwd(MonthWeekDay::new(3, 1, 0)?.compute_check_infos(1), (5097601, 5616001), (-26438399, -25919999), (5184001, 5702401), (-26438399, -25919999));
735 check_mwd(MonthWeekDay::new(3, 5, 0)?.compute_check_infos(1), (7171201, 7689601), (-24364799, -23846399), (7257601, 7776001), (-24364799, -23846399));
736 check_mwd(MonthWeekDay::new(12, 5, 0)?.compute_check_infos(1), (30931201, 31449601), (-604799, -86399), (31017601, 31536001), (-604799, -86399));
737
738 Ok(())
739 }
740
741 #[test]
742 fn test_check_dst_transition_rules_consistency() -> Result<(), TzError> {
743 let utc = LocalTimeType::utc();
744
745 let julian_1 = |year_day| -> Result<_, TzError> { Ok(RuleDay::Julian1WithoutLeap(Julian1WithoutLeap::new(year_day)?)) };
746 let julian_0 = |year_day| -> Result<_, TzError> { Ok(RuleDay::Julian0WithLeap(Julian0WithLeap::new(year_day)?)) };
747 let mwd = |month, week, week_day| -> Result<_, TzError> { Ok(RuleDay::MonthWeekDay(MonthWeekDay::new(month, week, week_day)?)) };
748
749 let check = |dst_start, dst_start_time, dst_end, dst_end_time| {
750 let check_1 = check_dst_transition_rules_consistency(&utc, &utc, dst_start, dst_start_time, dst_end, dst_end_time);
751 let check_2 = check_dst_transition_rules_consistency(&utc, &utc, dst_end, dst_end_time, dst_start, dst_start_time);
752 assert_eq!(check_1, check_2);
753
754 check_1
755 };
756
757 let check_all = |dst_start, dst_start_times: &[i32], dst_end, dst_end_time, results: &[bool]| {
758 assert_eq!(dst_start_times.len(), results.len());
759
760 for (&dst_start_time, &result) in dst_start_times.iter().zip(results) {
761 assert_eq!(check(dst_start, dst_start_time, dst_end, dst_end_time), result);
762 }
763 };
764
765 const DAY_1: i32 = 86400;
766 const DAY_2: i32 = 2 * DAY_1;
767 const DAY_3: i32 = 3 * DAY_1;
768 const DAY_4: i32 = 4 * DAY_1;
769 const DAY_5: i32 = 5 * DAY_1;
770 const DAY_6: i32 = 6 * DAY_1;
771
772 check_all(julian_1(59)?, &[-1, 0, 1], julian_1(60)?, -DAY_1, &[true, true, false]);
773 check_all(julian_1(365)?, &[-1, 0, 1], julian_1(1)?, -DAY_1, &[true, true, true]);
774
775 check_all(julian_0(58)?, &[-1, 0, 1], julian_0(59)?, -DAY_1, &[true, true, true]);
776 check_all(julian_0(364)?, &[-1, 0, 1], julian_0(0)?, -DAY_1, &[true, true, false]);
777 check_all(julian_0(365)?, &[-1, 0, 1], julian_0(0)?, 0, &[true, true, false]);
778
779 check_all(julian_1(90)?, &[-1, 0, 1], julian_0(90)?, 0, &[true, true, false]);
780 check_all(julian_1(365)?, &[-1, 0, 1], julian_0(0)?, 0, &[true, true, true]);
781
782 check_all(julian_0(89)?, &[-1, 0, 1], julian_1(90)?, 0, &[true, true, false]);
783 check_all(julian_0(364)?, &[-1, 0, 1], julian_1(1)?, -DAY_1, &[true, true, false]);
784 check_all(julian_0(365)?, &[-1, 0, 1], julian_1(1)?, 0, &[true, true, false]);
785
786 check_all(mwd(1, 4, 0)?, &[-1, 0, 1], julian_1(28)?, 0, &[true, true, false]);
787 check_all(mwd(2, 5, 0)?, &[-1, 0, 1], julian_1(60)?, -DAY_1, &[true, true, false]);
788 check_all(mwd(12, 5, 0)?, &[-1, 0, 1], julian_1(1)?, -DAY_1, &[true, true, false]);
789 check_all(mwd(12, 5, 0)?, &[DAY_3 - 1, DAY_3, DAY_3 + 1], julian_1(1)?, -DAY_4, &[false, true, true]);
790
791 check_all(mwd(1, 4, 0)?, &[-1, 0, 1], julian_0(27)?, 0, &[true, true, false]);
792 check_all(mwd(2, 5, 0)?, &[-1, 0, 1], julian_0(58)?, DAY_1, &[true, true, false]);
793 check_all(mwd(2, 4, 0)?, &[-1, 0, 1], julian_0(59)?, -DAY_1, &[true, true, false]);
794 check_all(mwd(2, 5, 0)?, &[-1, 0, 1], julian_0(59)?, 0, &[true, true, false]);
795 check_all(mwd(12, 5, 0)?, &[-1, 0, 1], julian_0(0)?, -DAY_1, &[true, true, false]);
796 check_all(mwd(12, 5, 0)?, &[DAY_3 - 1, DAY_3, DAY_3 + 1], julian_0(0)?, -DAY_4, &[false, true, true]);
797
798 check_all(julian_1(1)?, &[-1, 0, 1], mwd(1, 1, 0)?, 0, &[true, true, false]);
799 check_all(julian_1(53)?, &[-1, 0, 1], mwd(2, 5, 0)?, 0, &[true, true, false]);
800 check_all(julian_1(365)?, &[-1, 0, 1], mwd(1, 1, 0)?, -DAY_1, &[true, true, false]);
801 check_all(julian_1(365)?, &[DAY_3 - 1, DAY_3, DAY_3 + 1], mwd(1, 1, 0)?, -DAY_4, &[false, true, true]);
802
803 check_all(julian_0(0)?, &[-1, 0, 1], mwd(1, 1, 0)?, 0, &[true, true, false]);
804 check_all(julian_0(52)?, &[-1, 0, 1], mwd(2, 5, 0)?, 0, &[true, true, false]);
805 check_all(julian_0(59)?, &[-1, 0, 1], mwd(3, 1, 0)?, 0, &[true, true, false]);
806 check_all(julian_0(59)?, &[-DAY_3 - 1, -DAY_3, -DAY_3 + 1], mwd(2, 5, 0)?, DAY_4, &[true, true, false]);
807 check_all(julian_0(364)?, &[-1, 0, 1], mwd(1, 1, 0)?, -DAY_1, &[true, true, false]);
808 check_all(julian_0(365)?, &[-1, 0, 1], mwd(1, 1, 0)?, 0, &[true, true, false]);
809 check_all(julian_0(364)?, &[DAY_4 - 1, DAY_4, DAY_4 + 1], mwd(1, 1, 0)?, -DAY_4, &[false, true, true]);
810 check_all(julian_0(365)?, &[DAY_3 - 1, DAY_3, DAY_3 + 1], mwd(1, 1, 0)?, -DAY_4, &[false, true, true]);
811
812 let months_per_year = MONTHS_PER_YEAR as u8;
813 for i in 0..months_per_year - 1 {
814 let month = i + 1;
815 let month_1 = (i + 1) % months_per_year + 1;
816 let month_2 = (i + 2) % months_per_year + 1;
817
818 assert!(check(mwd(month, 1, 0)?, 0, mwd(month_2, 1, 0)?, 0));
819 assert!(check(mwd(month, 3, 0)?, DAY_4, mwd(month, 4, 0)?, -DAY_3));
820
821 check_all(mwd(month, 5, 0)?, &[-1, 0, 1], mwd(month, 5, 0)?, 0, &[true, true, true]);
822 check_all(mwd(month, 4, 0)?, &[-1, 0, 1], mwd(month, 5, 0)?, 0, &[true, true, false]);
823 check_all(mwd(month, 4, 0)?, &[DAY_4 - 1, DAY_4, DAY_4 + 1], mwd(month_1, 1, 0)?, -DAY_3, &[true, true, false]);
824 check_all(mwd(month, 5, 0)?, &[DAY_4 - 1, DAY_4, DAY_4 + 1], mwd(month_1, 1, 0)?, -DAY_3, &[true, true, true]);
825 check_all(mwd(month, 5, 0)?, &[-1, 0, 1], mwd(month_1, 5, 0)?, 0, &[true, true, true]);
826 check_all(mwd(month, 3, 2)?, &[-1, 0, 1], mwd(month, 4, 3)?, -DAY_1, &[true, true, false]);
827 check_all(mwd(month, 5, 2)?, &[-1, 0, 1], mwd(month, 5, 3)?, -DAY_1, &[false, true, true]);
828 check_all(mwd(month, 5, 2)?, &[-1, 0, 1], mwd(month_1, 1, 3)?, -DAY_1, &[true, true, false]);
829 check_all(mwd(month, 5, 2)?, &[-1, 0, 1], mwd(month_1, 5, 3)?, 0, &[true, true, true]);
830 }
831
832 check_all(mwd(2, 4, 2)?, &[-1, 0, 1], mwd(2, 5, 3)?, -DAY_1, &[false, true, true]);
833
834 check_all(mwd(3, 4, 2)?, &[-1, 0, 1], mwd(3, 5, 4)?, -DAY_2, &[true, true, false]);
835 check_all(mwd(3, 4, 2)?, &[-1, 0, 1], mwd(3, 5, 5)?, -DAY_3, &[true, true, true]);
836 check_all(mwd(3, 4, 2)?, &[-1, 0, 1], mwd(3, 5, 6)?, -DAY_4, &[false, true, true]);
837
838 check_all(mwd(4, 4, 2)?, &[-1, 0, 1], mwd(4, 5, 3)?, -DAY_1, &[true, true, false]);
839 check_all(mwd(4, 4, 2)?, &[-1, 0, 1], mwd(4, 5, 4)?, -DAY_2, &[true, true, true]);
840 check_all(mwd(4, 4, 2)?, &[-1, 0, 1], mwd(4, 5, 5)?, -DAY_3, &[false, true, true]);
841
842 check_all(mwd(2, 4, 2)?, &[DAY_5 - 1, DAY_5, DAY_5 + 1], mwd(3, 1, 3)?, -DAY_3, &[false, true, true]);
843
844 check_all(mwd(3, 4, 2)?, &[DAY_5 - 1, DAY_5, DAY_5 + 1], mwd(4, 1, 4)?, -DAY_4, &[true, true, false]);
845 check_all(mwd(3, 4, 2)?, &[DAY_5 - 1, DAY_5, DAY_5 + 1], mwd(4, 1, 5)?, -DAY_5, &[true, true, true]);
846 check_all(mwd(3, 4, 2)?, &[DAY_5 - 1, DAY_5, DAY_5 + 1], mwd(4, 1, 6)?, -DAY_6, &[false, true, true]);
847
848 check_all(mwd(4, 4, 2)?, &[DAY_5 - 1, DAY_5, DAY_5 + 1], mwd(5, 1, 3)?, -DAY_3, &[true, true, false]);
849 check_all(mwd(4, 4, 2)?, &[DAY_5 - 1, DAY_5, DAY_5 + 1], mwd(5, 1, 4)?, -DAY_4, &[true, true, true]);
850 check_all(mwd(4, 4, 2)?, &[DAY_5 - 1, DAY_5, DAY_5 + 1], mwd(5, 1, 5)?, -DAY_5, &[false, true, true]);
851
852 Ok(())
853 }
854
855 #[test]
856 fn test_rule_day() -> Result<(), TzError> {
857 let rule_day_j1 = RuleDay::Julian1WithoutLeap(Julian1WithoutLeap::new(60)?);
858 assert_eq!(rule_day_j1.transition_date(2000), (3, 1));
859 assert_eq!(rule_day_j1.transition_date(2001), (3, 1));
860 assert_eq!(rule_day_j1.unix_time(2000, 43200), 951912000);
861
862 let rule_day_j0 = RuleDay::Julian0WithLeap(Julian0WithLeap::new(59)?);
863 assert_eq!(rule_day_j0.transition_date(2000), (2, 29));
864 assert_eq!(rule_day_j0.transition_date(2001), (3, 1));
865 assert_eq!(rule_day_j0.unix_time(2000, 43200), 951825600);
866
867 let rule_day_j0_max = RuleDay::Julian0WithLeap(Julian0WithLeap::new(365)?);
868 assert_eq!(rule_day_j0_max.transition_date(2000), (12, 31));
869 assert_eq!(rule_day_j0_max.transition_date(2001), (12, 32));
870
871 assert_eq!(
872 RuleDay::Julian0WithLeap(Julian0WithLeap::new(365)?).unix_time(2000, 0),
873 RuleDay::Julian1WithoutLeap(Julian1WithoutLeap::new(365)?).unix_time(2000, 0)
874 );
875
876 assert_eq!(
877 RuleDay::Julian0WithLeap(Julian0WithLeap::new(365)?).unix_time(1999, 0),
878 RuleDay::Julian1WithoutLeap(Julian1WithoutLeap::new(1)?).unix_time(2000, 0),
879 );
880
881 let rule_day_mwd = RuleDay::MonthWeekDay(MonthWeekDay::new(2, 5, 2)?);
882 assert_eq!(rule_day_mwd.transition_date(2000), (2, 29));
883 assert_eq!(rule_day_mwd.transition_date(2001), (2, 27));
884 assert_eq!(rule_day_mwd.unix_time(2000, 43200), 951825600);
885 assert_eq!(rule_day_mwd.unix_time(2001, 43200), 983275200);
886
887 Ok(())
888 }
889
890 #[test]
891 fn test_transition_rule() -> Result<(), TzError> {
892 let transition_rule_fixed = TransitionRule::Fixed(LocalTimeType::new(-36000, false, None)?);
893 assert_eq!(transition_rule_fixed.find_local_time_type(0)?.ut_offset(), -36000);
894
895 let transition_rule_dst = TransitionRule::Alternate(AlternateTime::new(
896 LocalTimeType::new(43200, false, Some(b"NZST"))?,
897 LocalTimeType::new(46800, true, Some(b"NZDT"))?,
898 RuleDay::MonthWeekDay(MonthWeekDay::new(10, 1, 0)?),
899 7200,
900 RuleDay::MonthWeekDay(MonthWeekDay::new(3, 3, 0)?),
901 7200,
902 )?);
903
904 assert_eq!(transition_rule_dst.find_local_time_type(953384399)?.ut_offset(), 46800);
905 assert_eq!(transition_rule_dst.find_local_time_type(953384400)?.ut_offset(), 43200);
906 assert_eq!(transition_rule_dst.find_local_time_type(970322399)?.ut_offset(), 43200);
907 assert_eq!(transition_rule_dst.find_local_time_type(970322400)?.ut_offset(), 46800);
908
909 let transition_rule_negative_dst = TransitionRule::Alternate(AlternateTime::new(
910 LocalTimeType::new(3600, false, Some(b"IST"))?,
911 LocalTimeType::new(0, true, Some(b"GMT"))?,
912 RuleDay::MonthWeekDay(MonthWeekDay::new(10, 5, 0)?),
913 7200,
914 RuleDay::MonthWeekDay(MonthWeekDay::new(3, 5, 0)?),
915 3600,
916 )?);
917
918 assert_eq!(transition_rule_negative_dst.find_local_time_type(954032399)?.ut_offset(), 0);
919 assert_eq!(transition_rule_negative_dst.find_local_time_type(954032400)?.ut_offset(), 3600);
920 assert_eq!(transition_rule_negative_dst.find_local_time_type(972781199)?.ut_offset(), 3600);
921 assert_eq!(transition_rule_negative_dst.find_local_time_type(972781200)?.ut_offset(), 0);
922
923 let transition_rule_negative_time_1 = TransitionRule::Alternate(AlternateTime::new(
924 LocalTimeType::new(0, false, None)?,
925 LocalTimeType::new(0, true, None)?,
926 RuleDay::Julian0WithLeap(Julian0WithLeap::new(100)?),
927 0,
928 RuleDay::Julian0WithLeap(Julian0WithLeap::new(101)?),
929 -86500,
930 )?);
931
932 assert!(transition_rule_negative_time_1.find_local_time_type(8639899)?.is_dst());
933 assert!(!transition_rule_negative_time_1.find_local_time_type(8639900)?.is_dst());
934 assert!(!transition_rule_negative_time_1.find_local_time_type(8639999)?.is_dst());
935 assert!(transition_rule_negative_time_1.find_local_time_type(8640000)?.is_dst());
936
937 let transition_rule_negative_time_2 = TransitionRule::Alternate(AlternateTime::new(
938 LocalTimeType::new(-10800, false, Some(b"-03"))?,
939 LocalTimeType::new(-7200, true, Some(b"-02"))?,
940 RuleDay::MonthWeekDay(MonthWeekDay::new(3, 5, 0)?),
941 -7200,
942 RuleDay::MonthWeekDay(MonthWeekDay::new(10, 5, 0)?),
943 -3600,
944 )?);
945
946 assert_eq!(transition_rule_negative_time_2.find_local_time_type(954032399)?.ut_offset(), -10800);
947 assert_eq!(transition_rule_negative_time_2.find_local_time_type(954032400)?.ut_offset(), -7200);
948 assert_eq!(transition_rule_negative_time_2.find_local_time_type(972781199)?.ut_offset(), -7200);
949 assert_eq!(transition_rule_negative_time_2.find_local_time_type(972781200)?.ut_offset(), -10800);
950
951 let transition_rule_all_year_dst = TransitionRule::Alternate(AlternateTime::new(
952 LocalTimeType::new(-18000, false, Some(b"EST"))?,
953 LocalTimeType::new(-14400, true, Some(b"EDT"))?,
954 RuleDay::Julian0WithLeap(Julian0WithLeap::new(0)?),
955 0,
956 RuleDay::Julian1WithoutLeap(Julian1WithoutLeap::new(365)?),
957 90000,
958 )?);
959
960 assert_eq!(transition_rule_all_year_dst.find_local_time_type(946702799)?.ut_offset(), -14400);
961 assert_eq!(transition_rule_all_year_dst.find_local_time_type(946702800)?.ut_offset(), -14400);
962
963 Ok(())
964 }
965
966 #[test]
967 fn test_transition_rule_overflow() -> Result<(), TzError> {
968 let transition_rule_1 = TransitionRule::Alternate(AlternateTime::new(
969 LocalTimeType::new(-1, false, None)?,
970 LocalTimeType::new(-1, true, None)?,
971 RuleDay::Julian1WithoutLeap(Julian1WithoutLeap::new(365)?),
972 0,
973 RuleDay::Julian1WithoutLeap(Julian1WithoutLeap::new(1)?),
974 0,
975 )?);
976
977 let transition_rule_2 = TransitionRule::Alternate(AlternateTime::new(
978 LocalTimeType::new(1, false, None)?,
979 LocalTimeType::new(1, true, None)?,
980 RuleDay::Julian1WithoutLeap(Julian1WithoutLeap::new(365)?),
981 0,
982 RuleDay::Julian1WithoutLeap(Julian1WithoutLeap::new(1)?),
983 0,
984 )?);
985
986 assert!(matches!(transition_rule_1.find_local_time_type(i64::MIN), Err(TzError::OutOfRange)));
987 assert!(matches!(transition_rule_2.find_local_time_type(i64::MAX), Err(TzError::OutOfRange)));
988
989 Ok(())
990 }
991}