1use crate::datetime::{check_date_time_inputs, unix_time, DateTime, UtcDateTime};
4use crate::error::TzError;
5use crate::timezone::{TimeZoneRef, TransitionRule};
6
7#[cfg(feature = "alloc")]
8use alloc::vec::Vec;
9
10#[derive(Debug, Copy, Clone, PartialEq)]
12pub enum FoundDateTimeKind {
13 Normal(DateTime),
15 Skipped {
21 before_transition: DateTime,
23 after_transition: DateTime,
25 },
26}
27
28#[cfg(feature = "alloc")]
33#[derive(Debug, Default, Clone, PartialEq)]
34pub struct FoundDateTimeList(Vec<FoundDateTimeKind>);
35
36#[cfg(feature = "alloc")]
37impl FoundDateTimeList {
38 pub fn unique(&self) -> Option<DateTime> {
40 match *self.0.as_slice() {
41 [FoundDateTimeKind::Normal(date_time)] => Some(date_time),
42 _ => None,
43 }
44 }
45
46 pub fn earliest(&self) -> Option<DateTime> {
48 match *self.0.first()? {
50 FoundDateTimeKind::Normal(date_time) => Some(date_time),
51 FoundDateTimeKind::Skipped { before_transition, .. } => Some(before_transition),
52 }
53 }
54
55 pub fn latest(&self) -> Option<DateTime> {
57 match *self.0.last()? {
59 FoundDateTimeKind::Normal(date_time) => Some(date_time),
60 FoundDateTimeKind::Skipped { after_transition, .. } => Some(after_transition),
61 }
62 }
63
64 pub fn into_inner(self) -> Vec<FoundDateTimeKind> {
66 self.0
67 }
68}
69
70#[derive(Debug, PartialEq)]
72pub struct FoundDateTimeListRefMut<'a> {
73 buf: &'a mut [Option<FoundDateTimeKind>],
75 current_index: usize,
77 count: usize,
79}
80
81impl<'a> FoundDateTimeListRefMut<'a> {
82 pub fn new(buf: &'a mut [Option<FoundDateTimeKind>]) -> Self {
84 Self { buf, current_index: 0, count: 0 }
85 }
86
87 pub fn unique(&self) -> Option<DateTime> {
89 let mut iter = self.data().iter().flatten();
90 let first = iter.next();
91 let second = iter.next();
92
93 match (first, second) {
94 (Some(FoundDateTimeKind::Normal(date_time)), None) => Some(*date_time),
95 _ => None,
96 }
97 }
98
99 pub fn earliest(&self) -> Option<DateTime> {
101 match *self.data().iter().flatten().next()? {
103 FoundDateTimeKind::Normal(date_time) => Some(date_time),
104 FoundDateTimeKind::Skipped { before_transition, .. } => Some(before_transition),
105 }
106 }
107
108 pub fn latest(&self) -> Option<DateTime> {
110 match *self.data().iter().flatten().next_back()? {
112 FoundDateTimeKind::Normal(date_time) => Some(date_time),
113 FoundDateTimeKind::Skipped { after_transition, .. } => Some(after_transition),
114 }
115 }
116
117 pub fn data(&self) -> &[Option<FoundDateTimeKind>] {
119 &self.buf[..self.current_index]
120 }
121
122 pub fn count(&self) -> usize {
124 self.count
125 }
126
127 pub fn is_exhaustive(&self) -> bool {
129 self.current_index == self.count
130 }
131}
132
133pub(super) trait DateTimeList {
135 fn push(&mut self, found_date_time: FoundDateTimeKind);
137}
138
139#[cfg(feature = "alloc")]
140impl DateTimeList for FoundDateTimeList {
141 fn push(&mut self, found_date_time: FoundDateTimeKind) {
142 self.0.push(found_date_time);
143 }
144}
145
146impl<'a> DateTimeList for FoundDateTimeListRefMut<'a> {
147 fn push(&mut self, found_date_time: FoundDateTimeKind) {
148 if let Some(x) = self.buf.get_mut(self.current_index) {
149 *x = Some(found_date_time);
150 self.current_index += 1
151 }
152
153 self.count += 1;
154 }
155}
156
157#[allow(clippy::too_many_arguments)]
172pub(super) fn find_date_time(
173 found_date_time_list: &mut impl DateTimeList,
174 year: i32,
175 month: u8,
176 month_day: u8,
177 hour: u8,
178 minute: u8,
179 second: u8,
180 nanoseconds: u32,
181 time_zone_ref: TimeZoneRef<'_>,
182) -> Result<(), TzError> {
183 let transitions = time_zone_ref.transitions();
184 let local_time_types = time_zone_ref.local_time_types();
185 let extra_rule = time_zone_ref.extra_rule();
186
187 if transitions.is_empty() && extra_rule.is_none() {
188 let date_time = DateTime::new(year, month, month_day, hour, minute, second, nanoseconds, local_time_types[0])?;
189 found_date_time_list.push(FoundDateTimeKind::Normal(date_time));
190 return Ok(());
191 }
192
193 let new_datetime = |local_time_type, unix_time| DateTime { year, month, month_day, hour, minute, second, local_time_type, unix_time, nanoseconds };
194
195 check_date_time_inputs(year, month, month_day, hour, minute, second, nanoseconds)?;
196 let utc_unix_time = unix_time(year, month, month_day, hour, minute, second);
197
198 if !transitions.is_empty() {
200 let mut last_cached_time = None;
201
202 let mut get_time = |local_time_type_index: usize| -> Result<_, TzError> {
203 match last_cached_time {
204 Some((index, value)) if index == local_time_type_index => Ok(value),
205 _ => {
206 let unix_time = utc_unix_time - local_time_types[local_time_type_index].ut_offset() as i64;
208 let unix_leap_time = time_zone_ref.unix_time_to_unix_leap_time(unix_time)?;
209
210 last_cached_time = Some((local_time_type_index, (unix_time, unix_leap_time)));
211 Ok((unix_time, unix_leap_time))
212 }
213 }
214 };
215
216 let mut previous_transition_unix_leap_time = i64::MIN;
217 let mut previous_local_time_type_index = 0;
218
219 for (index, transition) in transitions.iter().enumerate() {
221 let local_time_type_before = local_time_types[previous_local_time_type_index];
222 let (unix_time_before, unix_leap_time_before) = get_time(previous_local_time_type_index)?;
223
224 if previous_transition_unix_leap_time <= unix_leap_time_before && unix_leap_time_before < transition.unix_leap_time() {
225 UtcDateTime::check_unix_time(unix_time_before)?;
226 found_date_time_list.push(FoundDateTimeKind::Normal(new_datetime(local_time_type_before, unix_time_before)));
227 } else {
228 if index < transitions.len() - 1 || extra_rule.is_some() {
230 let local_time_type_after = local_time_types[transition.local_time_type_index()];
231 let (_, unix_leap_time_after) = get_time(transition.local_time_type_index())?;
232
233 if unix_leap_time_before >= transition.unix_leap_time() && unix_leap_time_after < transition.unix_leap_time() {
235 let transition_unix_time = time_zone_ref.unix_leap_time_to_unix_time(transition.unix_leap_time())?;
236
237 found_date_time_list.push(FoundDateTimeKind::Skipped {
238 before_transition: DateTime::from_timespec_and_local(transition_unix_time, nanoseconds, local_time_type_before)?,
239 after_transition: DateTime::from_timespec_and_local(transition_unix_time, nanoseconds, local_time_type_after)?,
240 });
241 }
242 }
243 }
244
245 previous_transition_unix_leap_time = transition.unix_leap_time();
246 previous_local_time_type_index = transition.local_time_type_index();
247 }
248 }
249
250 match extra_rule {
252 None => {}
253 Some(TransitionRule::Fixed(local_time_type)) => {
254 let unix_time = utc_unix_time - local_time_type.ut_offset() as i64;
256
257 let condition = match transitions.last() {
258 Some(last_transition) => unix_time >= time_zone_ref.unix_leap_time_to_unix_time(last_transition.unix_leap_time())?,
259 None => true,
260 };
261
262 if condition {
263 UtcDateTime::check_unix_time(unix_time)?;
264 found_date_time_list.push(FoundDateTimeKind::Normal(new_datetime(*local_time_type, unix_time)));
265 }
266 }
267 Some(TransitionRule::Alternate(alternate_time)) => {
268 let std_ut_offset = alternate_time.std().ut_offset() as i64;
269 let dst_ut_offset = alternate_time.dst().ut_offset() as i64;
270
271 let unix_time_std = utc_unix_time - std_ut_offset;
273 let unix_time_dst = utc_unix_time - dst_ut_offset;
274
275 let dst_start_time_in_utc = alternate_time.dst_start_time() as i64 - std_ut_offset;
276 let dst_end_time_in_utc = alternate_time.dst_end_time() as i64 - dst_ut_offset;
277
278 UtcDateTime::check_unix_time(unix_time_std)?;
280 UtcDateTime::check_unix_time(unix_time_dst)?;
281
282 if !(i32::MIN + 2..=i32::MAX - 2).contains(&year) {
284 return Err(TzError::OutOfRange);
285 }
286
287 let mut additional_transition_times = [
291 alternate_time.dst_start().unix_time(year - 1, dst_start_time_in_utc),
292 alternate_time.dst_end().unix_time(year - 1, dst_end_time_in_utc),
293 alternate_time.dst_start().unix_time(year, dst_start_time_in_utc),
294 alternate_time.dst_end().unix_time(year, dst_end_time_in_utc),
295 alternate_time.dst_start().unix_time(year + 1, dst_start_time_in_utc),
296 alternate_time.dst_end().unix_time(year + 1, dst_end_time_in_utc),
297 i64::MAX,
298 ];
299
300 let sorted = additional_transition_times.windows(2).all(|x| x[0] <= x[1]);
302
303 if !sorted {
304 for chunk in additional_transition_times.chunks_exact_mut(2) {
305 chunk.swap(0, 1);
306 }
307 };
308
309 let transition_start = (alternate_time.std(), alternate_time.dst(), unix_time_std, unix_time_dst);
310 let transition_end = (alternate_time.dst(), alternate_time.std(), unix_time_dst, unix_time_std);
311
312 let additional_transitions = if sorted {
313 [&transition_start, &transition_end, &transition_start, &transition_end, &transition_start, &transition_end, &transition_start]
314 } else {
315 [&transition_end, &transition_start, &transition_end, &transition_start, &transition_end, &transition_start, &transition_end]
316 };
317
318 let mut previous_transition_unix_time = match transitions.last() {
319 Some(last_transition) => time_zone_ref.unix_leap_time_to_unix_time(last_transition.unix_leap_time())?,
320 None => i64::MIN,
321 };
322
323 if let Some(first_valid) = additional_transition_times.iter().position(|&unix_time| previous_transition_unix_time < unix_time) {
325 let valid_transition_times = &additional_transition_times[first_valid..];
326 let valid_transitions = &additional_transitions[first_valid..];
327
328 let valid_iter = valid_transition_times.iter().copied().zip(valid_transitions.iter().copied());
329
330 for (transition_unix_time, &(&local_time_type_before, &local_time_type_after, unix_time_before, unix_time_after)) in valid_iter {
331 if previous_transition_unix_time <= unix_time_before && unix_time_before < transition_unix_time {
332 found_date_time_list.push(FoundDateTimeKind::Normal(new_datetime(local_time_type_before, unix_time_before)));
333 } else {
334 if unix_time_before >= transition_unix_time && unix_time_after < transition_unix_time {
336 found_date_time_list.push(FoundDateTimeKind::Skipped {
337 before_transition: DateTime::from_timespec_and_local(transition_unix_time, nanoseconds, local_time_type_before)?,
338 after_transition: DateTime::from_timespec_and_local(transition_unix_time, nanoseconds, local_time_type_after)?,
339 });
340 }
341 }
342
343 previous_transition_unix_time = transition_unix_time;
344 }
345 }
346 }
347 }
348
349 Ok(())
350}
351
352#[cfg(feature = "alloc")]
353#[cfg(test)]
354mod tests {
355 use super::*;
356 use crate::datetime::tests::check_equal_date_time;
357 use crate::timezone::{AlternateTime, Julian0WithLeap, Julian1WithoutLeap, LocalTimeType, RuleDay, TimeZone, Transition};
358
359 use alloc::vec;
360
361 fn check_equal_option_date_time(x: &Option<DateTime>, y: &Option<DateTime>) {
362 match (x, y) {
363 (None, None) => (),
364 (Some(x), Some(y)) => check_equal_date_time(x, y),
365 _ => panic!("not equal"),
366 }
367 }
368
369 enum Check {
370 Normal([i32; 1]),
371 Skipped([(i32, u8, u8, u8, u8, u8, i32); 2]),
372 }
373
374 fn check(
375 time_zone_ref: TimeZoneRef<'_>,
376 posssible_date_time_results: &[Check],
377 searched: (i32, u8, u8, u8, u8, u8),
378 result_indices: &[usize],
379 unique: Option<[usize; 2]>,
380 earlier: Option<[usize; 2]>,
381 later: Option<[usize; 2]>,
382 ) -> Result<(), TzError> {
383 let new_date_time = |(year, month, month_day, hour, minute, second, ut_offset)| {
384 DateTime::new(year, month, month_day, hour, minute, second, 0, LocalTimeType::with_ut_offset(ut_offset)?)
385 };
386
387 let (year, month, month_day, hour, minute, second) = searched;
388
389 let mut found_date_times = FoundDateTimeList::default();
390 find_date_time(&mut found_date_times, year, month, month_day, hour, minute, second, 0, time_zone_ref)?;
391
392 let mut buf = vec![None; result_indices.len()];
393 let mut found_date_time_list = FoundDateTimeListRefMut::new(&mut buf);
394 find_date_time(&mut found_date_time_list, year, month, month_day, hour, minute, second, 0, time_zone_ref)?;
395
396 let indexed_date_time = |[index_1, index_2]: [usize; 2]| match posssible_date_time_results[index_1] {
397 Check::Normal(arr) => new_date_time((year, month, month_day, hour, minute, second, arr[index_2])),
398 Check::Skipped(arr) => new_date_time(arr[index_2]),
399 };
400
401 check_equal_option_date_time(&found_date_times.unique(), &unique.map(indexed_date_time).transpose()?);
402 check_equal_option_date_time(&found_date_times.earliest(), &earlier.map(indexed_date_time).transpose()?);
403 check_equal_option_date_time(&found_date_times.latest(), &later.map(indexed_date_time).transpose()?);
404
405 let found_date_times_inner = found_date_times.into_inner();
406 assert_eq!(found_date_times_inner.len(), result_indices.len());
407
408 assert!(found_date_time_list.is_exhaustive());
409 assert_eq!(found_date_times_inner, buf.iter().copied().flatten().collect::<Vec<_>>());
410
411 for (found_date_time, &result_index) in found_date_times_inner.iter().zip(result_indices) {
412 match posssible_date_time_results[result_index] {
413 Check::Normal([ut_offset]) => {
414 assert_eq!(*found_date_time, FoundDateTimeKind::Normal(new_date_time((year, month, month_day, hour, minute, second, ut_offset))?));
415 }
416 Check::Skipped([before, after]) => {
417 let skipped = FoundDateTimeKind::Skipped { before_transition: new_date_time(before)?, after_transition: new_date_time(after)? };
418 assert_eq!(*found_date_time, skipped);
419 }
420 };
421 }
422
423 Ok(())
424 }
425
426 #[test]
427 fn test_find_date_time_fixed() -> Result<(), TzError> {
428 let local_time_type = LocalTimeType::with_ut_offset(3600)?;
429
430 let results = &[Check::Normal([3600])];
431
432 let time_zone_1 = TimeZone::new(vec![], vec![local_time_type], vec![], None)?;
433 let time_zone_2 = TimeZone::new(vec![], vec![local_time_type], vec![], Some(TransitionRule::Fixed(local_time_type)))?;
434
435 check(time_zone_1.as_ref(), results, (2000, 1, 1, 0, 0, 0), &[0], Some([0, 0]), Some([0, 0]), Some([0, 0]))?;
436 check(time_zone_2.as_ref(), results, (2000, 1, 1, 0, 0, 0), &[0], Some([0, 0]), Some([0, 0]), Some([0, 0]))?;
437
438 let time_zone_3 = TimeZone::new(vec![Transition::new(0, 0)], vec![local_time_type], vec![], Some(TransitionRule::Fixed(local_time_type)))?;
439
440 check(time_zone_3.as_ref(), results, (1960, 1, 1, 0, 0, 0), &[0], Some([0, 0]), Some([0, 0]), Some([0, 0]))?;
441 check(time_zone_3.as_ref(), results, (1980, 1, 1, 0, 0, 0), &[0], Some([0, 0]), Some([0, 0]), Some([0, 0]))?;
442
443 Ok(())
444 }
445
446 #[test]
447 fn test_find_date_time_no_offset() -> Result<(), TzError> {
448 let local_time_types = [
449 LocalTimeType::new(0, false, Some(b"STD1"))?,
450 LocalTimeType::new(0, true, Some(b"DST1"))?,
451 LocalTimeType::new(0, false, Some(b"STD2"))?,
452 LocalTimeType::new(0, true, Some(b"DST2"))?,
453 ];
454
455 let time_zone = TimeZone::new(
456 vec![Transition::new(3600, 1), Transition::new(7200, 2)],
457 local_time_types.to_vec(),
458 vec![],
459 Some(TransitionRule::Alternate(AlternateTime::new(
460 local_time_types[2],
461 local_time_types[3],
462 RuleDay::Julian0WithLeap(Julian0WithLeap::new(0)?),
463 10800,
464 RuleDay::Julian0WithLeap(Julian0WithLeap::new(0)?),
465 14400,
466 )?)),
467 )?;
468
469 let time_zone_ref = time_zone.as_ref();
470
471 let find_unique_local_time_type = |year, month, month_day, hour, minute, second, nanoseconds| -> Result<_, TzError> {
472 let mut found_date_time_list = FoundDateTimeList::default();
473 find_date_time(&mut found_date_time_list, year, month, month_day, hour, minute, second, nanoseconds, time_zone_ref)?;
474
475 let mut buf = [None; 1];
476 let mut found_date_time_list_ref_mut = FoundDateTimeListRefMut::new(&mut buf);
477 find_date_time(&mut found_date_time_list_ref_mut, year, month, month_day, hour, minute, second, 0, time_zone_ref)?;
478 assert!(found_date_time_list_ref_mut.is_exhaustive());
479
480 let datetime_1 = found_date_time_list.unique().unwrap();
481 let datetime_2 = found_date_time_list_ref_mut.unique().unwrap();
482 assert_eq!(datetime_1, datetime_2);
483
484 Ok(*datetime_1.local_time_type())
485 };
486
487 assert_eq!(local_time_types[0], find_unique_local_time_type(1970, 1, 1, 0, 30, 0, 0)?);
488 assert_eq!(local_time_types[1], find_unique_local_time_type(1970, 1, 1, 1, 30, 0, 0)?);
489 assert_eq!(local_time_types[2], find_unique_local_time_type(1970, 1, 1, 2, 30, 0, 0)?);
490 assert_eq!(local_time_types[3], find_unique_local_time_type(1970, 1, 1, 3, 30, 0, 0)?);
491 assert_eq!(local_time_types[2], find_unique_local_time_type(1970, 1, 1, 4, 30, 0, 0)?);
492
493 Ok(())
494 }
495
496 #[test]
497 fn test_find_date_time_extra_rule_only() -> Result<(), TzError> {
498 let time_zone = TimeZone::new(
499 vec![],
500 vec![LocalTimeType::utc(), LocalTimeType::with_ut_offset(3600)?],
501 vec![],
502 Some(TransitionRule::Alternate(AlternateTime::new(
503 LocalTimeType::utc(),
504 LocalTimeType::with_ut_offset(3600)?,
505 RuleDay::Julian1WithoutLeap(Julian1WithoutLeap::new(1)?),
506 7200,
507 RuleDay::Julian1WithoutLeap(Julian1WithoutLeap::new(1)?),
508 12600,
509 )?)),
510 )?;
511
512 let time_zone_ref = time_zone.as_ref();
513
514 let results = &[
515 Check::Normal([0]),
516 Check::Normal([3600]),
517 Check::Skipped([(2000, 1, 1, 2, 0, 0, 0), (2000, 1, 1, 3, 0, 0, 3600)]),
518 Check::Skipped([(2010, 1, 1, 2, 0, 0, 0), (2010, 1, 1, 3, 0, 0, 3600)]),
519 ];
520
521 check(time_zone_ref, results, (2000, 1, 1, 1, 45, 0), &[0], Some([0, 0]), Some([0, 0]), Some([0, 0]))?;
522 check(time_zone_ref, results, (2000, 1, 1, 2, 15, 0), &[2], None, Some([2, 0]), Some([2, 1]))?;
523 check(time_zone_ref, results, (2000, 1, 1, 2, 45, 0), &[2, 0], None, Some([2, 0]), Some([0, 0]))?;
524 check(time_zone_ref, results, (2000, 1, 1, 3, 15, 0), &[1, 0], None, Some([1, 0]), Some([0, 0]))?;
525 check(time_zone_ref, results, (2000, 1, 1, 3, 45, 0), &[0], Some([0, 0]), Some([0, 0]), Some([0, 0]))?;
526
527 check(time_zone_ref, results, (2010, 1, 1, 1, 45, 0), &[0], Some([0, 0]), Some([0, 0]), Some([0, 0]))?;
528 check(time_zone_ref, results, (2010, 1, 1, 2, 15, 0), &[3], None, Some([3, 0]), Some([3, 1]))?;
529 check(time_zone_ref, results, (2010, 1, 1, 2, 45, 0), &[3, 0], None, Some([3, 0]), Some([0, 0]))?;
530 check(time_zone_ref, results, (2010, 1, 1, 3, 15, 0), &[1, 0], None, Some([1, 0]), Some([0, 0]))?;
531 check(time_zone_ref, results, (2010, 1, 1, 3, 45, 0), &[0], Some([0, 0]), Some([0, 0]), Some([0, 0]))?;
532
533 Ok(())
534 }
535
536 #[test]
537 fn test_find_date_time_transitions_only() -> Result<(), TzError> {
538 let time_zone = TimeZone::new(
539 vec![
540 Transition::new(0, 0),
541 Transition::new(7200, 1),
542 Transition::new(14400, 2),
543 Transition::new(25200, 3),
544 Transition::new(28800, 4),
545 Transition::new(32400, 0),
546 ],
547 vec![
548 LocalTimeType::new(0, false, None)?,
549 LocalTimeType::new(3600, false, None)?,
550 LocalTimeType::new(-10800, false, None)?,
551 LocalTimeType::new(-19800, false, None)?,
552 LocalTimeType::new(-16200, false, None)?,
553 ],
554 vec![],
555 None,
556 )?;
557
558 let time_zone_ref = time_zone.as_ref();
559
560 let results = &[
561 Check::Normal([0]),
562 Check::Normal([3600]),
563 Check::Normal([-10800]),
564 Check::Normal([-19800]),
565 Check::Normal([-16200]),
566 Check::Skipped([(1970, 1, 1, 2, 0, 0, 0), (1970, 1, 1, 3, 0, 0, 3600)]),
567 Check::Skipped([(1970, 1, 1, 2, 30, 0, -19800), (1970, 1, 1, 3, 30, 0, -16200)]),
568 ];
569
570 check(time_zone_ref, results, (1970, 1, 1, 0, 0, 0), &[0], Some([0, 0]), Some([0, 0]), Some([0, 0]))?;
571 check(time_zone_ref, results, (1970, 1, 1, 1, 0, 0), &[0, 2], None, Some([0, 0]), Some([2, 0]))?;
572 check(time_zone_ref, results, (1970, 1, 1, 1, 15, 0), &[0, 2], None, Some([0, 0]), Some([2, 0]))?;
573 check(time_zone_ref, results, (1970, 1, 1, 1, 30, 0), &[0, 2, 3], None, Some([0, 0]), Some([3, 0]))?;
574 check(time_zone_ref, results, (1970, 1, 1, 1, 45, 0), &[0, 2, 3], None, Some([0, 0]), Some([3, 0]))?;
575 check(time_zone_ref, results, (1970, 1, 1, 2, 0, 0), &[5, 2, 3], None, Some([5, 0]), Some([3, 0]))?;
576 check(time_zone_ref, results, (1970, 1, 1, 2, 15, 0), &[5, 2, 3], None, Some([5, 0]), Some([3, 0]))?;
577 check(time_zone_ref, results, (1970, 1, 1, 2, 30, 0), &[5, 2, 6], None, Some([5, 0]), Some([6, 1]))?;
578 check(time_zone_ref, results, (1970, 1, 1, 2, 45, 0), &[5, 2, 6], None, Some([5, 0]), Some([6, 1]))?;
579 check(time_zone_ref, results, (1970, 1, 1, 3, 0, 0), &[1, 2, 6], None, Some([1, 0]), Some([6, 1]))?;
580 check(time_zone_ref, results, (1970, 1, 1, 3, 15, 0), &[1, 2, 6], None, Some([1, 0]), Some([6, 1]))?;
581 check(time_zone_ref, results, (1970, 1, 1, 3, 30, 0), &[1, 2, 4], None, Some([1, 0]), Some([4, 0]))?;
582 check(time_zone_ref, results, (1970, 1, 1, 3, 45, 0), &[1, 2, 4], None, Some([1, 0]), Some([4, 0]))?;
583 check(time_zone_ref, results, (1970, 1, 1, 4, 0, 0), &[1, 4], None, Some([1, 0]), Some([4, 0]))?;
584 check(time_zone_ref, results, (1970, 1, 1, 4, 15, 0), &[1, 4], None, Some([1, 0]), Some([4, 0]))?;
585 check(time_zone_ref, results, (1970, 1, 1, 4, 30, 0), &[1], Some([1, 0]), Some([1, 0]), Some([1, 0]))?;
586 check(time_zone_ref, results, (1970, 1, 1, 4, 45, 0), &[1], Some([1, 0]), Some([1, 0]), Some([1, 0]))?;
587 check(time_zone_ref, results, (1970, 1, 1, 5, 0, 0), &[], None, None, None)?;
588
589 Ok(())
590 }
591
592 #[test]
593 fn test_find_date_time_transitions_with_extra_rule() -> Result<(), TzError> {
594 let time_zone = TimeZone::new(
595 vec![Transition::new(0, 0), Transition::new(3600, 1), Transition::new(7200, 0), Transition::new(10800, 2)],
596 vec![LocalTimeType::utc(), LocalTimeType::with_ut_offset(i32::MAX)?, LocalTimeType::with_ut_offset(3600)?],
597 vec![],
598 Some(TransitionRule::Alternate(AlternateTime::new(
599 LocalTimeType::utc(),
600 LocalTimeType::with_ut_offset(3600)?,
601 RuleDay::Julian1WithoutLeap(Julian1WithoutLeap::new(300)?),
602 0,
603 RuleDay::Julian1WithoutLeap(Julian1WithoutLeap::new(90)?),
604 3600,
605 )?)),
606 )?;
607
608 let time_zone_ref = time_zone.as_ref();
609
610 let results = &[
611 Check::Normal([0]),
612 Check::Normal([3600]),
613 Check::Normal([i32::MAX]),
614 Check::Skipped([(1970, 1, 1, 1, 0, 0, 0), (2038, 1, 19, 4, 14, 7, i32::MAX)]),
615 Check::Skipped([(1970, 1, 1, 3, 0, 0, 0), (1970, 1, 1, 4, 0, 0, 3600)]),
616 Check::Skipped([(1970, 10, 27, 0, 0, 0, 0), (1970, 10, 27, 1, 0, 0, 3600)]),
617 Check::Skipped([(2000, 10, 27, 0, 0, 0, 0), (2000, 10, 27, 1, 0, 0, 3600)]),
618 Check::Skipped([(2030, 10, 27, 0, 0, 0, 0), (2030, 10, 27, 1, 0, 0, 3600)]),
619 Check::Skipped([(2038, 10, 27, 0, 0, 0, 0), (2038, 10, 27, 1, 0, 0, 3600)]),
620 ];
621
622 check(time_zone_ref, results, (1970, 1, 1, 0, 30, 0), &[0], Some([0, 0]), Some([0, 0]), Some([0, 0]))?;
623 check(time_zone_ref, results, (1970, 1, 1, 1, 30, 0), &[3], None, Some([3, 0]), Some([3, 1]))?;
624 check(time_zone_ref, results, (1970, 1, 1, 2, 30, 0), &[3, 0], None, Some([3, 0]), Some([0, 0]))?;
625 check(time_zone_ref, results, (1970, 1, 1, 3, 30, 0), &[3, 4], None, Some([3, 0]), Some([4, 1]))?;
626 check(time_zone_ref, results, (1970, 1, 1, 4, 30, 0), &[3, 1], None, Some([3, 0]), Some([1, 0]))?;
627
628 check(time_zone_ref, results, (1970, 2, 1, 0, 0, 0), &[3, 1], None, Some([3, 0]), Some([1, 0]))?;
629 check(time_zone_ref, results, (1970, 3, 31, 0, 30, 0), &[3, 1, 0], None, Some([3, 0]), Some([0, 0]))?;
630 check(time_zone_ref, results, (1970, 6, 1, 0, 0, 0), &[3, 0], None, Some([3, 0]), Some([0, 0]))?;
631 check(time_zone_ref, results, (1970, 10, 27, 0, 30, 0), &[3, 5], None, Some([3, 0]), Some([5, 1]))?;
632 check(time_zone_ref, results, (1970, 11, 1, 0, 0, 0), &[3, 1], None, Some([3, 0]), Some([1, 0]))?;
633
634 check(time_zone_ref, results, (2000, 2, 1, 0, 0, 0), &[3, 1], None, Some([3, 0]), Some([1, 0]))?;
635 check(time_zone_ref, results, (2000, 3, 31, 0, 30, 0), &[3, 1, 0], None, Some([3, 0]), Some([0, 0]))?;
636 check(time_zone_ref, results, (2000, 6, 1, 0, 0, 0), &[3, 0], None, Some([3, 0]), Some([0, 0]))?;
637 check(time_zone_ref, results, (2000, 10, 27, 0, 30, 0), &[3, 6], None, Some([3, 0]), Some([6, 1]))?;
638 check(time_zone_ref, results, (2000, 11, 1, 0, 0, 0), &[3, 1], None, Some([3, 0]), Some([1, 0]))?;
639
640 check(time_zone_ref, results, (2030, 2, 1, 0, 0, 0), &[3, 1], None, Some([3, 0]), Some([1, 0]))?;
641 check(time_zone_ref, results, (2030, 3, 31, 0, 30, 0), &[3, 1, 0], None, Some([3, 0]), Some([0, 0]))?;
642 check(time_zone_ref, results, (2030, 6, 1, 0, 0, 0), &[3, 0], None, Some([3, 0]), Some([0, 0]))?;
643 check(time_zone_ref, results, (2030, 10, 27, 0, 30, 0), &[3, 7], None, Some([3, 0]), Some([7, 1]))?;
644 check(time_zone_ref, results, (2030, 11, 1, 0, 0, 0), &[3, 1], None, Some([3, 0]), Some([1, 0]))?;
645
646 check(time_zone_ref, results, (2038, 1, 19, 5, 0, 0), &[2, 1], None, Some([2, 0]), Some([1, 0]))?;
647 check(time_zone_ref, results, (2038, 2, 1, 0, 0, 0), &[1], Some([1, 0]), Some([1, 0]), Some([1, 0]))?;
648 check(time_zone_ref, results, (2038, 3, 31, 0, 30, 0), &[1, 0], None, Some([1, 0]), Some([0, 0]))?;
649 check(time_zone_ref, results, (2038, 6, 1, 0, 0, 0), &[0], Some([0, 0]), Some([0, 0]), Some([0, 0]))?;
650 check(time_zone_ref, results, (2038, 10, 27, 0, 30, 0), &[8], None, Some([8, 0]), Some([8, 1]))?;
651 check(time_zone_ref, results, (2038, 11, 1, 0, 0, 0), &[1], Some([1, 0]), Some([1, 0]), Some([1, 0]))?;
652
653 Ok(())
654 }
655
656 #[test]
657 fn test_find_date_time_ref_mut() -> Result<(), TzError> {
658 let transitions = &[Transition::new(3600, 1), Transition::new(86400, 0), Transition::new(i64::MAX, 0)];
659 let local_time_types = &[LocalTimeType::new(0, false, Some(b"STD"))?, LocalTimeType::new(3600, true, Some(b"DST"))?];
660 let time_zone_ref = TimeZoneRef::new(transitions, local_time_types, &[], &None)?;
661
662 let mut small_buf = [None; 1];
663 let mut found_date_time_small_list = FoundDateTimeListRefMut::new(&mut small_buf);
664 find_date_time(&mut found_date_time_small_list, 1970, 1, 2, 0, 30, 0, 0, time_zone_ref)?;
665 assert!(!found_date_time_small_list.is_exhaustive());
666
667 let mut buf = [None; 2];
668 let mut found_date_time_list_1 = FoundDateTimeListRefMut::new(&mut buf);
669 find_date_time(&mut found_date_time_list_1, 1970, 1, 2, 0, 30, 0, 0, time_zone_ref)?;
670 let data = found_date_time_list_1.data();
671 assert!(found_date_time_list_1.is_exhaustive());
672 assert_eq!(found_date_time_list_1.count(), 2);
673 assert!(matches!(data, [Some(FoundDateTimeKind::Normal(..)), Some(FoundDateTimeKind::Normal(..))]));
674
675 let mut found_date_time_list_2 = FoundDateTimeListRefMut::new(&mut buf);
676 find_date_time(&mut found_date_time_list_2, 1970, 1, 1, 1, 30, 0, 0, time_zone_ref)?;
677 let data = found_date_time_list_2.data();
678 assert!(found_date_time_list_2.is_exhaustive());
679 assert_eq!(found_date_time_list_2.count(), 1);
680 assert!(found_date_time_list_2.unique().is_none());
681 assert!(matches!(data, &[Some(FoundDateTimeKind::Skipped { .. })]));
682
683 Ok(())
684 }
685}