tz/datetime/
find.rs

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