1use super::parser::Cursor;
2use super::timezone::{LocalTimeType, SECONDS_PER_WEEK};
3use super::{
4 CUMUL_DAY_IN_MONTHS_NORMAL_YEAR, DAY_IN_MONTHS_NORMAL_YEAR, DAYS_PER_WEEK, Error,
5 SECONDS_PER_DAY,
6};
7use crate::{Datelike, NaiveDateTime};
8use std::cmp::Ordering;
9
10#[derive(#[automatically_derived]
impl ::core::fmt::Debug for TransitionRule {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match self {
TransitionRule::Fixed(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "Fixed",
&__self_0),
TransitionRule::Alternate(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"Alternate", &__self_0),
}
}
}Debug, #[automatically_derived]
impl ::core::marker::Copy for TransitionRule { }Copy, #[automatically_derived]
impl ::core::clone::Clone for TransitionRule {
#[inline]
fn clone(&self) -> TransitionRule {
let _: ::core::clone::AssertParamIsClone<LocalTimeType>;
let _: ::core::clone::AssertParamIsClone<AlternateTime>;
*self
}
}Clone, #[automatically_derived]
impl ::core::cmp::Eq for TransitionRule {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<LocalTimeType>;
let _: ::core::cmp::AssertParamIsEq<AlternateTime>;
}
}Eq, #[automatically_derived]
impl ::core::cmp::PartialEq for TransitionRule {
#[inline]
fn eq(&self, other: &TransitionRule) -> bool {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
__self_discr == __arg1_discr &&
match (self, other) {
(TransitionRule::Fixed(__self_0),
TransitionRule::Fixed(__arg1_0)) => __self_0 == __arg1_0,
(TransitionRule::Alternate(__self_0),
TransitionRule::Alternate(__arg1_0)) =>
__self_0 == __arg1_0,
_ => unsafe { ::core::intrinsics::unreachable() }
}
}
}PartialEq)]
12pub(super) enum TransitionRule {
13 Fixed(LocalTimeType),
15 Alternate(AlternateTime),
17}
18
19impl TransitionRule {
20 pub(super) fn from_tz_string(
24 tz_string: &[u8],
25 use_string_extensions: bool,
26 ) -> Result<Self, Error> {
27 let mut cursor = Cursor::new(tz_string);
28
29 let std_time_zone = Some(parse_name(&mut cursor)?);
30 let std_offset = parse_offset(&mut cursor)?;
31
32 if cursor.is_empty() {
33 return Ok(LocalTimeType::new(-std_offset, false, std_time_zone)?.into());
34 }
35
36 let dst_time_zone = Some(parse_name(&mut cursor)?);
37
38 let dst_offset = match cursor.peek() {
39 Some(&b',') => std_offset - 3600,
40 Some(_) => parse_offset(&mut cursor)?,
41 None => {
42 return Err(Error::UnsupportedTzString("DST start and end rules must be provided"));
43 }
44 };
45
46 if cursor.is_empty() {
47 return Err(Error::UnsupportedTzString("DST start and end rules must be provided"));
48 }
49
50 cursor.read_tag(b",")?;
51 let (dst_start, dst_start_time) = RuleDay::parse(&mut cursor, use_string_extensions)?;
52
53 cursor.read_tag(b",")?;
54 let (dst_end, dst_end_time) = RuleDay::parse(&mut cursor, use_string_extensions)?;
55
56 if !cursor.is_empty() {
57 return Err(Error::InvalidTzString("remaining data after parsing TZ string"));
58 }
59
60 Ok(AlternateTime::new(
61 LocalTimeType::new(-std_offset, false, std_time_zone)?,
62 LocalTimeType::new(-dst_offset, true, dst_time_zone)?,
63 dst_start,
64 dst_start_time,
65 dst_end,
66 dst_end_time,
67 )?
68 .into())
69 }
70
71 pub(super) fn find_local_time_type(&self, unix_time: i64) -> Result<&LocalTimeType, Error> {
73 match self {
74 TransitionRule::Fixed(local_time_type) => Ok(local_time_type),
75 TransitionRule::Alternate(alternate_time) => {
76 alternate_time.find_local_time_type(unix_time)
77 }
78 }
79 }
80
81 pub(super) fn find_local_time_type_from_local(
83 &self,
84 local_time: NaiveDateTime,
85 ) -> Result<crate::MappedLocalTime<LocalTimeType>, Error> {
86 match self {
87 TransitionRule::Fixed(local_time_type) => {
88 Ok(crate::MappedLocalTime::Single(*local_time_type))
89 }
90 TransitionRule::Alternate(alternate_time) => {
91 alternate_time.find_local_time_type_from_local(local_time)
92 }
93 }
94 }
95}
96
97impl From<LocalTimeType> for TransitionRule {
98 fn from(inner: LocalTimeType) -> Self {
99 TransitionRule::Fixed(inner)
100 }
101}
102
103impl From<AlternateTime> for TransitionRule {
104 fn from(inner: AlternateTime) -> Self {
105 TransitionRule::Alternate(inner)
106 }
107}
108
109#[derive(#[automatically_derived]
impl ::core::fmt::Debug for AlternateTime {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
let names: &'static _ =
&["std", "dst", "dst_start", "dst_start_time", "dst_end",
"dst_end_time"];
let values: &[&dyn ::core::fmt::Debug] =
&[&self.std, &self.dst, &self.dst_start, &self.dst_start_time,
&self.dst_end, &&self.dst_end_time];
::core::fmt::Formatter::debug_struct_fields_finish(f, "AlternateTime",
names, values)
}
}Debug, #[automatically_derived]
impl ::core::marker::Copy for AlternateTime { }Copy, #[automatically_derived]
impl ::core::clone::Clone for AlternateTime {
#[inline]
fn clone(&self) -> AlternateTime {
let _: ::core::clone::AssertParamIsClone<LocalTimeType>;
let _: ::core::clone::AssertParamIsClone<RuleDay>;
let _: ::core::clone::AssertParamIsClone<i32>;
*self
}
}Clone, #[automatically_derived]
impl ::core::cmp::Eq for AlternateTime {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<LocalTimeType>;
let _: ::core::cmp::AssertParamIsEq<RuleDay>;
let _: ::core::cmp::AssertParamIsEq<i32>;
}
}Eq, #[automatically_derived]
impl ::core::cmp::PartialEq for AlternateTime {
#[inline]
fn eq(&self, other: &AlternateTime) -> bool {
self.dst_start_time == other.dst_start_time &&
self.dst_end_time == other.dst_end_time &&
self.std == other.std && self.dst == other.dst &&
self.dst_start == other.dst_start &&
self.dst_end == other.dst_end
}
}PartialEq)]
111pub(super) struct AlternateTime {
112 pub(super) std: LocalTimeType,
114 pub(super) dst: LocalTimeType,
116 dst_start: RuleDay,
118 dst_start_time: i32,
120 dst_end: RuleDay,
122 dst_end_time: i32,
124}
125
126impl AlternateTime {
127 const fn new(
129 std: LocalTimeType,
130 dst: LocalTimeType,
131 dst_start: RuleDay,
132 dst_start_time: i32,
133 dst_end: RuleDay,
134 dst_end_time: i32,
135 ) -> Result<Self, Error> {
136 if !((dst_start_time as i64).abs() < SECONDS_PER_WEEK
138 && (dst_end_time as i64).abs() < SECONDS_PER_WEEK)
139 {
140 return Err(Error::TransitionRule("invalid DST start or end time"));
141 }
142
143 Ok(Self { std, dst, dst_start, dst_start_time, dst_end, dst_end_time })
144 }
145
146 fn find_local_time_type(&self, unix_time: i64) -> Result<&LocalTimeType, Error> {
148 let dst_start_time_in_utc = self.dst_start_time as i64 - self.std.ut_offset as i64;
150 let dst_end_time_in_utc = self.dst_end_time as i64 - self.dst.ut_offset as i64;
151
152 let current_year = match UtcDateTime::from_timespec(unix_time) {
153 Ok(dt) => dt.year,
154 Err(error) => return Err(error),
155 };
156
157 if !(i32::MIN + 2..=i32::MAX - 2).contains(¤t_year) {
159 return Err(Error::OutOfRange("out of range date time"));
160 }
161
162 let current_year_dst_start_unix_time =
163 self.dst_start.unix_time(current_year, dst_start_time_in_utc);
164 let current_year_dst_end_unix_time =
165 self.dst_end.unix_time(current_year, dst_end_time_in_utc);
166
167 let is_dst =
169 match Ord::cmp(¤t_year_dst_start_unix_time, ¤t_year_dst_end_unix_time) {
170 Ordering::Less | Ordering::Equal => {
171 if unix_time < current_year_dst_start_unix_time {
172 let previous_year_dst_end_unix_time =
173 self.dst_end.unix_time(current_year - 1, dst_end_time_in_utc);
174 if unix_time < previous_year_dst_end_unix_time {
175 let previous_year_dst_start_unix_time =
176 self.dst_start.unix_time(current_year - 1, dst_start_time_in_utc);
177 previous_year_dst_start_unix_time <= unix_time
178 } else {
179 false
180 }
181 } else if unix_time < current_year_dst_end_unix_time {
182 true
183 } else {
184 let next_year_dst_start_unix_time =
185 self.dst_start.unix_time(current_year + 1, dst_start_time_in_utc);
186 if next_year_dst_start_unix_time <= unix_time {
187 let next_year_dst_end_unix_time =
188 self.dst_end.unix_time(current_year + 1, dst_end_time_in_utc);
189 unix_time < next_year_dst_end_unix_time
190 } else {
191 false
192 }
193 }
194 }
195 Ordering::Greater => {
196 if unix_time < current_year_dst_end_unix_time {
197 let previous_year_dst_start_unix_time =
198 self.dst_start.unix_time(current_year - 1, dst_start_time_in_utc);
199 if unix_time < previous_year_dst_start_unix_time {
200 let previous_year_dst_end_unix_time =
201 self.dst_end.unix_time(current_year - 1, dst_end_time_in_utc);
202 unix_time < previous_year_dst_end_unix_time
203 } else {
204 true
205 }
206 } else if unix_time < current_year_dst_start_unix_time {
207 false
208 } else {
209 let next_year_dst_end_unix_time =
210 self.dst_end.unix_time(current_year + 1, dst_end_time_in_utc);
211 if next_year_dst_end_unix_time <= unix_time {
212 let next_year_dst_start_unix_time =
213 self.dst_start.unix_time(current_year + 1, dst_start_time_in_utc);
214 next_year_dst_start_unix_time <= unix_time
215 } else {
216 true
217 }
218 }
219 }
220 };
221
222 if is_dst { Ok(&self.dst) } else { Ok(&self.std) }
223 }
224
225 fn find_local_time_type_from_local(
226 &self,
227 local_time: NaiveDateTime,
228 ) -> Result<crate::MappedLocalTime<LocalTimeType>, Error> {
229 let current_year = local_time.year();
231 let local_time = local_time.and_utc().timestamp();
232
233 let dst_start_transition_start =
234 self.dst_start.unix_time(current_year, 0) + i64::from(self.dst_start_time);
235 let dst_start_transition_end = self.dst_start.unix_time(current_year, 0)
236 + i64::from(self.dst_start_time)
237 + i64::from(self.dst.ut_offset)
238 - i64::from(self.std.ut_offset);
239
240 let dst_end_transition_start =
241 self.dst_end.unix_time(current_year, 0) + i64::from(self.dst_end_time);
242 let dst_end_transition_end = self.dst_end.unix_time(current_year, 0)
243 + i64::from(self.dst_end_time)
244 + i64::from(self.std.ut_offset)
245 - i64::from(self.dst.ut_offset);
246
247 match self.std.ut_offset.cmp(&self.dst.ut_offset) {
248 Ordering::Equal => Ok(crate::MappedLocalTime::Single(self.std)),
249 Ordering::Less => {
250 if self.dst_start.transition_date(current_year).0
251 < self.dst_end.transition_date(current_year).0
252 {
253 if local_time <= dst_start_transition_start {
256 Ok(crate::MappedLocalTime::Single(self.std))
257 } else if local_time > dst_start_transition_start
258 && local_time < dst_start_transition_end
259 {
260 Ok(crate::MappedLocalTime::None)
261 } else if local_time >= dst_start_transition_end
262 && local_time < dst_end_transition_end
263 {
264 Ok(crate::MappedLocalTime::Single(self.dst))
265 } else if local_time >= dst_end_transition_end
266 && local_time <= dst_end_transition_start
267 {
268 Ok(crate::MappedLocalTime::Ambiguous(self.std, self.dst))
269 } else {
270 Ok(crate::MappedLocalTime::Single(self.std))
271 }
272 } else {
273 if local_time < dst_end_transition_end {
276 Ok(crate::MappedLocalTime::Single(self.dst))
277 } else if local_time >= dst_end_transition_end
278 && local_time <= dst_end_transition_start
279 {
280 Ok(crate::MappedLocalTime::Ambiguous(self.std, self.dst))
281 } else if local_time > dst_end_transition_end
282 && local_time < dst_start_transition_start
283 {
284 Ok(crate::MappedLocalTime::Single(self.std))
285 } else if local_time >= dst_start_transition_start
286 && local_time < dst_start_transition_end
287 {
288 Ok(crate::MappedLocalTime::None)
289 } else {
290 Ok(crate::MappedLocalTime::Single(self.dst))
291 }
292 }
293 }
294 Ordering::Greater => {
295 if self.dst_start.transition_date(current_year).0
296 < self.dst_end.transition_date(current_year).0
297 {
298 if local_time < dst_start_transition_end {
301 Ok(crate::MappedLocalTime::Single(self.std))
302 } else if local_time >= dst_start_transition_end
303 && local_time <= dst_start_transition_start
304 {
305 Ok(crate::MappedLocalTime::Ambiguous(self.dst, self.std))
306 } else if local_time > dst_start_transition_start
307 && local_time < dst_end_transition_start
308 {
309 Ok(crate::MappedLocalTime::Single(self.dst))
310 } else if local_time >= dst_end_transition_start
311 && local_time < dst_end_transition_end
312 {
313 Ok(crate::MappedLocalTime::None)
314 } else {
315 Ok(crate::MappedLocalTime::Single(self.std))
316 }
317 } else {
318 if local_time <= dst_end_transition_start {
321 Ok(crate::MappedLocalTime::Single(self.dst))
322 } else if local_time > dst_end_transition_start
323 && local_time < dst_end_transition_end
324 {
325 Ok(crate::MappedLocalTime::None)
326 } else if local_time >= dst_end_transition_end
327 && local_time < dst_start_transition_end
328 {
329 Ok(crate::MappedLocalTime::Single(self.std))
330 } else if local_time >= dst_start_transition_end
331 && local_time <= dst_start_transition_start
332 {
333 Ok(crate::MappedLocalTime::Ambiguous(self.dst, self.std))
334 } else {
335 Ok(crate::MappedLocalTime::Single(self.dst))
336 }
337 }
338 }
339 }
340 }
341}
342
343fn parse_name<'a>(cursor: &mut Cursor<'a>) -> Result<&'a [u8], Error> {
345 match cursor.peek() {
346 Some(b'<') => {}
347 _ => return Ok(cursor.read_while(u8::is_ascii_alphabetic)?),
348 }
349
350 cursor.read_exact(1)?;
351 let unquoted = cursor.read_until(|&x| x == b'>')?;
352 cursor.read_exact(1)?;
353 Ok(unquoted)
354}
355
356fn parse_offset(cursor: &mut Cursor) -> Result<i32, Error> {
358 let (sign, hour, minute, second) = parse_signed_hhmmss(cursor)?;
359
360 if !(0..=23).contains(&hour) {
362 return Err(Error::InvalidTzString("invalid offset hour"));
363 }
364 if !(0..=59).contains(&minute) {
365 return Err(Error::InvalidTzString("invalid offset minute"));
366 }
367 if !(0..=59).contains(&second) {
368 return Err(Error::InvalidTzString("invalid offset second"));
369 }
370
371 Ok(sign * (hour * 3600 + minute * 60 + second))
372}
373
374fn parse_rule_time(cursor: &mut Cursor) -> Result<i32, Error> {
376 let (hour, minute, second) = parse_hhmmss(cursor)?;
377
378 if !(0..=24).contains(&hour) {
379 return Err(Error::InvalidTzString("invalid day time hour"));
380 }
381 if !(0..=59).contains(&minute) {
382 return Err(Error::InvalidTzString("invalid day time minute"));
383 }
384 if !(0..=59).contains(&second) {
385 return Err(Error::InvalidTzString("invalid day time second"));
386 }
387
388 Ok(hour * 3600 + minute * 60 + second)
389}
390
391fn parse_rule_time_extended(cursor: &mut Cursor) -> Result<i32, Error> {
393 let (sign, hour, minute, second) = parse_signed_hhmmss(cursor)?;
394
395 if !(-167..=167).contains(&hour) {
396 return Err(Error::InvalidTzString("invalid day time hour"));
397 }
398 if !(0..=59).contains(&minute) {
399 return Err(Error::InvalidTzString("invalid day time minute"));
400 }
401 if !(0..=59).contains(&second) {
402 return Err(Error::InvalidTzString("invalid day time second"));
403 }
404
405 Ok(sign * (hour * 3600 + minute * 60 + second))
406}
407
408fn parse_hhmmss(cursor: &mut Cursor) -> Result<(i32, i32, i32), Error> {
410 let hour = cursor.read_int()?;
411
412 let mut minute = 0;
413 let mut second = 0;
414
415 if cursor.read_optional_tag(b":")? {
416 minute = cursor.read_int()?;
417
418 if cursor.read_optional_tag(b":")? {
419 second = cursor.read_int()?;
420 }
421 }
422
423 Ok((hour, minute, second))
424}
425
426fn parse_signed_hhmmss(cursor: &mut Cursor) -> Result<(i32, i32, i32, i32), Error> {
428 let mut sign = 1;
429 if let Some(&c) = cursor.peek() {
430 if c == b'+' || c == b'-' {
431 cursor.read_exact(1)?;
432 if c == b'-' {
433 sign = -1;
434 }
435 }
436 }
437
438 let (hour, minute, second) = parse_hhmmss(cursor)?;
439 Ok((sign, hour, minute, second))
440}
441
442#[derive(#[automatically_derived]
impl ::core::fmt::Debug for RuleDay {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match self {
RuleDay::Julian1WithoutLeap(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"Julian1WithoutLeap", &__self_0),
RuleDay::Julian0WithLeap(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"Julian0WithLeap", &__self_0),
RuleDay::MonthWeekday {
month: __self_0, week: __self_1, week_day: __self_2 } =>
::core::fmt::Formatter::debug_struct_field3_finish(f,
"MonthWeekday", "month", __self_0, "week", __self_1,
"week_day", &__self_2),
}
}
}Debug, #[automatically_derived]
impl ::core::marker::Copy for RuleDay { }Copy, #[automatically_derived]
impl ::core::clone::Clone for RuleDay {
#[inline]
fn clone(&self) -> RuleDay {
let _: ::core::clone::AssertParamIsClone<u16>;
let _: ::core::clone::AssertParamIsClone<u8>;
*self
}
}Clone, #[automatically_derived]
impl ::core::cmp::Eq for RuleDay {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<u16>;
let _: ::core::cmp::AssertParamIsEq<u8>;
}
}Eq, #[automatically_derived]
impl ::core::cmp::PartialEq for RuleDay {
#[inline]
fn eq(&self, other: &RuleDay) -> bool {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
__self_discr == __arg1_discr &&
match (self, other) {
(RuleDay::Julian1WithoutLeap(__self_0),
RuleDay::Julian1WithoutLeap(__arg1_0)) =>
__self_0 == __arg1_0,
(RuleDay::Julian0WithLeap(__self_0),
RuleDay::Julian0WithLeap(__arg1_0)) => __self_0 == __arg1_0,
(RuleDay::MonthWeekday {
month: __self_0, week: __self_1, week_day: __self_2 },
RuleDay::MonthWeekday {
month: __arg1_0, week: __arg1_1, week_day: __arg1_2 }) =>
__self_0 == __arg1_0 && __self_1 == __arg1_1 &&
__self_2 == __arg1_2,
_ => unsafe { ::core::intrinsics::unreachable() }
}
}
}PartialEq)]
444enum RuleDay {
445 Julian1WithoutLeap(u16),
447 Julian0WithLeap(u16),
449 MonthWeekday {
451 month: u8,
453 week: u8,
455 week_day: u8,
457 },
458}
459
460impl RuleDay {
461 fn parse(cursor: &mut Cursor, use_string_extensions: bool) -> Result<(Self, i32), Error> {
463 let date = match cursor.peek() {
464 Some(b'M') => {
465 cursor.read_exact(1)?;
466 let month = cursor.read_int()?;
467 cursor.read_tag(b".")?;
468 let week = cursor.read_int()?;
469 cursor.read_tag(b".")?;
470 let week_day = cursor.read_int()?;
471 RuleDay::month_weekday(month, week, week_day)?
472 }
473 Some(b'J') => {
474 cursor.read_exact(1)?;
475 RuleDay::julian_1(cursor.read_int()?)?
476 }
477 _ => RuleDay::julian_0(cursor.read_int()?)?,
478 };
479
480 Ok((
481 date,
482 match (cursor.read_optional_tag(b"/")?, use_string_extensions) {
483 (false, _) => 2 * 3600,
484 (true, true) => parse_rule_time_extended(cursor)?,
485 (true, false) => parse_rule_time(cursor)?,
486 },
487 ))
488 }
489
490 fn julian_1(julian_day_1: u16) -> Result<Self, Error> {
492 if !(1..=365).contains(&julian_day_1) {
493 return Err(Error::TransitionRule("invalid rule day julian day"));
494 }
495
496 Ok(RuleDay::Julian1WithoutLeap(julian_day_1))
497 }
498
499 const fn julian_0(julian_day_0: u16) -> Result<Self, Error> {
501 if julian_day_0 > 365 {
502 return Err(Error::TransitionRule("invalid rule day julian day"));
503 }
504
505 Ok(RuleDay::Julian0WithLeap(julian_day_0))
506 }
507
508 fn month_weekday(month: u8, week: u8, week_day: u8) -> Result<Self, Error> {
510 if !(1..=12).contains(&month) {
511 return Err(Error::TransitionRule("invalid rule day month"));
512 }
513
514 if !(1..=5).contains(&week) {
515 return Err(Error::TransitionRule("invalid rule day week"));
516 }
517
518 if week_day > 6 {
519 return Err(Error::TransitionRule("invalid rule day week day"));
520 }
521
522 Ok(RuleDay::MonthWeekday { month, week, week_day })
523 }
524
525 fn transition_date(&self, year: i32) -> (usize, i64) {
532 match *self {
533 RuleDay::Julian1WithoutLeap(year_day) => {
534 let year_day = year_day as i64;
535
536 let month = match CUMUL_DAY_IN_MONTHS_NORMAL_YEAR.binary_search(&(year_day - 1)) {
537 Ok(x) => x + 1,
538 Err(x) => x,
539 };
540
541 let month_day = year_day - CUMUL_DAY_IN_MONTHS_NORMAL_YEAR[month - 1];
542
543 (month, month_day)
544 }
545 RuleDay::Julian0WithLeap(year_day) => {
546 let leap = is_leap_year(year) as i64;
547
548 let cumul_day_in_months = [
549 0,
550 31,
551 59 + leap,
552 90 + leap,
553 120 + leap,
554 151 + leap,
555 181 + leap,
556 212 + leap,
557 243 + leap,
558 273 + leap,
559 304 + leap,
560 334 + leap,
561 ];
562
563 let year_day = year_day as i64;
564
565 let month = match cumul_day_in_months.binary_search(&year_day) {
566 Ok(x) => x + 1,
567 Err(x) => x,
568 };
569
570 let month_day = 1 + year_day - cumul_day_in_months[month - 1];
571
572 (month, month_day)
573 }
574 RuleDay::MonthWeekday { month: rule_month, week, week_day } => {
575 let leap = is_leap_year(year) as i64;
576
577 let month = rule_month as usize;
578
579 let mut day_in_month = DAY_IN_MONTHS_NORMAL_YEAR[month - 1];
580 if month == 2 {
581 day_in_month += leap;
582 }
583
584 let week_day_of_first_month_day =
585 (4 + days_since_unix_epoch(year, month, 1)).rem_euclid(DAYS_PER_WEEK);
586 let first_week_day_occurrence_in_month =
587 1 + (week_day as i64 - week_day_of_first_month_day).rem_euclid(DAYS_PER_WEEK);
588
589 let mut month_day =
590 first_week_day_occurrence_in_month + (week as i64 - 1) * DAYS_PER_WEEK;
591 if month_day > day_in_month {
592 month_day -= DAYS_PER_WEEK
593 }
594
595 (month, month_day)
596 }
597 }
598 }
599
600 fn unix_time(&self, year: i32, day_time_in_utc: i64) -> i64 {
602 let (month, month_day) = self.transition_date(year);
603 days_since_unix_epoch(year, month, month_day) * SECONDS_PER_DAY + day_time_in_utc
604 }
605}
606
607#[derive(#[automatically_derived]
impl ::core::fmt::Debug for UtcDateTime {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
let names: &'static _ =
&["year", "month", "month_day", "hour", "minute", "second"];
let values: &[&dyn ::core::fmt::Debug] =
&[&self.year, &self.month, &self.month_day, &self.hour,
&self.minute, &&self.second];
::core::fmt::Formatter::debug_struct_fields_finish(f, "UtcDateTime",
names, values)
}
}Debug, #[automatically_derived]
impl ::core::marker::Copy for UtcDateTime { }Copy, #[automatically_derived]
impl ::core::clone::Clone for UtcDateTime {
#[inline]
fn clone(&self) -> UtcDateTime {
let _: ::core::clone::AssertParamIsClone<i32>;
let _: ::core::clone::AssertParamIsClone<u8>;
*self
}
}Clone, #[automatically_derived]
impl ::core::cmp::Eq for UtcDateTime {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<i32>;
let _: ::core::cmp::AssertParamIsEq<u8>;
}
}Eq, #[automatically_derived]
impl ::core::cmp::PartialEq for UtcDateTime {
#[inline]
fn eq(&self, other: &UtcDateTime) -> bool {
self.year == other.year && self.month == other.month &&
self.month_day == other.month_day && self.hour == other.hour
&& self.minute == other.minute && self.second == other.second
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Ord for UtcDateTime {
#[inline]
fn cmp(&self, other: &UtcDateTime) -> ::core::cmp::Ordering {
match ::core::cmp::Ord::cmp(&self.year, &other.year) {
::core::cmp::Ordering::Equal =>
match ::core::cmp::Ord::cmp(&self.month, &other.month) {
::core::cmp::Ordering::Equal =>
match ::core::cmp::Ord::cmp(&self.month_day,
&other.month_day) {
::core::cmp::Ordering::Equal =>
match ::core::cmp::Ord::cmp(&self.hour, &other.hour) {
::core::cmp::Ordering::Equal =>
match ::core::cmp::Ord::cmp(&self.minute, &other.minute) {
::core::cmp::Ordering::Equal =>
::core::cmp::Ord::cmp(&self.second, &other.second),
cmp => cmp,
},
cmp => cmp,
},
cmp => cmp,
},
cmp => cmp,
},
cmp => cmp,
}
}
}Ord, #[automatically_derived]
impl ::core::cmp::PartialOrd for UtcDateTime {
#[inline]
fn partial_cmp(&self, other: &UtcDateTime)
-> ::core::option::Option<::core::cmp::Ordering> {
::core::option::Option::Some(::core::cmp::Ord::cmp(self, other))
}
}PartialOrd)]
609pub(crate) struct UtcDateTime {
610 pub(crate) year: i32,
612 pub(crate) month: u8,
614 pub(crate) month_day: u8,
616 pub(crate) hour: u8,
618 pub(crate) minute: u8,
620 pub(crate) second: u8,
622}
623
624impl UtcDateTime {
625 pub(crate) fn from_timespec(unix_time: i64) -> Result<Self, Error> {
627 let seconds = match unix_time.checked_sub(UNIX_OFFSET_SECS) {
628 Some(seconds) => seconds,
629 None => return Err(Error::OutOfRange("out of range operation")),
630 };
631
632 let mut remaining_days = seconds / SECONDS_PER_DAY;
633 let mut remaining_seconds = seconds % SECONDS_PER_DAY;
634 if remaining_seconds < 0 {
635 remaining_seconds += SECONDS_PER_DAY;
636 remaining_days -= 1;
637 }
638
639 let mut cycles_400_years = remaining_days / DAYS_PER_400_YEARS;
640 remaining_days %= DAYS_PER_400_YEARS;
641 if remaining_days < 0 {
642 remaining_days += DAYS_PER_400_YEARS;
643 cycles_400_years -= 1;
644 }
645
646 let cycles_100_years = Ord::min(remaining_days / DAYS_PER_100_YEARS, 3);
647 remaining_days -= cycles_100_years * DAYS_PER_100_YEARS;
648
649 let cycles_4_years = Ord::min(remaining_days / DAYS_PER_4_YEARS, 24);
650 remaining_days -= cycles_4_years * DAYS_PER_4_YEARS;
651
652 let remaining_years = Ord::min(remaining_days / DAYS_PER_NORMAL_YEAR, 3);
653 remaining_days -= remaining_years * DAYS_PER_NORMAL_YEAR;
654
655 let mut year = OFFSET_YEAR
656 + remaining_years
657 + cycles_4_years * 4
658 + cycles_100_years * 100
659 + cycles_400_years * 400;
660
661 let mut month = 0;
662 while month < DAY_IN_MONTHS_LEAP_YEAR_FROM_MARCH.len() {
663 let days = DAY_IN_MONTHS_LEAP_YEAR_FROM_MARCH[month];
664 if remaining_days < days {
665 break;
666 }
667 remaining_days -= days;
668 month += 1;
669 }
670 month += 2;
671
672 if month >= MONTHS_PER_YEAR as usize {
673 month -= MONTHS_PER_YEAR as usize;
674 year += 1;
675 }
676 month += 1;
677
678 let month_day = 1 + remaining_days;
679
680 let hour = remaining_seconds / SECONDS_PER_HOUR;
681 let minute = (remaining_seconds / SECONDS_PER_MINUTE) % MINUTES_PER_HOUR;
682 let second = remaining_seconds % SECONDS_PER_MINUTE;
683
684 let year = match year >= i32::MIN as i64 && year <= i32::MAX as i64 {
685 true => year as i32,
686 false => return Err(Error::OutOfRange("i64 is out of range for i32")),
687 };
688
689 Ok(Self {
690 year,
691 month: month as u8,
692 month_day: month_day as u8,
693 hour: hour as u8,
694 minute: minute as u8,
695 second: second as u8,
696 })
697 }
698}
699
700const NANOSECONDS_PER_SECOND: u32 = 1_000_000_000;
702const SECONDS_PER_MINUTE: i64 = 60;
704const SECONDS_PER_HOUR: i64 = 3600;
706const MINUTES_PER_HOUR: i64 = 60;
708const MONTHS_PER_YEAR: i64 = 12;
710const DAYS_PER_NORMAL_YEAR: i64 = 365;
712const DAYS_PER_4_YEARS: i64 = DAYS_PER_NORMAL_YEAR * 4 + 1;
714const DAYS_PER_100_YEARS: i64 = DAYS_PER_NORMAL_YEAR * 100 + 24;
716const DAYS_PER_400_YEARS: i64 = DAYS_PER_NORMAL_YEAR * 400 + 97;
718const UNIX_OFFSET_SECS: i64 = 951868800;
720const OFFSET_YEAR: i64 = 2000;
722const DAY_IN_MONTHS_LEAP_YEAR_FROM_MARCH: [i64; 12] =
724 [31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29];
725
726pub(crate) const fn days_since_unix_epoch(year: i32, month: usize, month_day: i64) -> i64 {
734 let is_leap_year = is_leap_year(year);
735
736 let year = year as i64;
737
738 let mut result = (year - 1970) * 365;
739
740 if year >= 1970 {
741 result += (year - 1968) / 4;
742 result -= (year - 1900) / 100;
743 result += (year - 1600) / 400;
744
745 if is_leap_year && month < 3 {
746 result -= 1;
747 }
748 } else {
749 result += (year - 1972) / 4;
750 result -= (year - 2000) / 100;
751 result += (year - 2000) / 400;
752
753 if is_leap_year && month >= 3 {
754 result += 1;
755 }
756 }
757
758 result += CUMUL_DAY_IN_MONTHS_NORMAL_YEAR[month - 1] + month_day - 1;
759
760 result
761}
762
763pub(crate) const fn is_leap_year(year: i32) -> bool {
765 year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)
766}
767
768#[cfg(test)]
769mod tests {
770 use super::super::timezone::Transition;
771 use super::super::{Error, TimeZone};
772 use super::{AlternateTime, LocalTimeType, RuleDay, TransitionRule};
773
774 #[test]
775 fn test_quoted() -> Result<(), Error> {
776 let transition_rule = TransitionRule::from_tz_string(b"<-03>+3<+03>-3,J1,J365", false)?;
777 assert_eq!(
778 transition_rule,
779 AlternateTime::new(
780 LocalTimeType::new(-10800, false, Some(b"-03"))?,
781 LocalTimeType::new(10800, true, Some(b"+03"))?,
782 RuleDay::julian_1(1)?,
783 7200,
784 RuleDay::julian_1(365)?,
785 7200,
786 )?
787 .into()
788 );
789 Ok(())
790 }
791
792 #[test]
793 fn test_full() -> Result<(), Error> {
794 let tz_string = b"NZST-12:00:00NZDT-13:00:00,M10.1.0/02:00:00,M3.3.0/02:00:00";
795 let transition_rule = TransitionRule::from_tz_string(tz_string, false)?;
796 assert_eq!(
797 transition_rule,
798 AlternateTime::new(
799 LocalTimeType::new(43200, false, Some(b"NZST"))?,
800 LocalTimeType::new(46800, true, Some(b"NZDT"))?,
801 RuleDay::month_weekday(10, 1, 0)?,
802 7200,
803 RuleDay::month_weekday(3, 3, 0)?,
804 7200,
805 )?
806 .into()
807 );
808 Ok(())
809 }
810
811 #[test]
812 fn test_negative_dst() -> Result<(), Error> {
813 let tz_string = b"IST-1GMT0,M10.5.0,M3.5.0/1";
814 let transition_rule = TransitionRule::from_tz_string(tz_string, false)?;
815 assert_eq!(
816 transition_rule,
817 AlternateTime::new(
818 LocalTimeType::new(3600, false, Some(b"IST"))?,
819 LocalTimeType::new(0, true, Some(b"GMT"))?,
820 RuleDay::month_weekday(10, 5, 0)?,
821 7200,
822 RuleDay::month_weekday(3, 5, 0)?,
823 3600,
824 )?
825 .into()
826 );
827 Ok(())
828 }
829
830 #[test]
831 fn test_negative_hour() -> Result<(), Error> {
832 let tz_string = b"<-03>3<-02>,M3.5.0/-2,M10.5.0/-1";
833 assert!(TransitionRule::from_tz_string(tz_string, false).is_err());
834
835 assert_eq!(
836 TransitionRule::from_tz_string(tz_string, true)?,
837 AlternateTime::new(
838 LocalTimeType::new(-10800, false, Some(b"-03"))?,
839 LocalTimeType::new(-7200, true, Some(b"-02"))?,
840 RuleDay::month_weekday(3, 5, 0)?,
841 -7200,
842 RuleDay::month_weekday(10, 5, 0)?,
843 -3600,
844 )?
845 .into()
846 );
847 Ok(())
848 }
849
850 #[test]
851 fn test_invalid_offset_hour() {
852 assert!(TransitionRule::from_tz_string(b"FOO24", false).is_err());
854 assert!(TransitionRule::from_tz_string(b"FOO+24", false).is_err());
855 assert!(TransitionRule::from_tz_string(b"FOO-24", false).is_err());
856 assert!(TransitionRule::from_tz_string(b"FOO23:59:59", false).is_ok());
858 }
859
860 #[test]
861 fn test_all_year_dst() -> Result<(), Error> {
862 let tz_string = b"EST5EDT,0/0,J365/25";
863 assert!(TransitionRule::from_tz_string(tz_string, false).is_err());
864
865 assert_eq!(
866 TransitionRule::from_tz_string(tz_string, true)?,
867 AlternateTime::new(
868 LocalTimeType::new(-18000, false, Some(b"EST"))?,
869 LocalTimeType::new(-14400, true, Some(b"EDT"))?,
870 RuleDay::julian_0(0)?,
871 0,
872 RuleDay::julian_1(365)?,
873 90000,
874 )?
875 .into()
876 );
877 Ok(())
878 }
879
880 #[test]
881 fn test_v3_file() -> Result<(), Error> {
882 let bytes = b"TZif3\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\x04\0\0\x1c\x20\0\0IST\0TZif3\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\x01\0\0\0\0\0\0\0\x01\0\0\0\x01\0\0\0\x04\0\0\0\0\x7f\xe8\x17\x80\0\0\0\x1c\x20\0\0IST\0\x01\x01\x0aIST-2IDT,M3.4.4/26,M10.5.0\x0a";
883
884 let time_zone = TimeZone::from_tz_data(bytes)?;
885
886 let time_zone_result = TimeZone::new(
887 vec![Transition::new(2145916800, 0)],
888 vec![LocalTimeType::new(7200, false, Some(b"IST"))?],
889 Vec::new(),
890 Some(TransitionRule::from(AlternateTime::new(
891 LocalTimeType::new(7200, false, Some(b"IST"))?,
892 LocalTimeType::new(10800, true, Some(b"IDT"))?,
893 RuleDay::month_weekday(3, 4, 4)?,
894 93600,
895 RuleDay::month_weekday(10, 5, 0)?,
896 7200,
897 )?)),
898 )?;
899
900 assert_eq!(time_zone, time_zone_result);
901
902 Ok(())
903 }
904
905 #[test]
906 fn test_rule_day() -> Result<(), Error> {
907 let rule_day_j1 = RuleDay::julian_1(60)?;
908 assert_eq!(rule_day_j1.transition_date(2000), (3, 1));
909 assert_eq!(rule_day_j1.transition_date(2001), (3, 1));
910 assert_eq!(rule_day_j1.unix_time(2000, 43200), 951912000);
911
912 let rule_day_j0 = RuleDay::julian_0(59)?;
913 assert_eq!(rule_day_j0.transition_date(2000), (2, 29));
914 assert_eq!(rule_day_j0.transition_date(2001), (3, 1));
915 assert_eq!(rule_day_j0.unix_time(2000, 43200), 951825600);
916
917 let rule_day_mwd = RuleDay::month_weekday(2, 5, 2)?;
918 assert_eq!(rule_day_mwd.transition_date(2000), (2, 29));
919 assert_eq!(rule_day_mwd.transition_date(2001), (2, 27));
920 assert_eq!(rule_day_mwd.unix_time(2000, 43200), 951825600);
921 assert_eq!(rule_day_mwd.unix_time(2001, 43200), 983275200);
922
923 Ok(())
924 }
925
926 #[test]
927 fn test_transition_rule() -> Result<(), Error> {
928 let transition_rule_fixed = TransitionRule::from(LocalTimeType::new(-36000, false, None)?);
929 assert_eq!(transition_rule_fixed.find_local_time_type(0)?.offset(), -36000);
930
931 let transition_rule_dst = TransitionRule::from(AlternateTime::new(
932 LocalTimeType::new(43200, false, Some(b"NZST"))?,
933 LocalTimeType::new(46800, true, Some(b"NZDT"))?,
934 RuleDay::month_weekday(10, 1, 0)?,
935 7200,
936 RuleDay::month_weekday(3, 3, 0)?,
937 7200,
938 )?);
939
940 assert_eq!(transition_rule_dst.find_local_time_type(953384399)?.offset(), 46800);
941 assert_eq!(transition_rule_dst.find_local_time_type(953384400)?.offset(), 43200);
942 assert_eq!(transition_rule_dst.find_local_time_type(970322399)?.offset(), 43200);
943 assert_eq!(transition_rule_dst.find_local_time_type(970322400)?.offset(), 46800);
944
945 let transition_rule_negative_dst = TransitionRule::from(AlternateTime::new(
946 LocalTimeType::new(3600, false, Some(b"IST"))?,
947 LocalTimeType::new(0, true, Some(b"GMT"))?,
948 RuleDay::month_weekday(10, 5, 0)?,
949 7200,
950 RuleDay::month_weekday(3, 5, 0)?,
951 3600,
952 )?);
953
954 assert_eq!(transition_rule_negative_dst.find_local_time_type(954032399)?.offset(), 0);
955 assert_eq!(transition_rule_negative_dst.find_local_time_type(954032400)?.offset(), 3600);
956 assert_eq!(transition_rule_negative_dst.find_local_time_type(972781199)?.offset(), 3600);
957 assert_eq!(transition_rule_negative_dst.find_local_time_type(972781200)?.offset(), 0);
958
959 let transition_rule_negative_time_1 = TransitionRule::from(AlternateTime::new(
960 LocalTimeType::new(0, false, None)?,
961 LocalTimeType::new(0, true, None)?,
962 RuleDay::julian_0(100)?,
963 0,
964 RuleDay::julian_0(101)?,
965 -86500,
966 )?);
967
968 assert!(transition_rule_negative_time_1.find_local_time_type(8639899)?.is_dst());
969 assert!(!transition_rule_negative_time_1.find_local_time_type(8639900)?.is_dst());
970 assert!(!transition_rule_negative_time_1.find_local_time_type(8639999)?.is_dst());
971 assert!(transition_rule_negative_time_1.find_local_time_type(8640000)?.is_dst());
972
973 let transition_rule_negative_time_2 = TransitionRule::from(AlternateTime::new(
974 LocalTimeType::new(-10800, false, Some(b"-03"))?,
975 LocalTimeType::new(-7200, true, Some(b"-02"))?,
976 RuleDay::month_weekday(3, 5, 0)?,
977 -7200,
978 RuleDay::month_weekday(10, 5, 0)?,
979 -3600,
980 )?);
981
982 assert_eq!(
983 transition_rule_negative_time_2.find_local_time_type(954032399)?.offset(),
984 -10800
985 );
986 assert_eq!(
987 transition_rule_negative_time_2.find_local_time_type(954032400)?.offset(),
988 -7200
989 );
990 assert_eq!(
991 transition_rule_negative_time_2.find_local_time_type(972781199)?.offset(),
992 -7200
993 );
994 assert_eq!(
995 transition_rule_negative_time_2.find_local_time_type(972781200)?.offset(),
996 -10800
997 );
998
999 let transition_rule_all_year_dst = TransitionRule::from(AlternateTime::new(
1000 LocalTimeType::new(-18000, false, Some(b"EST"))?,
1001 LocalTimeType::new(-14400, true, Some(b"EDT"))?,
1002 RuleDay::julian_0(0)?,
1003 0,
1004 RuleDay::julian_1(365)?,
1005 90000,
1006 )?);
1007
1008 assert_eq!(transition_rule_all_year_dst.find_local_time_type(946702799)?.offset(), -14400);
1009 assert_eq!(transition_rule_all_year_dst.find_local_time_type(946702800)?.offset(), -14400);
1010
1011 Ok(())
1012 }
1013
1014 #[test]
1015 fn test_transition_rule_overflow() -> Result<(), Error> {
1016 let transition_rule_1 = TransitionRule::from(AlternateTime::new(
1017 LocalTimeType::new(-1, false, None)?,
1018 LocalTimeType::new(-1, true, None)?,
1019 RuleDay::julian_1(365)?,
1020 0,
1021 RuleDay::julian_1(1)?,
1022 0,
1023 )?);
1024
1025 let transition_rule_2 = TransitionRule::from(AlternateTime::new(
1026 LocalTimeType::new(1, false, None)?,
1027 LocalTimeType::new(1, true, None)?,
1028 RuleDay::julian_1(365)?,
1029 0,
1030 RuleDay::julian_1(1)?,
1031 0,
1032 )?);
1033
1034 let min_unix_time = -67768100567971200;
1035 let max_unix_time = 67767976233532799;
1036
1037 assert!(matches!(
1038 transition_rule_1.find_local_time_type(min_unix_time),
1039 Err(Error::OutOfRange(_))
1040 ));
1041 assert!(matches!(
1042 transition_rule_2.find_local_time_type(max_unix_time),
1043 Err(Error::OutOfRange(_))
1044 ));
1045
1046 Ok(())
1047 }
1048}