radio_datetime_utils/
lib.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2// Copyright 2022-2025 René Ladan <rene0+codeberg@freedom.nl>
3
4//! Definition of date/time structures commonly useful for time station decoders.
5
6//! Build with no_std for embedded platforms.
7#![cfg_attr(not(test), no_std)]
8
9pub mod radio_datetime_helpers;
10
11/// DST change has been announced (mutually exclusive with DST_PROCESSED)
12pub const DST_ANNOUNCED: u8 = 1;
13/// DST change has been processed (mutually exclusive with DST_ANNOUNCED)
14pub const DST_PROCESSED: u8 = 2;
15/// unexpected jump in DST state
16pub const DST_JUMP: u8 = 4;
17/// DST is active
18pub const DST_SUMMER: u8 = 8;
19
20/// Leap second has been announced (mutually exclusive with LEAP_PROCESSED)
21pub const LEAP_ANNOUNCED: u8 = 1;
22/// Leap second has been processed (mutually exclusive with LEAP_ANNOUNCED)
23pub const LEAP_PROCESSED: u8 = 2;
24/// Leap second is unexpectedly absent
25pub const LEAP_MISSING: u8 = 4;
26
27/// Size of bit buffer in seconds plus one spare because we cannot know
28/// which method accessing the buffer is called after increase_second().
29pub const BIT_BUFFER_SIZE: usize = 61 + 1;
30
31/// Represents a date and time transmitted over radio.
32#[derive(Clone, Copy, Debug)]
33#[cfg_attr(test, derive(PartialEq))]
34pub struct RadioDateTimeUtils {
35    year: Option<u8>,
36    month: Option<u8>,
37    day: Option<u8>,
38    weekday: Option<u8>,
39    hour: Option<u8>,
40    minute: Option<u8>,
41    dst: Option<u8>,
42    leap_second: Option<u8>,
43    dut1: Option<i8>, // DUT1 in deci-seconds (MSF) or centi-seconds (RBU)
44    jump_year: bool,
45    jump_month: bool,
46    jump_day: bool,
47    jump_weekday: bool,
48    jump_hour: bool,
49    jump_minute: bool,
50    jump_dut1: bool,
51    min_weekday: u8,
52    max_weekday: u8,
53    minutes_running: u8,   // internal counter for set_dst() and set_leap_second()
54    dst_count: u8,         // internal counter for set_dst()
55    first_minute: bool,    // internal flag for set_dst()
56    leap_second_count: u8, // internal counter for set_leap_second()
57    is_readonly: bool,     // internal flag to mark the structure as read-only
58    is_utc: Option<bool>,  // if the time is transmitted in UTC, optional until part of `new`
59    utc_offset: Option<i16>,
60    utc_offset_hours: Option<i8>,
61    utc_offset_minutes: Option<i8>,
62}
63
64/// Macro to avoid repetition in `set_XXX()`
65macro_rules! set_value {
66    ($this:ident, $me:ident, $value:expr, $min:expr, $max:expr, $terms:expr, $jump_me:ident,
67     $check_jump:expr) => {
68        if $this.is_readonly {
69            return;
70        }
71        let val = if $value.is_some() && $terms && ($min..=$max).contains(&$value.unwrap()) {
72            $value
73        } else {
74            $this.$me
75        };
76        $this.$jump_me = $check_jump && val.is_some() && $this.$me.is_some() && val != $this.$me;
77        $this.$me = val;
78    };
79}
80
81/// Macro to avoid repetition in UTC/localtime conversions
82macro_rules! convert_time {
83    ($this:ident, $is_utc:expr, $mult:expr) => {
84        if $this.is_utc.is_none() || !$this.is_valid() {
85            None
86        } else if $this.is_readonly || $this.is_utc.unwrap() == $is_utc {
87            Some(*$this)
88        } else {
89            let mut u = $this.utc_offset.unwrap();
90            let h;
91            let m;
92            if $this.dst.is_some_and(|x| x & DST_SUMMER > 0) {
93                u += 60;
94                h = (u / 60) as i8;
95                m = (u % 60) as i8;
96            } else {
97                h = $this.utc_offset_hours.unwrap();
98                m = $this.utc_offset_minutes.unwrap();
99            }
100            let mut rdt_new = if u == 0 {
101                *$this
102            } else if ($mult * u) > 0 {
103                if m == 0 {
104                    RadioDateTimeUtils::incr_hour(
105                        *$this,
106                        ($mult * h) as u8,
107                        RadioDateTimeUtils::id_hour,
108                    )
109                } else {
110                    RadioDateTimeUtils::incr_minute(
111                        *$this,
112                        ($mult * m) as u8,
113                        ($mult * h) as u8,
114                        RadioDateTimeUtils::id_hour,
115                    )
116                }
117            } else if m == 0 {
118                RadioDateTimeUtils::decr_hour(*$this, ($mult * -h) as u8)
119            } else {
120                RadioDateTimeUtils::decr_minute(*$this, ($mult * -m) as u8, ($mult * -h) as u8)
121            };
122            rdt_new.is_readonly = true;
123            Some(rdt_new)
124        }
125    };
126}
127
128impl RadioDateTimeUtils {
129    /// Initialize a new RadioDateTimeUtils instance
130    ///
131    /// # Arguments
132    /// * `sunday` - the numeric value of Sunday, i.e. 7 for DCF77 or 0 for MSF
133    pub fn new(sunday: u8) -> Self {
134        Self {
135            year: None,
136            month: None,
137            day: None,
138            weekday: None,
139            hour: None,
140            minute: None,
141            dst: None,
142            dut1: None,
143            dst_count: 0,
144            leap_second: None,
145            leap_second_count: 0,
146            jump_year: false,
147            jump_month: false,
148            jump_day: false,
149            jump_weekday: false,
150            jump_hour: false,
151            jump_minute: false,
152            jump_dut1: false,
153            min_weekday: (sunday != 0) as u8,
154            max_weekday: if sunday == 7 { 7 } else { 6 },
155            minutes_running: 0,
156            first_minute: true,
157            is_readonly: false,
158            is_utc: None,
159            utc_offset: None,
160            utc_offset_hours: None,
161            utc_offset_minutes: None,
162        }
163    }
164
165    /// Reset the RadioDateTimeUtils instance, except for Sunday, UTC offset, and in-UTC.
166    pub fn reset(&mut self) {
167        self.year = None;
168        self.month = None;
169        self.day = None;
170        self.weekday = None;
171        self.hour = None;
172        self.minute = None;
173        self.dst = None;
174        self.dut1 = None;
175        self.dst_count = 0;
176        self.leap_second = None;
177        self.leap_second_count = 0;
178        self.jump_year = false;
179        self.jump_month = false;
180        self.jump_day = false;
181        self.jump_weekday = false;
182        self.jump_hour = false;
183        self.jump_minute = false;
184        self.jump_dut1 = false;
185        self.minutes_running = 0;
186        self.first_minute = true;
187        self.is_readonly = false;
188    }
189
190    /// Get the current year, truncated to two digits.
191    pub fn get_year(&self) -> Option<u8> {
192        self.year
193    }
194
195    /// Get the current month.
196    pub fn get_month(&self) -> Option<u8> {
197        self.month
198    }
199
200    /// Get the current day of the month.
201    pub fn get_day(&self) -> Option<u8> {
202        self.day
203    }
204
205    /// Get the current day of the week as a number.
206    pub fn get_weekday(&self) -> Option<u8> {
207        self.weekday
208    }
209
210    /// Get the current hour.
211    pub fn get_hour(&self) -> Option<u8> {
212        self.hour
213    }
214
215    /// Get the current minute.
216    pub fn get_minute(&self) -> Option<u8> {
217        self.minute
218    }
219
220    /// Get the current bitmask value (if any) of the daylight saving time status.
221    pub fn get_dst(&self) -> Option<u8> {
222        self.dst
223    }
224
225    /// Get the current bitmask value of the leap second status.
226    pub fn get_leap_second(&self) -> Option<u8> {
227        self.leap_second
228    }
229
230    /// Get the value of DUT1 (UT1 - UTC).
231    pub fn get_dut1(&self) -> Option<i8> {
232        self.dut1
233    }
234
235    /// Return if the DUT1 has jumped unexpectedly (not at midnight UTC).
236    pub fn get_jump_dut1(&self) -> bool {
237        self.jump_dut1
238    }
239
240    /// Return if the year has jumped unexpectedly.
241    pub fn get_jump_year(&self) -> bool {
242        self.jump_year
243    }
244
245    /// Return if the month has jumped unexpectedly.
246    pub fn get_jump_month(&self) -> bool {
247        self.jump_month
248    }
249
250    /// Return if the day-of-month has jumped unexpectedly.
251    pub fn get_jump_day(&self) -> bool {
252        self.jump_day
253    }
254
255    /// Return if the day-of-week has jumped unexpectedly.
256    pub fn get_jump_weekday(&self) -> bool {
257        self.jump_weekday
258    }
259
260    /// Return if the hour has jumped unexpectedly.
261    pub fn get_jump_hour(&self) -> bool {
262        self.jump_hour
263    }
264
265    /// Return if the minute has jumped unexpectedly.
266    pub fn get_jump_minute(&self) -> bool {
267        self.jump_minute
268    }
269
270    /// Set the time zone offset from local normal-time to UTC in minutes, eastwards being positive,
271    /// and set whether the station transmits in UTC.
272    ///
273    /// This function is needed until `utc_offset` and `is_utc` are part of `new()`.
274    ///
275    /// # Arguments
276    /// * `utc_offset` - the offset in minutes, between -1080 and +1080 inclusive
277    /// * `is_utc` - indicates if the station transmits in UTC
278    pub fn set_utc_offset(&mut self, utc_offset: i16, is_utc: bool) {
279        if (-1080..=1080).contains(&utc_offset) {
280            self.utc_offset = Some(utc_offset);
281            self.utc_offset_hours = Some((utc_offset / 60) as i8);
282            self.utc_offset_minutes = Some((utc_offset % 60) as i8);
283            self.is_utc = Some(is_utc);
284        }
285    }
286
287    /// Returns if the current date/time is valid (date, time, DST are all `is_some()`).
288    pub fn is_valid(&self) -> bool {
289        self.dst.is_some()
290            && self.year.is_some()
291            && self.month.is_some()
292            && self.day.is_some()
293            && self.weekday.is_some()
294            && self.hour.is_some()
295            && self.minute.is_some()
296    }
297
298    /// Clear all jump values.
299    pub fn clear_jumps(&mut self) {
300        if self.is_readonly {
301            return;
302        }
303        self.jump_year = false;
304        self.jump_month = false;
305        self.jump_day = false;
306        self.jump_weekday = false;
307        self.jump_hour = false;
308        self.jump_minute = false;
309        if self.dst.is_some() {
310            self.dst = Some(self.dst.unwrap() & !DST_JUMP);
311        }
312        self.jump_dut1 = false;
313    }
314
315    /// Add one minute to the current date and time, return if the operation succeeded.
316    ///
317    /// * Years are limited to 2 digits, so this function wraps after 100 years.
318    /// * The operation is valid in local time and accounts for DST changes.
319    pub fn add_minute(&mut self) -> bool {
320        if self.is_readonly || !self.is_valid() {
321            return false;
322        }
323        let rdt_new = RadioDateTimeUtils::incr_minute(*self, 1, 0, RadioDateTimeUtils::dst_hour);
324        self.minute = rdt_new.minute;
325        self.hour = rdt_new.hour;
326        self.day = rdt_new.day;
327        self.weekday = rdt_new.weekday;
328        self.month = rdt_new.month;
329        self.year = rdt_new.year;
330        true
331    }
332
333    /// Set the year value, valid values are 0 through 99.
334    ///
335    /// # Arguments
336    /// * `value` - the new year value. None or invalid values keep the old value.
337    /// * `terms` - extra validations to pass.
338    /// * `check_jump` - check if the value has jumped unexpectedly compared to `add_minute()`.
339    pub fn set_year(&mut self, value: Option<u8>, terms: bool, check_jump: bool) {
340        set_value!(self, year, value, 0, 99, terms, jump_year, check_jump);
341    }
342
343    /// Set the month value, valid values are 1 through 12.
344    ///
345    /// # Arguments
346    /// * `value` - the new month value. None or invalid values keep the old value.
347    /// * `terms` - extra validations to pass.
348    /// * `check_jump` - check if the value has jumped unexpectedly compared to `add_minute()`.
349    pub fn set_month(&mut self, value: Option<u8>, terms: bool, check_jump: bool) {
350        set_value!(self, month, value, 1, 12, terms, jump_month, check_jump);
351    }
352
353    /// Set the day-of-week value, valid values are 0/1 through 6/7, depending on how this
354    /// instance was created.
355    ///
356    /// # Arguments
357    /// * `value` - the new day-of-week value. None or invalid values keep the old value.
358    /// * `terms` - extra validations to pass.
359    /// * `check_jump` - check if the value has jumped unexpectedly compared to `add_minute()`.
360    pub fn set_weekday(&mut self, value: Option<u8>, terms: bool, check_jump: bool) {
361        set_value!(
362            self,
363            weekday,
364            value,
365            self.min_weekday,
366            self.max_weekday,
367            terms,
368            jump_weekday,
369            check_jump
370        );
371    }
372
373    /// Set the day-in-month value, valid values are 1 through the last day of that month.
374    ///
375    /// If the year, month, or weekday are absent, the last day of the month cannot be
376    /// calculated which means the old day-in-month value is kept.
377    ///
378    /// # Arguments
379    /// * `value` - the new day-in-month value. None or invalid values keep the old value.
380    /// * `terms` - extra validations to pass.
381    /// * `check_jump` - check if the value has jumped unexpectedly compared to `add_minute()`.
382    pub fn set_day(&mut self, value: Option<u8>, terms: bool, check_jump: bool) {
383        if value.is_none() {
384            return;
385        }
386        let s_value = value.unwrap();
387        if self.year.is_none()
388            || self.month.is_none()
389            || self.weekday.is_none()
390            || !(1..=31).contains(&s_value)
391        {
392            return;
393        }
394        let last_day = self.last_day(s_value);
395        set_value!(
396            self,
397            day,
398            value,
399            1,
400            last_day.unwrap(),
401            last_day.is_some() && terms,
402            jump_day,
403            check_jump
404        );
405    }
406
407    /// Set the hour value, valid values are 0 through 23.
408    ///
409    /// # Arguments
410    /// * `value` - the new hour value. None or invalid values keep the old value.
411    /// * `terms` - extra validations to pass.
412    /// * `check_jump` - check if the value has jumped unexpectedly compared to `add_minute()`.
413    pub fn set_hour(&mut self, value: Option<u8>, terms: bool, check_jump: bool) {
414        set_value!(self, hour, value, 0, 23, terms, jump_hour, check_jump);
415    }
416
417    /// Set the minute value, valid values are 0 through 59.
418    ///
419    /// # Arguments
420    /// * `value` - the new minute value. None or invalid values keep the old value.
421    /// * `terms` - extra validations to pass.
422    /// * `check_jump` - check if the value has jumped unexpectedly compared to `add_minute()`.
423    pub fn set_minute(&mut self, value: Option<u8>, terms: bool, check_jump: bool) {
424        set_value!(self, minute, value, 0, 59, terms, jump_minute, check_jump);
425    }
426
427    /// Set the DUT1 value, valid values are -99 through 99.
428    ///
429    /// # Arguments
430    /// * `value` - the new DUT1 value. None or invalid values keep the old value.
431    /// * `terms` - extra validations to pass.
432    /// * `check_jump` - check if the value has jumped unexpectedly compared to IERS.
433    pub fn set_dut1(&mut self, value: Option<i8>, terms: bool, check_jump: bool) {
434        set_value!(self, dut1, value, -99, 99, terms, jump_dut1, check_jump);
435        if let Some(utc_time) = self.get_utc() {
436            self.jump_dut1 &=
437                utc_time.hour.is_some_and(|h| h != 0) || utc_time.minute.is_some_and(|m| m != 0);
438        }
439    }
440
441    /// Set the month and day-in-month value from the given day-in-year and is-leap-year values.
442    ///
443    /// # Arguments
444    /// * `day_in_year` - the new day-in-year value.
445    /// * `is_leap_year` - this year is a leap year.
446    /// * `terms_month` - extra validation to pass for the month.
447    /// * `terms_day` - extra validation to pass for the day-in-month.
448    /// * `check_jump` - check if the day-in-month or month have jumped unexpectedly compared to
449    ///   `add_minute()`
450    pub fn set_month_day(
451        &mut self,
452        day_in_year: Option<u16>,
453        is_leap_year: Option<bool>,
454        terms_month: bool,
455        terms_day: bool,
456        check_jump: bool,
457    ) {
458        if self.is_readonly || day_in_year.is_none() || is_leap_year.is_none() {
459            return;
460        }
461        let s_diy = day_in_year.unwrap();
462        let la = if is_leap_year.unwrap() { 1 } else { 0 };
463        if s_diy < 1 || s_diy > 365 + la {
464            return;
465        }
466        let month_idx = [
467            0,
468            31,
469            59 + la,
470            90 + la,
471            120 + la,
472            151 + la,
473            181 + la,
474            212 + la,
475            243 + la,
476            273 + la,
477            304 + la,
478            334 + la,
479        ];
480        let mut m = 0;
481        while m < 12 && month_idx[m] < s_diy {
482            m += 1;
483        }
484        let mo = Some(m as u8);
485        set_value!(self, month, mo, 1, 12, terms_month, jump_month, check_jump);
486        let dy = (s_diy - month_idx[m - 1]) as u8;
487        set_value!(self, day, Some(dy), 1, 31, terms_day, jump_day, check_jump);
488    }
489
490    /// Set the DST mask value, both the actual value and any information on transitions.
491    ///
492    /// # Arguments
493    /// * `value` - the new DST value. A None value or unannounced changes keep the current value.
494    /// * `announce` - if any announcement is made on a transition. The history of this value of
495    ///   the last hour (or part thereof if started later) is kept to compensate for spurious
496    ///   True values.
497    /// * `check_jump` - check if the value changed unexpectedly.
498    pub fn set_dst(&mut self, value: Option<bool>, announce: Option<bool>, check_jump: bool) {
499        if self.is_readonly || value.is_none() || announce.is_none() {
500            return;
501        }
502        if self.dst.is_none() {
503            self.dst = Some(0);
504        }
505        // Clear any jump flag from the previous decoding:
506        self.dst = Some(self.dst.unwrap() & !DST_JUMP);
507        // Determine if a DST change is announced:
508        if announce == Some(true) {
509            self.dst_count += 1;
510        }
511        if self.minute.is_some_and(|x| x > 0) {
512            if 2 * self.dst_count > self.minutes_running {
513                self.dst = Some(self.dst.unwrap() | DST_ANNOUNCED);
514            } else {
515                self.dst = Some(self.dst.unwrap() & !DST_ANNOUNCED);
516            }
517        }
518        if value.unwrap() != ((self.dst.unwrap() & DST_SUMMER) != 0) {
519            // Time offset changed.
520            if self.first_minute
521                || ((self.dst.unwrap() & DST_ANNOUNCED) != 0 && self.minute == Some(0))
522            {
523                // Change is valid.
524                if value.unwrap() {
525                    self.dst = Some(self.dst.unwrap() | DST_SUMMER);
526                } else {
527                    self.dst = Some(self.dst.unwrap() & !DST_SUMMER);
528                }
529            } else if check_jump {
530                self.dst = Some(self.dst.unwrap() | DST_JUMP);
531            }
532        }
533        if self.minute == Some(0) && (self.dst.unwrap() & DST_ANNOUNCED) != 0 {
534            // DST change processed:
535            self.dst = Some(self.dst.unwrap() | DST_PROCESSED);
536        } else if self.minute.is_some() {
537            self.dst = Some(self.dst.unwrap() & !DST_PROCESSED);
538        }
539        // Always reset announcement at the hour:
540        if self.minute == Some(0) {
541            self.dst = Some(self.dst.unwrap() & !DST_ANNOUNCED);
542            self.dst_count = 0;
543        }
544        self.first_minute = false;
545    }
546
547    /// Set the leap second value.
548    ///
549    /// # Arguments
550    /// * `announce` - if any announcement is made on a positive leap second. The history of this
551    ///   value of the last hour (or part thereof if started later) is kept to compensate for
552    ///   spurious Some(True) values.
553    /// * `minute_length` - the length of the decoded minute in seconds.
554    pub fn set_leap_second(&mut self, announce: Option<bool>, minute_length: u8) {
555        if self.is_readonly || announce.is_none() || !(60..=61).contains(&minute_length) {
556            return;
557        }
558        if self.leap_second.is_none() {
559            self.leap_second = Some(0);
560        }
561        // Determine if a leap second is announced:
562        if announce == Some(true) {
563            self.leap_second_count += 1;
564        }
565        if self.minute.is_some_and(|x| x > 0) {
566            if 2 * self.leap_second_count > self.minutes_running {
567                self.leap_second = Some(self.leap_second.unwrap() | LEAP_ANNOUNCED);
568            } else {
569                self.leap_second = Some(self.leap_second.unwrap() & !LEAP_ANNOUNCED);
570            }
571        }
572        // Process possible leap second:
573        if self.minute == Some(0) && (self.leap_second.unwrap() & LEAP_ANNOUNCED) != 0 {
574            self.leap_second = Some(self.leap_second.unwrap() | LEAP_PROCESSED);
575            if minute_length == 60 {
576                // Leap second processed, but missing:
577                self.leap_second = Some(self.leap_second.unwrap() | LEAP_MISSING);
578            } else {
579                // Leap second processed and present:
580                self.leap_second = Some(self.leap_second.unwrap() & !LEAP_MISSING);
581            }
582        } else if self.minute.is_some() {
583            self.leap_second = Some(self.leap_second.unwrap() & !LEAP_PROCESSED & !LEAP_MISSING);
584        }
585        // Always reset announcement at the hour:
586        if self.minute == Some(0) {
587            self.leap_second = Some(self.leap_second.unwrap() & !LEAP_ANNOUNCED);
588            self.leap_second_count = 0;
589        }
590    }
591
592    /// Bump the internal minute counter needed for set_dst() and set_leap_second()
593    ///
594    /// The code above this library must call this function, as this library cannot
595    /// know which function got called first, or if just one of them should be called.
596    pub fn bump_minutes_running(&mut self) {
597        if self.is_readonly {
598            return;
599        }
600        self.minutes_running += 1;
601        if self.minute == Some(0) {
602            self.minutes_running = 0;
603        }
604    }
605
606    /// Return a copy of the current date/time in UTC (for local time sources),
607    /// does nothing for incomplete inputs or results that are already converted to UTC.
608    ///
609    /// The return type is Optional until utc_offset is part of `new()`.
610    pub fn get_utc(&self) -> Option<RadioDateTimeUtils> {
611        #![allow(clippy::neg_multiply)]
612        convert_time!(self, true, -1)
613    }
614
615    /// Return a copy of the current date/time in local time (for UTC sources),
616    /// does nothing for incomplete inputs or results that are already converted to local time.
617    ///
618    /// The return type is Optional until utc_offset is part of `new()`.
619    pub fn get_local_time(&self) -> Option<RadioDateTimeUtils> {
620        convert_time!(self, false, 1)
621    }
622
623    /// Return the last calendar day of the current date, or None in case of error.
624    ///
625    /// # Arguments
626    /// * `day` - day of the month in February '00, used to see if `year` is a leap year
627    fn last_day(&self, day: u8) -> Option<u8> {
628        let s_year = self.year.unwrap();
629        let s_month = self.month.unwrap();
630        let s_weekday = self.weekday.unwrap();
631        if s_month == 2 {
632            if day > 29 {
633                None
634            } else if (s_year != 0 && s_year % 4 == 0)
635                || (s_year == 0 && RadioDateTimeUtils::is_leap_century(day, s_weekday))
636            {
637                Some(29)
638            } else {
639                Some(28)
640            }
641        } else if s_month == 4 || s_month == 6 || s_month == 9 || s_month == 11 {
642            Some(30)
643        } else {
644            Some(31)
645        }
646    }
647
648    /// Check if the century based on the given day in February '00 is divisible by 400.
649    ///
650    /// Based on xx00-02-28 is a Monday <=> xx00 is a leap year
651    ///
652    /// # Arguments
653    /// * `day` - day of the month in February '00
654    /// * `weekday` - day of the week in February '00
655    fn is_leap_century(day: u8, weekday: u8) -> bool {
656        // Ensure Sunday is 7 when dealing with e.g. MSF :
657        let wd = if weekday == 0 { 7 } else { weekday };
658
659        // Week day 1 is a Monday, assume this is a leap year.
660        // If so, we should reach Monday xx00-02-28
661        if day < 29 {
662            // (8 - wd) % 7 == (0,(8-2)..=(8-7)) == (0,6..=1) --> Monday=0, Tuesday=6, .., Sunday=1
663            // Transpose day to 22..=28, then check if the result plus
664            // the inverted day-of-week adds up to 28
665            day + 7 * ((28 - day) / 7) + ((8 - wd) % 7) == 28
666        } else {
667            wd == 2 // Tuesday xx00-02-29
668        }
669    }
670
671    /// Helper that returns the value for the hour after a DST change.
672    ///
673    /// Except for Troll Research Station (UTC+0/UTC+2) and Lord Howe Island (UTC+10:30/UTC+11)
674    /// all DST differences are 1 hour. Neither of the above have their own time station
675    /// (VNG shut down on 2002-12-31).
676    ///
677    /// # Arguments
678    /// * `s_dst` - current DST flags
679    /// * `s_hour` - current hour
680    fn dst_hour(s_dst: u8, s_hour: u8) -> u8 {
681        if (s_dst & DST_ANNOUNCED) != 0 {
682            return if (s_dst & DST_SUMMER) != 0 {
683                s_hour - 1 // changing to winter
684            } else {
685                s_hour + 1 // changing to summer, hour can become 24 here
686            };
687        }
688        s_hour
689    }
690
691    /// Helper that just returns the hour unmodified.
692    ///
693    /// # Arguments
694    /// * `_` - unused
695    /// * `s_hour` - current hour
696    fn id_hour(_: u8, s_hour: u8) -> u8 {
697        s_hour
698    }
699
700    /// Helper that advances the given time by the given amount of minutes
701    /// and calls `incr_hour()` with the callback if needed.
702    ///
703    /// # Arguments
704    /// * `rdt` - RadioDateTimeUtils structure to advance the time of
705    /// * `minutes` - amount of minutes to advance the time with
706    /// * `hours` - pass-through variable for `incr_hour()`
707    /// * `hour_cb` - callback function to pass to `incr_hour()`
708    fn incr_minute(
709        rdt: RadioDateTimeUtils,
710        minutes: u8,
711        hours: u8,
712        hour_cb: fn(u8, u8) -> u8,
713    ) -> RadioDateTimeUtils {
714        let mut rdt_new = rdt;
715        let mut s_minute = rdt.minute.unwrap();
716        s_minute += minutes;
717        if s_minute >= 60 {
718            s_minute -= 60;
719            rdt_new = RadioDateTimeUtils::incr_hour(rdt, hours + 1, hour_cb)
720        }
721        rdt_new.minute = Some(s_minute);
722        rdt_new
723    }
724
725    /// Helper that advances the given time by the given amount of hours.
726    ///
727    /// # Arguments
728    /// * `rdt` - RadioDateTimeUtils structure to advance the time of
729    /// * `hours` - amount of hours to advance the time with
730    /// * `hour_cb` - callback to optionally compensate for DST changes
731    fn incr_hour(
732        rdt: RadioDateTimeUtils,
733        hours: u8,
734        hour_cb: fn(u8, u8) -> u8,
735    ) -> RadioDateTimeUtils {
736        let mut rdt_new = rdt;
737        let mut s_hour = rdt.hour.unwrap();
738        s_hour += hours;
739        s_hour = hour_cb(rdt_new.dst.unwrap(), s_hour);
740        if s_hour >= 24 {
741            s_hour -= 24;
742            let mut s_day = rdt.day.unwrap();
743            let old_last_day = rdt.last_day(s_day).unwrap();
744            let mut s_weekday = rdt.weekday.unwrap();
745            s_weekday += 1;
746            if s_weekday == rdt.max_weekday + 1 {
747                s_weekday = rdt.min_weekday;
748            }
749            s_day += 1;
750            if s_day > old_last_day {
751                s_day = 1;
752                let mut s_month = rdt.month.unwrap();
753                s_month += 1;
754                if s_month == 13 {
755                    s_month = 1;
756                    let mut s_year = rdt.year.unwrap();
757                    s_year += 1;
758                    if s_year == 100 {
759                        s_year = 0;
760                    }
761                    rdt_new.year = Some(s_year);
762                }
763                rdt_new.month = Some(s_month);
764            }
765            rdt_new.weekday = Some(s_weekday);
766            rdt_new.day = Some(s_day);
767        }
768        rdt_new.hour = Some(s_hour);
769        rdt_new
770    }
771
772    /// Helper that decreases the given time by the given amount of minutes
773    /// and calls `decr_hour()` when needed.
774    ///
775    /// # Arguments
776    /// * `rdt` - RadioDateTimeUtils structure to decrease the time of
777    /// * `minutes` - amount of minutes to decrease the time with
778    /// * `hours` - pass-through variable for `decr_hour()`
779    fn decr_minute(rdt: RadioDateTimeUtils, minutes: u8, hours: u8) -> RadioDateTimeUtils {
780        let mut s_minute = rdt.minute.unwrap();
781        let mut h = hours;
782        if s_minute < minutes {
783            s_minute += 60 - minutes;
784            h += 1;
785        } else {
786            s_minute -= minutes;
787        }
788        let mut rdt_new = RadioDateTimeUtils::decr_hour(rdt, h);
789        rdt_new.minute = Some(s_minute);
790        rdt_new
791    }
792
793    /// Helper that decreases the given time by the given amount of hours.
794    ///
795    /// # Arguments
796    /// * `rdt` - RadioDateTimeUtils structure to decrease the time of
797    /// * `hours` - amount of hours to decrease the time with
798    fn decr_hour(rdt: RadioDateTimeUtils, hours: u8) -> RadioDateTimeUtils {
799        let mut rdt_new = rdt;
800        let mut s_hour = rdt.hour.unwrap();
801        if s_hour < hours {
802            s_hour += 24 - hours;
803            let mut s_weekday = rdt.weekday.unwrap();
804            if s_weekday == rdt.min_weekday {
805                s_weekday = rdt.max_weekday;
806            } else {
807                s_weekday -= 1;
808            }
809            rdt_new.weekday = Some(s_weekday);
810            let mut s_day = rdt.day.unwrap();
811            if s_day == 1 {
812                let mut s_month = rdt.month.unwrap();
813                if s_month == 1 {
814                    s_month = 12;
815                    let mut s_year = rdt.year.unwrap();
816                    if s_year == 0 {
817                        s_year = 99;
818                    } else {
819                        s_year -= 1;
820                    }
821                    rdt_new.year = Some(s_year);
822                } else {
823                    s_month -= 1;
824                }
825                rdt_new.month = Some(s_month);
826                s_day = rdt_new.last_day(29).unwrap();
827            } else {
828                s_day -= 1;
829            }
830            rdt_new.day = Some(s_day);
831        } else {
832            s_hour -= hours;
833        }
834        rdt_new.hour = Some(s_hour);
835        rdt_new
836    }
837}
838
839#[cfg(test)]
840mod tests {
841    use super::*;
842
843    #[test]
844    fn test_set_year_some_invalid_jump() {
845        let mut rdt = RadioDateTimeUtils::new(0);
846        rdt.set_year(Some(22), false, true);
847        assert_eq!(rdt.year, None);
848        assert_eq!(rdt.jump_year, false);
849    }
850
851    #[test]
852    fn test_set_year_none_valid_jump() {
853        let mut rdt = RadioDateTimeUtils::new(0);
854        rdt.set_year(None, true, true);
855        assert_eq!(rdt.year, None);
856        assert_eq!(rdt.jump_year, false);
857    }
858
859    #[test]
860    fn test_set_year_too_large_valid_no_jump() {
861        let mut rdt = RadioDateTimeUtils::new(0);
862        rdt.set_year(Some(100), true, false);
863        assert_eq!(rdt.year, None);
864        assert_eq!(rdt.jump_year, false);
865    }
866
867    #[test]
868    fn test_set_year_some_valid_no_jump() {
869        let mut rdt = RadioDateTimeUtils::new(0);
870        rdt.set_year(Some(22), true, false);
871        assert_eq!(rdt.year, Some(22));
872        assert_eq!(rdt.jump_year, false);
873    }
874
875    #[test]
876    fn continue_set_year_too_large_valid_jump() {
877        let mut rdt = RadioDateTimeUtils::new(0);
878        rdt.set_year(Some(22), true, false);
879        rdt.set_year(Some(100), true, true);
880        assert_eq!(rdt.year, Some(22));
881        assert_eq!(rdt.jump_year, false);
882    }
883
884    #[test]
885    fn test_set_year_some_valid_jump() {
886        let mut rdt = RadioDateTimeUtils::new(0);
887        rdt.set_year(Some(22), true, true);
888        assert_eq!(rdt.year, Some(22));
889        assert_eq!(rdt.jump_year, false);
890    }
891
892    #[test]
893    fn continue_set_year_some_valid_jump() {
894        let mut rdt = RadioDateTimeUtils::new(0);
895        rdt.set_year(Some(22), true, true);
896        rdt.set_year(Some(23), true, true);
897        assert_eq!(rdt.year, Some(23));
898        assert_eq!(rdt.jump_year, true);
899    }
900
901    #[test]
902    fn continue_set_year_some_none_no_jump() {
903        let mut rdt = RadioDateTimeUtils::new(0);
904        rdt.set_year(Some(25), true, true);
905        rdt.set_year(None, true, true);
906        assert_eq!(rdt.year, Some(25));
907        assert_eq!(rdt.jump_year, false);
908    }
909
910    #[test]
911    fn continue_set_year_none_some_no_jump() {
912        let mut rdt = RadioDateTimeUtils::new(0);
913        rdt.set_year(None, true, true);
914        rdt.set_year(Some(25), true, true);
915        assert_eq!(rdt.year, Some(25));
916        assert_eq!(rdt.jump_year, false);
917    }
918
919    #[test]
920    fn test_set_month_some_invalid_jump() {
921        let mut rdt = RadioDateTimeUtils::new(0);
922        rdt.set_month(Some(9), false, true);
923        assert_eq!(rdt.month, None);
924        assert_eq!(rdt.jump_month, false);
925    }
926
927    #[test]
928    fn test_set_month_none_valid_jump() {
929        let mut rdt = RadioDateTimeUtils::new(0);
930        rdt.set_month(None, true, true);
931        assert_eq!(rdt.month, None);
932        assert_eq!(rdt.jump_month, false);
933    }
934
935    #[test]
936    fn test_set_month_too_small_valid_no_jump() {
937        let mut rdt = RadioDateTimeUtils::new(0);
938        rdt.set_month(Some(0), true, false);
939        assert_eq!(rdt.month, None);
940        assert_eq!(rdt.jump_month, false);
941    }
942
943    #[test]
944    fn test_set_month_some_valid_no_jump() {
945        let mut rdt = RadioDateTimeUtils::new(0);
946        rdt.set_month(Some(9), true, false);
947        assert_eq!(rdt.month, Some(9));
948        assert_eq!(rdt.jump_month, false);
949    }
950
951    #[test]
952    fn continue_set_month_too_large_valid_jump() {
953        let mut rdt = RadioDateTimeUtils::new(0);
954        rdt.set_month(Some(9), true, false);
955        rdt.set_month(Some(13), true, true);
956        assert_eq!(rdt.month, Some(9));
957        assert_eq!(rdt.jump_month, false);
958    }
959
960    #[test]
961    fn test_set_month_some_valid_jump() {
962        let mut rdt = RadioDateTimeUtils::new(0);
963        rdt.set_month(Some(9), true, true);
964        assert_eq!(rdt.month, Some(9));
965        assert_eq!(rdt.jump_month, false);
966    }
967
968    #[test]
969    fn continue_set_month_some_valid_jump() {
970        let mut rdt = RadioDateTimeUtils::new(0);
971        rdt.set_month(Some(9), true, true);
972        rdt.set_month(Some(10), true, true);
973        assert_eq!(rdt.month, Some(10));
974        assert_eq!(rdt.jump_month, true);
975    }
976
977    #[test]
978    fn continue_set_month_some_none_no_jump() {
979        let mut rdt = RadioDateTimeUtils::new(0);
980        rdt.set_month(Some(1), true, true);
981        rdt.set_month(None, true, true);
982        assert_eq!(rdt.month, Some(1));
983        assert_eq!(rdt.jump_month, false);
984    }
985
986    #[test]
987    fn continue_set_month_none_some_no_jump() {
988        let mut rdt = RadioDateTimeUtils::new(0);
989        rdt.set_month(None, true, true);
990        rdt.set_month(Some(1), true, true);
991        assert_eq!(rdt.month, Some(1));
992        assert_eq!(rdt.jump_month, false);
993    }
994
995    #[test]
996    fn test_set_day_some_invalid_jump() {
997        let mut rdt = RadioDateTimeUtils::new(0);
998        rdt.set_day(Some(23), false, true);
999        assert_eq!(rdt.day, None);
1000        assert_eq!(rdt.jump_day, false);
1001    }
1002
1003    #[test]
1004    fn test_set_day_none_valid_jump() {
1005        let mut rdt = RadioDateTimeUtils::new(0);
1006        rdt.set_day(None, true, true);
1007        assert_eq!(rdt.day, None);
1008        assert_eq!(rdt.jump_day, false);
1009    }
1010
1011    #[test]
1012    fn test_set_day_too_small_valid_no_jump() {
1013        let mut rdt = RadioDateTimeUtils::new(0);
1014        rdt.set_day(Some(0), true, false);
1015        assert_eq!(rdt.day, None);
1016        assert_eq!(rdt.jump_day, false);
1017    }
1018
1019    #[test]
1020    fn test_set_day_some_valid_no_jump() {
1021        let mut rdt = RadioDateTimeUtils::new(0);
1022        // set_day() requires a full date and weekday to work
1023        rdt.year = Some(22);
1024        rdt.month = Some(9);
1025        rdt.weekday = Some(10); // any Some value works here because rdt.month != Some(2)
1026        rdt.set_day(Some(23), true, false);
1027        assert_eq!(rdt.day, Some(23));
1028        assert_eq!(rdt.jump_day, false);
1029    }
1030
1031    #[test]
1032    fn continue_set_day_too_large_valid_jump() {
1033        let mut rdt = RadioDateTimeUtils::new(0);
1034        // set_day() requires a full date and weekday to work
1035        rdt.year = Some(22);
1036        rdt.month = Some(9);
1037        rdt.weekday = Some(5); // any Some value works here because rdt.month != Some(2)
1038        rdt.set_day(Some(23), true, false);
1039        rdt.set_day(Some(32), true, true);
1040        assert_eq!(rdt.day, Some(23));
1041        assert_eq!(rdt.jump_day, false);
1042    }
1043
1044    #[test]
1045    fn test_set_day_some_valid_jump() {
1046        let mut rdt = RadioDateTimeUtils::new(0);
1047        rdt.year = Some(22);
1048        rdt.month = Some(9);
1049        rdt.weekday = Some(0); // any Some value works here because rdt.month != Some(2)
1050        rdt.set_day(Some(23), true, true);
1051        assert_eq!(rdt.day, Some(23));
1052        assert_eq!(rdt.jump_day, false);
1053    }
1054
1055    #[test]
1056    fn test_set_day_partial_input_jump() {
1057        let mut rdt = RadioDateTimeUtils::new(0);
1058        rdt.month = Some(7);
1059        rdt.weekday = Some(0);
1060        rdt.set_day(Some(21), true, true);
1061        assert_eq!(rdt.day, None);
1062        assert_eq!(rdt.jump_day, false);
1063    }
1064
1065    #[test]
1066    fn continue_set_day_some_valid_jump() {
1067        let mut rdt = RadioDateTimeUtils::new(0);
1068        rdt.year = Some(22);
1069        rdt.month = Some(9);
1070        rdt.weekday = Some(7); // any Some value works here because rdt.month != Some(2)
1071        rdt.set_day(Some(23), true, true);
1072        rdt.set_day(Some(24), true, true);
1073        assert_eq!(rdt.day, Some(24));
1074        assert_eq!(rdt.jump_day, true);
1075    }
1076
1077    #[test]
1078    fn continue_set_day_some_none_no_jump() {
1079        let mut rdt = RadioDateTimeUtils::new(0);
1080        rdt.year = Some(25);
1081        rdt.month = Some(1);
1082        rdt.weekday = Some(3);
1083        rdt.set_day(Some(1), true, true);
1084        rdt.set_day(None, true, true);
1085        assert_eq!(rdt.day, Some(1));
1086        assert_eq!(rdt.jump_day, false);
1087    }
1088
1089    #[test]
1090    fn continue_set_day_none_some_no_jump() {
1091        let mut rdt = RadioDateTimeUtils::new(0);
1092        rdt.year = Some(25);
1093        rdt.month = Some(1);
1094        rdt.weekday = Some(3);
1095        rdt.set_day(None, true, true);
1096        rdt.set_day(Some(1), true, true);
1097        assert_eq!(rdt.day, Some(1));
1098        assert_eq!(rdt.jump_day, false);
1099    }
1100
1101    #[test]
1102    fn test_set_weekday_some_invalid_jump() {
1103        let mut rdt = RadioDateTimeUtils::new(0);
1104        rdt.set_weekday(Some(5), false, true);
1105        assert_eq!(rdt.weekday, None);
1106        assert_eq!(rdt.jump_weekday, false);
1107    }
1108
1109    #[test]
1110    fn test_set_weekday_none_valid_jump() {
1111        let mut rdt = RadioDateTimeUtils::new(0);
1112        rdt.set_weekday(None, true, true);
1113        assert_eq!(rdt.weekday, None);
1114        assert_eq!(rdt.jump_weekday, false);
1115    }
1116
1117    #[test]
1118    fn test_set_weekday_too_large_valid_no_jump() {
1119        let mut rdt = RadioDateTimeUtils::new(0);
1120        rdt.set_weekday(Some(7), true, false);
1121        assert_eq!(rdt.weekday, None);
1122        assert_eq!(rdt.jump_weekday, false);
1123    }
1124
1125    #[test]
1126    fn test_set_weekday_some_valid_no_jump() {
1127        let mut rdt = RadioDateTimeUtils::new(0);
1128        rdt.set_weekday(Some(5), true, false);
1129        assert_eq!(rdt.weekday, Some(5));
1130        assert_eq!(rdt.jump_weekday, false);
1131    }
1132
1133    #[test]
1134    fn continue_set_weekday_too_large_valid_jump() {
1135        let mut rdt = RadioDateTimeUtils::new(0);
1136        rdt.set_weekday(Some(5), true, false);
1137        rdt.set_weekday(Some(7), true, true);
1138        assert_eq!(rdt.weekday, Some(5));
1139        assert_eq!(rdt.jump_weekday, false);
1140    }
1141
1142    #[test]
1143    fn test_set_weekday_some_valid_jump() {
1144        let mut rdt = RadioDateTimeUtils::new(0);
1145        rdt.set_weekday(Some(5), true, true);
1146        assert_eq!(rdt.weekday, Some(5));
1147        assert_eq!(rdt.jump_weekday, false);
1148    }
1149
1150    #[test]
1151    fn continue_set_weekday_some_valid_jump() {
1152        let mut rdt = RadioDateTimeUtils::new(0);
1153        rdt.set_weekday(Some(5), true, true);
1154        rdt.set_weekday(Some(6), true, true);
1155        assert_eq!(rdt.weekday, Some(6));
1156        assert_eq!(rdt.jump_weekday, true);
1157    }
1158
1159    #[test]
1160    fn continue_set_weekday_some_none_no_jump() {
1161        let mut rdt = RadioDateTimeUtils::new(0);
1162        rdt.set_weekday(Some(3), true, true);
1163        rdt.set_weekday(None, true, true);
1164        assert_eq!(rdt.weekday, Some(3));
1165        assert_eq!(rdt.jump_weekday, false);
1166    }
1167
1168    #[test]
1169    fn continue_set_weekday_none_some_no_jump() {
1170        let mut rdt = RadioDateTimeUtils::new(0);
1171        rdt.set_weekday(None, true, true);
1172        rdt.set_weekday(Some(3), true, true);
1173        assert_eq!(rdt.weekday, Some(3));
1174        assert_eq!(rdt.jump_weekday, false);
1175    }
1176
1177    #[test]
1178    fn test_set_weekday7_too_small_valid_no_jump() {
1179        let mut rdt = RadioDateTimeUtils::new(7);
1180        rdt.set_weekday(Some(0), true, false);
1181        assert_eq!(rdt.weekday, None);
1182        assert_eq!(rdt.jump_weekday, false);
1183    }
1184
1185    #[test]
1186    fn continue_set_weekday7_too_small_valid_jump() {
1187        let mut rdt = RadioDateTimeUtils::new(7);
1188        rdt.set_weekday(Some(5), true, false);
1189        rdt.set_weekday(Some(0), true, true);
1190        assert_eq!(rdt.weekday, Some(5));
1191        assert_eq!(rdt.jump_weekday, false);
1192    }
1193
1194    #[test]
1195    fn test_set_hour_some_invalid_jump() {
1196        let mut rdt = RadioDateTimeUtils::new(0);
1197        rdt.set_hour(Some(22), false, true);
1198        assert_eq!(rdt.hour, None);
1199        assert_eq!(rdt.jump_hour, false);
1200    }
1201
1202    #[test]
1203    fn test_set_hour_none_valid_jump() {
1204        let mut rdt = RadioDateTimeUtils::new(0);
1205        rdt.set_hour(None, true, true);
1206        assert_eq!(rdt.hour, None);
1207        assert_eq!(rdt.jump_hour, false);
1208    }
1209
1210    #[test]
1211    fn test_set_hour_too_large_valid_no_jump() {
1212        let mut rdt = RadioDateTimeUtils::new(0);
1213        rdt.set_hour(Some(24), true, false);
1214        assert_eq!(rdt.hour, None);
1215        assert_eq!(rdt.jump_hour, false);
1216    }
1217
1218    #[test]
1219    fn test_set_hour_some_valid_no_jump() {
1220        let mut rdt = RadioDateTimeUtils::new(0);
1221        rdt.set_hour(Some(22), true, false);
1222        assert_eq!(rdt.hour, Some(22));
1223        assert_eq!(rdt.jump_hour, false);
1224    }
1225
1226    #[test]
1227    fn continue_set_hour_too_large_valid_jump() {
1228        let mut rdt = RadioDateTimeUtils::new(0);
1229        rdt.set_hour(Some(22), true, false);
1230        rdt.set_hour(Some(24), true, true);
1231        assert_eq!(rdt.hour, Some(22));
1232        assert_eq!(rdt.jump_hour, false);
1233    }
1234
1235    #[test]
1236    fn test_set_hour_some_valid_jump() {
1237        let mut rdt = RadioDateTimeUtils::new(0);
1238        rdt.set_hour(Some(22), true, true);
1239        assert_eq!(rdt.hour, Some(22));
1240        assert_eq!(rdt.jump_hour, false);
1241    }
1242
1243    #[test]
1244    fn continue_set_hour_some_valid_jump() {
1245        let mut rdt = RadioDateTimeUtils::new(0);
1246        rdt.set_hour(Some(22), true, true);
1247        rdt.set_hour(Some(23), true, true);
1248        assert_eq!(rdt.hour, Some(23));
1249        assert_eq!(rdt.jump_hour, true);
1250    }
1251
1252    #[test]
1253    fn continue_set_hour_some_none_no_jump() {
1254        let mut rdt = RadioDateTimeUtils::new(0);
1255        rdt.set_hour(Some(19), true, true);
1256        rdt.set_hour(None, true, true);
1257        assert_eq!(rdt.hour, Some(19));
1258        assert_eq!(rdt.jump_hour, false);
1259    }
1260
1261    #[test]
1262    fn continue_set_hour_none_some_no_jump() {
1263        let mut rdt = RadioDateTimeUtils::new(0);
1264        rdt.set_hour(None, true, true);
1265        rdt.set_hour(Some(19), true, true);
1266        assert_eq!(rdt.hour, Some(19));
1267        assert_eq!(rdt.jump_hour, false);
1268    }
1269
1270    #[test]
1271    fn test_set_minute_some_invalid_jump() {
1272        let mut rdt = RadioDateTimeUtils::new(0);
1273        rdt.set_minute(Some(47), false, true);
1274        assert_eq!(rdt.minute, None);
1275        assert_eq!(rdt.jump_minute, false);
1276    }
1277
1278    #[test]
1279    fn test_set_minute_none_valid_jump() {
1280        let mut rdt = RadioDateTimeUtils::new(0);
1281        rdt.set_minute(None, true, true);
1282        assert_eq!(rdt.minute, None);
1283        assert_eq!(rdt.jump_minute, false);
1284    }
1285
1286    #[test]
1287    fn test_set_minute_too_large_valid_no_jump() {
1288        let mut rdt = RadioDateTimeUtils::new(0);
1289        rdt.set_minute(Some(60), true, false);
1290        assert_eq!(rdt.minute, None);
1291        assert_eq!(rdt.jump_minute, false);
1292    }
1293
1294    #[test]
1295    fn test_set_minute_some_valid_no_jump() {
1296        let mut rdt = RadioDateTimeUtils::new(0);
1297        rdt.set_minute(Some(47), true, false);
1298        assert_eq!(rdt.minute, Some(47));
1299        assert_eq!(rdt.jump_minute, false);
1300    }
1301
1302    #[test]
1303    fn continue_set_minute_too_large_valid_jump() {
1304        let mut rdt = RadioDateTimeUtils::new(0);
1305        rdt.set_minute(Some(47), true, false);
1306        rdt.set_minute(Some(60), true, true);
1307        assert_eq!(rdt.minute, Some(47));
1308        assert_eq!(rdt.jump_minute, false);
1309    }
1310
1311    #[test]
1312    fn test_set_minute_some_valid_jump() {
1313        let mut rdt = RadioDateTimeUtils::new(0);
1314        rdt.set_minute(Some(47), true, true);
1315        assert_eq!(rdt.minute, Some(47));
1316        assert_eq!(rdt.jump_minute, false);
1317    }
1318
1319    #[test]
1320    fn continue_set_minute_some_valid_jump() {
1321        let mut rdt = RadioDateTimeUtils::new(0);
1322        rdt.set_minute(Some(47), true, true);
1323        rdt.set_minute(Some(48), true, true);
1324        assert_eq!(rdt.minute, Some(48));
1325        assert_eq!(rdt.jump_minute, true);
1326    }
1327
1328    #[test]
1329    fn continue_set_minute_some_none_no_jump() {
1330        let mut rdt = RadioDateTimeUtils::new(0);
1331        rdt.set_minute(Some(25), true, true);
1332        rdt.set_minute(None, true, true);
1333        assert_eq!(rdt.minute, Some(25));
1334        assert_eq!(rdt.jump_minute, false);
1335    }
1336
1337    #[test]
1338    fn continue_set_minute_none_some_no_jump() {
1339        let mut rdt = RadioDateTimeUtils::new(0);
1340        rdt.set_minute(None, true, true);
1341        rdt.set_minute(Some(25), true, true);
1342        assert_eq!(rdt.minute, Some(25));
1343        assert_eq!(rdt.jump_minute, false);
1344    }
1345
1346    #[test]
1347    fn test_set_dut1_some_invalid_jump() {
1348        let mut rdt = RadioDateTimeUtils::new(0);
1349        rdt.set_dut1(Some(47), false, true);
1350        assert_eq!(rdt.dut1, None);
1351        assert_eq!(rdt.jump_dut1, false);
1352    }
1353
1354    #[test]
1355    fn test_set_dut1_none_valid_jump() {
1356        let mut rdt = RadioDateTimeUtils::new(0);
1357        rdt.set_dut1(None, true, true);
1358        assert_eq!(rdt.dut1, None);
1359        assert_eq!(rdt.jump_dut1, false);
1360    }
1361
1362    #[test]
1363    fn test_set_dut1_too_small_valid_no_jump() {
1364        let mut rdt = RadioDateTimeUtils::new(0);
1365        rdt.set_dut1(Some(-100), true, false);
1366        assert_eq!(rdt.dut1, None);
1367        assert_eq!(rdt.jump_dut1, false);
1368    }
1369
1370    #[test]
1371    fn test_set_dut1_too_large_valid_no_jump() {
1372        let mut rdt = RadioDateTimeUtils::new(0);
1373        rdt.set_dut1(Some(100), true, false);
1374        assert_eq!(rdt.dut1, None);
1375        assert_eq!(rdt.jump_dut1, false);
1376    }
1377
1378    #[test]
1379    fn test_set_dut1_some_valid_no_jump() {
1380        let mut rdt = RadioDateTimeUtils::new(0);
1381        rdt.set_dut1(Some(47), true, false);
1382        assert_eq!(rdt.dut1, Some(47));
1383        assert_eq!(rdt.jump_dut1, false);
1384    }
1385
1386    #[test]
1387    fn continue_set_dut1_too_large_valid_jump() {
1388        let mut rdt = RadioDateTimeUtils::new(0);
1389        rdt.set_dut1(Some(47), true, false);
1390        rdt.set_dut1(Some(100), true, true);
1391        assert_eq!(rdt.dut1, Some(47));
1392        assert_eq!(rdt.jump_dut1, false);
1393    }
1394
1395    #[test]
1396    fn test_set_dut1_some_valid_jump() {
1397        let mut rdt = RadioDateTimeUtils::new(0);
1398        rdt.set_dut1(Some(47), true, true);
1399        assert_eq!(rdt.dut1, Some(47));
1400        assert_eq!(rdt.jump_dut1, false);
1401    }
1402
1403    #[test]
1404    fn continue_set_dut1_some_valid_jump() {
1405        let mut rdt = RadioDateTimeUtils::new(0);
1406        rdt.set_dut1(Some(47), true, true);
1407        rdt.set_dut1(Some(48), true, true);
1408        assert_eq!(rdt.dut1, Some(48));
1409        assert_eq!(rdt.jump_dut1, true);
1410    }
1411
1412    #[test]
1413    fn continue_set_dut1_some_none_no_jump() {
1414        let mut rdt = RadioDateTimeUtils::new(0);
1415        rdt.set_dut1(Some(25), true, true);
1416        rdt.set_dut1(None, true, true);
1417        assert_eq!(rdt.dut1, Some(25));
1418        assert_eq!(rdt.jump_dut1, false);
1419    }
1420
1421    #[test]
1422    fn continue_set_dut1_none_some_no_jump() {
1423        let mut rdt = RadioDateTimeUtils::new(0);
1424        rdt.set_dut1(None, true, true);
1425        rdt.set_dut1(Some(25), true, true);
1426        assert_eq!(rdt.dut1, Some(25));
1427        assert_eq!(rdt.jump_dut1, false);
1428    }
1429
1430    fn create_dut1_rdt() -> RadioDateTimeUtils {
1431        let mut rdt = RadioDateTimeUtils::new(0);
1432        rdt.set_utc_offset(0, false);
1433        rdt.set_year(Some(25), true, false);
1434        rdt.set_month(Some(5), true, false);
1435        rdt.set_weekday(Some(2), true, false);
1436        rdt.set_day(Some(20), true, false);
1437        rdt.set_hour(Some(22), true, false);
1438        rdt.set_minute(Some(16), true, false);
1439        rdt.set_dst(Some(true), Some(false), false);
1440        rdt
1441    }
1442
1443    #[test]
1444    fn test_set_dut1_some_invalid_jump_utc_set() {
1445        let mut rdt = create_dut1_rdt();
1446        rdt.set_dut1(Some(47), false, true);
1447        assert_eq!(rdt.dut1, None);
1448        assert_eq!(rdt.jump_dut1, false);
1449    }
1450
1451    #[test]
1452    fn test_set_dut1_none_valid_jump_utc_set() {
1453        let mut rdt = create_dut1_rdt();
1454        rdt.set_dut1(None, true, true);
1455        assert_eq!(rdt.dut1, None);
1456        assert_eq!(rdt.jump_dut1, false);
1457    }
1458
1459    #[test]
1460    fn continue_set_dut1_too_large_valid_jump_utc_set() {
1461        let mut rdt = create_dut1_rdt();
1462        rdt.set_dut1(Some(47), true, false);
1463        rdt.set_dut1(Some(100), true, true);
1464        assert_eq!(rdt.dut1, Some(47));
1465        assert_eq!(rdt.jump_dut1, false);
1466    }
1467
1468    #[test]
1469    fn test_set_dut1_some_valid_jump_utc_set() {
1470        let mut rdt = create_dut1_rdt();
1471        rdt.set_dut1(Some(47), true, true);
1472        assert_eq!(rdt.dut1, Some(47));
1473        assert_eq!(rdt.jump_dut1, false);
1474    }
1475
1476    #[test]
1477    fn continue_set_dut1_some_valid_jump_utc_set() {
1478        let mut rdt = create_dut1_rdt();
1479        rdt.set_dut1(Some(47), true, true);
1480        rdt.set_dut1(Some(48), true, true);
1481        assert_eq!(rdt.dut1, Some(48));
1482        assert_eq!(rdt.jump_dut1, true);
1483    }
1484
1485    #[test]
1486    fn continue_set_dut1_some_none_no_jump_utc_set() {
1487        let mut rdt = create_dut1_rdt();
1488        rdt.set_dut1(Some(25), true, true);
1489        rdt.set_dut1(None, true, true);
1490        assert_eq!(rdt.dut1, Some(25));
1491        assert_eq!(rdt.jump_dut1, false);
1492    }
1493
1494    #[test]
1495    fn continue_set_dut1_none_some_no_jump_utc_set() {
1496        let mut rdt = create_dut1_rdt();
1497        rdt.set_dut1(None, true, true);
1498        rdt.set_dut1(Some(25), true, true);
1499        assert_eq!(rdt.dut1, Some(25));
1500        assert_eq!(rdt.jump_dut1, false);
1501    }
1502
1503    // below DUT1 tests adapted from msf60_utils
1504
1505    #[test]
1506    fn test_dut1_none_none_no_jump() {
1507        let mut rdt = create_dut1_rdt();
1508        rdt.set_minute(Some(58), true, false);
1509        rdt.set_dut1(None, true, false);
1510        assert_eq!(rdt.dut1, None);
1511        assert_eq!(rdt.jump_dut1, false);
1512        rdt.set_minute(Some(59), true, true);
1513        rdt.set_dut1(None, true, true);
1514        assert_eq!(rdt.dut1, None);
1515        assert_eq!(rdt.jump_dut1, false);
1516    }
1517
1518    #[test]
1519    fn test_dut1_none_some_no_jump() {
1520        let mut rdt = create_dut1_rdt();
1521        rdt.set_minute(Some(58), true, false);
1522        rdt.set_dut1(None, true, false);
1523        assert_eq!(rdt.dut1, None);
1524        assert_eq!(rdt.jump_dut1, false);
1525        rdt.set_minute(Some(59), true, true);
1526        rdt.set_dut1(Some(-2), true, true);
1527        assert_eq!(rdt.dut1, Some(-2));
1528        assert_eq!(rdt.jump_dut1, false);
1529    }
1530
1531    #[test]
1532    fn test_dut1_some_none_no_jump() {
1533        let mut rdt = create_dut1_rdt();
1534        rdt.set_minute(Some(58), true, false);
1535        rdt.set_dut1(Some(-2), true, false);
1536        assert_eq!(rdt.dut1, Some(-2));
1537        assert_eq!(rdt.jump_dut1, false);
1538        rdt.set_minute(Some(59), true, true);
1539        rdt.set_dut1(Some(-2), false, true); // simulate dut1p * dut1n != 0
1540        assert_eq!(rdt.dut1, Some(-2));
1541        assert_eq!(rdt.jump_dut1, false);
1542    }
1543
1544    #[test]
1545    fn test_dut1_some_x_some_x_no_jump() {
1546        let mut rdt = create_dut1_rdt();
1547        rdt.set_minute(Some(58), true, false);
1548        rdt.set_dut1(Some(-2), true, false);
1549        assert_eq!(rdt.dut1, Some(-2));
1550        assert_eq!(rdt.jump_dut1, false);
1551        rdt.set_minute(Some(59), true, true);
1552        rdt.set_dut1(Some(-2), true, true); // do nothing
1553        assert_eq!(rdt.dut1, Some(-2));
1554        assert_eq!(rdt.jump_dut1, false);
1555    }
1556
1557    #[test]
1558    fn test_dut1_some_x_some_y_jump() {
1559        let mut rdt = create_dut1_rdt();
1560        rdt.set_minute(Some(58), true, false);
1561        rdt.set_dut1(Some(-2), true, false);
1562        assert_eq!(rdt.dut1, Some(-2));
1563        assert_eq!(rdt.jump_dut1, false);
1564        rdt.set_minute(Some(59), true, true);
1565        rdt.set_dut1(Some(-3), true, true);
1566        assert_eq!(rdt.dut1, Some(-3));
1567        assert_eq!(rdt.jump_dut1, true);
1568        // no jump for DUT1=-3 on next minute (23:00):
1569        rdt.set_hour(Some(23), true, true);
1570        rdt.set_minute(Some(0), true, true);
1571        rdt.set_dut1(Some(-3), true, true);
1572        assert_eq!(rdt.dut1, Some(-3));
1573        assert_eq!(rdt.jump_dut1, false);
1574    }
1575
1576    #[test]
1577    fn test_dut1_some_x_some_x_midnight_no_jump() {
1578        let mut rdt = create_dut1_rdt();
1579        rdt.set_minute(Some(59), true, false);
1580        rdt.set_hour(Some(0), true, false); // BST active in create_dut1_rdt()
1581        rdt.set_dut1(Some(-2), true, false);
1582        assert_eq!(rdt.dut1, Some(-2));
1583        assert_eq!(rdt.jump_dut1, false);
1584        rdt.set_minute(Some(0), true, true);
1585        rdt.set_hour(Some(1), true, true);
1586        rdt.set_dut1(Some(-2), true, true); // no change
1587        assert_eq!(rdt.dut1, Some(-2));
1588        assert_eq!(rdt.jump_dut1, false);
1589    }
1590
1591    #[test]
1592    fn test_dut1_some_x_some_y_midnight_no_jump() {
1593        let mut rdt = create_dut1_rdt();
1594        assert_eq!(rdt.dst, Some(DST_SUMMER));
1595        rdt.set_minute(Some(59), true, false);
1596        rdt.set_hour(Some(0), true, false); // BST active in create_dut1_rdt()
1597        rdt.set_dut1(Some(-2), true, false);
1598        assert_eq!(rdt.dut1, Some(-2));
1599        assert_eq!(rdt.jump_dut1, false);
1600        assert_eq!(rdt.dst, Some(DST_SUMMER));
1601        rdt.set_minute(Some(0), true, true);
1602        rdt.set_hour(Some(1), true, true);
1603        rdt.set_dut1(Some(-3), true, true);
1604        assert_eq!(rdt.dut1, Some(-3));
1605        assert_eq!(rdt.jump_dut1, false);
1606    }
1607
1608    #[test]
1609    fn test_last_day7_regular() {
1610        let mut dcf77 = RadioDateTimeUtils::new(7);
1611        dcf77.year = Some(22);
1612        dcf77.month = Some(6);
1613        dcf77.weekday = Some(7);
1614        assert_eq!(dcf77.last_day(5), Some(30)); // today, Sunday 2022-06-05
1615    }
1616
1617    #[test]
1618    fn test_last_day7_non_existent() {
1619        let mut dcf77 = RadioDateTimeUtils::new(7);
1620        dcf77.year = Some(22);
1621        dcf77.month = Some(2);
1622        dcf77.weekday = Some(4);
1623        assert_eq!(dcf77.last_day(29), Some(28)); // non-existent date, Thursday 22-02-29
1624    }
1625
1626    #[test]
1627    fn test_last_day7_happy_new_year() {
1628        let mut dcf77 = RadioDateTimeUtils::new(7);
1629        dcf77.year = Some(0);
1630        dcf77.month = Some(1);
1631        dcf77.weekday = Some(1);
1632        assert_eq!(dcf77.last_day(1), Some(31));
1633        // first day, weekday off/do-not-care, Monday 00-01-01
1634    }
1635
1636    #[test]
1637    fn test_last_day7_regular_leap() {
1638        let mut dcf77 = RadioDateTimeUtils::new(7);
1639        dcf77.year = Some(20);
1640        dcf77.month = Some(2);
1641        dcf77.weekday = Some(3);
1642        assert_eq!(dcf77.last_day(3), Some(29)); // regular leap year, Wednesday 2020-02-03
1643    }
1644
1645    #[test]
1646    fn test_last_day7_bogus_weekday() {
1647        let mut dcf77 = RadioDateTimeUtils::new(7);
1648        dcf77.year = Some(20);
1649        dcf77.month = Some(2);
1650        dcf77.weekday = Some(4);
1651        assert_eq!(dcf77.last_day(3), Some(29));
1652        // same date with bogus weekday, "Thursday" 2020-02-03
1653    }
1654
1655    #[test]
1656    fn test_last_day7_century_leap_1() {
1657        let mut dcf77 = RadioDateTimeUtils::new(7);
1658        dcf77.year = Some(0);
1659        dcf77.month = Some(2);
1660        dcf77.weekday = Some(2);
1661        assert_eq!(dcf77.last_day(1), Some(29));
1662        // century-leap-year, day/weekday must match, Tuesday 2000-02-01
1663    }
1664
1665    #[test]
1666    fn test_last_day7_century_regular() {
1667        let mut dcf77 = RadioDateTimeUtils::new(7);
1668        dcf77.year = Some(0);
1669        dcf77.month = Some(2);
1670        dcf77.weekday = Some(1);
1671        assert_eq!(dcf77.last_day(1), Some(28)); // century-regular-year, Monday 2100-02-01
1672    }
1673
1674    #[test]
1675    fn test_last_day7_century_leap_6() {
1676        let mut dcf77 = RadioDateTimeUtils::new(7);
1677        dcf77.year = Some(0);
1678        dcf77.month = Some(2);
1679        dcf77.weekday = Some(7);
1680        assert_eq!(dcf77.last_day(6), Some(29)); // century-leap-year, Sunday 2000-02-06
1681    }
1682
1683    #[test]
1684    fn test_last_day0_century_leap() {
1685        let mut msf = RadioDateTimeUtils::new(0);
1686        msf.year = Some(0);
1687        msf.month = Some(2);
1688        msf.weekday = Some(0);
1689        assert_eq!(msf.last_day(6), Some(29)); // century-leap-year, Sunday 2000-02-06
1690    }
1691
1692    #[test]
1693    fn test_last_day0_too_large_day() {
1694        let mut msf = RadioDateTimeUtils::new(0);
1695        msf.year = Some(0);
1696        msf.month = Some(2);
1697        msf.weekday = Some(0);
1698        assert_eq!(msf.last_day(32), None); // invalid input, Sunday 00-02-32
1699    }
1700
1701    #[test]
1702    fn test_dst_some_starting_no_dst_no_announcement_no_jump() {
1703        let mut rdt = RadioDateTimeUtils::new(0);
1704        // Simple initial minute update, no announcement:
1705        rdt.minute = Some(11);
1706        rdt.set_dst(Some(false), Some(false), false);
1707        assert_eq!(rdt.dst, Some(0)); // no flags
1708        assert_eq!(rdt.dst_count, 0);
1709    }
1710
1711    #[test]
1712    fn test_dst_some_starting_dst_no_announcement_no_jump() {
1713        let mut rdt = RadioDateTimeUtils::new(0);
1714        // Simple initial minute update, no announcement:
1715        rdt.minute = Some(11);
1716        rdt.set_dst(Some(true), Some(false), false);
1717        assert_eq!(rdt.dst, Some(DST_SUMMER));
1718        assert_eq!(rdt.dst_count, 0);
1719    }
1720
1721    #[test]
1722    fn test_dst_starting_at_new_hour_no_announcement_jump() {
1723        let mut rdt = RadioDateTimeUtils::new(0);
1724        // Simple initial minute update at top-of-hour, no announcement:
1725        rdt.minute = Some(0);
1726        rdt.set_dst(Some(false), Some(false), true);
1727        assert_eq!(rdt.dst, Some(0)); // no flags
1728        assert_eq!(rdt.dst_count, 0);
1729    }
1730
1731    #[test]
1732    fn test_dst_running_no_announcement_jump() {
1733        let mut rdt = RadioDateTimeUtils::new(0);
1734        // A bit further in the hour. no announcement:
1735        rdt.minute = Some(15);
1736        rdt.minutes_running = 15;
1737        rdt.set_dst(Some(false), Some(false), true);
1738        assert_eq!(rdt.dst, Some(0)); // no flags
1739        assert_eq!(rdt.dst_count, 0);
1740    }
1741
1742    #[test]
1743    fn test_dst_spurious_announcement_jump() {
1744        let mut rdt = RadioDateTimeUtils::new(7);
1745        // DST change announced spuriously:
1746        rdt.minute = Some(15);
1747        rdt.minutes_running = 15;
1748        rdt.set_dst(Some(false), Some(true), true);
1749        assert_eq!(rdt.dst, Some(0)); // no flags
1750        assert_eq!(rdt.dst_count, 1);
1751    }
1752
1753    #[test]
1754    fn test_dst_announced() {
1755        let mut rdt = RadioDateTimeUtils::new(7);
1756        // Change our mind, the previous announcement was valid:
1757        // Do not cheat with self.dst_count:
1758        rdt.minute = Some(0);
1759        for _ in 0..10 {
1760            rdt.minute = Some(rdt.minute.unwrap() + 1);
1761            rdt.minutes_running += 1;
1762            rdt.set_dst(Some(false), Some(true), true);
1763        }
1764        assert_eq!(rdt.dst, Some(DST_ANNOUNCED));
1765        assert_eq!(rdt.minutes_running, 10);
1766        assert_eq!(rdt.dst_count, 10);
1767    }
1768
1769    #[test]
1770    fn continue_dst_to_summer() {
1771        let mut rdt = RadioDateTimeUtils::new(7);
1772        // Announcement bit was gone, but there should be enough evidence:
1773        rdt.minute = Some(0);
1774        for _ in 0..11 {
1775            rdt.minute = Some(rdt.minute.unwrap() + 1);
1776            rdt.minutes_running += 1;
1777            rdt.set_dst(Some(false), Some(true), true);
1778        }
1779        assert_eq!(rdt.dst, Some(DST_ANNOUNCED));
1780        rdt.minute = Some(0);
1781        rdt.set_dst(Some(true), Some(false), true);
1782        // Top of hour, so announcement should be reset:
1783        assert_eq!(rdt.dst, Some(DST_PROCESSED | DST_SUMMER));
1784        assert_eq!(rdt.dst_count, 0);
1785    }
1786
1787    #[test]
1788    fn continue_dst_to_winter() {
1789        let mut rdt = RadioDateTimeUtils::new(7);
1790        // Announcement bit was gone, but there should be enough evidence:
1791        rdt.minute = Some(0);
1792        for _ in 0..12 {
1793            rdt.minute = Some(rdt.minute.unwrap() + 1);
1794            rdt.minutes_running += 1;
1795            rdt.set_dst(Some(true), Some(true), true);
1796        }
1797        assert_eq!(rdt.dst, Some(DST_ANNOUNCED | DST_SUMMER));
1798        rdt.minute = Some(0);
1799        rdt.set_dst(Some(false), Some(false), true);
1800        // Top of hour, so announcement should be reset:
1801        assert_eq!(rdt.dst, Some(DST_PROCESSED));
1802        assert_eq!(rdt.dst_count, 0);
1803    }
1804
1805    #[test]
1806    fn continue2_dst_none_minute() {
1807        let mut rdt = RadioDateTimeUtils::new(7);
1808        rdt.minute = Some(0);
1809        for _ in 0..13 {
1810            rdt.minute = Some(rdt.minute.unwrap() + 1);
1811            rdt.minutes_running += 1;
1812            rdt.set_dst(Some(false), Some(true), true);
1813        }
1814        assert_eq!(rdt.dst, Some(DST_ANNOUNCED));
1815        rdt.minute = Some(0);
1816        rdt.set_dst(Some(true), Some(false), true);
1817        // Nothing should change because of the None minute:
1818        rdt.minute = None;
1819        rdt.set_dst(Some(true), Some(true), true);
1820        assert_eq!(rdt.dst, Some(DST_PROCESSED | DST_SUMMER));
1821        assert_eq!(rdt.dst_count, 1);
1822    }
1823
1824    #[test]
1825    fn continue_dst_jump_no_dst_no_jump() {
1826        let mut rdt = RadioDateTimeUtils::new(0);
1827        rdt.minute = Some(11);
1828        rdt.set_dst(Some(false), Some(false), false);
1829        assert_eq!(rdt.dst, Some(0));
1830        assert_eq!(rdt.dst_count, 0);
1831        rdt.set_dst(Some(true), Some(false), false);
1832        assert_eq!(rdt.dst, Some(0)); // DST jumped but we do not care
1833        assert_eq!(rdt.dst_count, 0);
1834    }
1835
1836    #[test]
1837    fn continue_dst_jump_no_dst_jump() {
1838        let mut rdt = RadioDateTimeUtils::new(0);
1839        rdt.minute = Some(11);
1840        rdt.set_dst(Some(false), Some(false), true);
1841        assert_eq!(rdt.dst, Some(0));
1842        assert_eq!(rdt.dst_count, 0);
1843        rdt.set_dst(Some(true), Some(false), true);
1844        assert_eq!(rdt.dst, Some(DST_JUMP)); // DST jumped
1845        assert_eq!(rdt.dst_count, 0);
1846    }
1847
1848    #[test]
1849    fn continue_dst_jump_dst_no_jump() {
1850        let mut rdt = RadioDateTimeUtils::new(0);
1851        rdt.minute = Some(11);
1852        rdt.set_dst(Some(true), Some(false), false);
1853        assert_eq!(rdt.dst, Some(DST_SUMMER));
1854        assert_eq!(rdt.dst_count, 0);
1855        rdt.set_dst(Some(false), Some(false), false);
1856        assert_eq!(rdt.dst, Some(DST_SUMMER)); // DST jumped but we do not care
1857        assert_eq!(rdt.dst_count, 0);
1858    }
1859
1860    #[test]
1861    fn continue_dst_jump_dst_jump() {
1862        let mut rdt = RadioDateTimeUtils::new(0);
1863        rdt.minute = Some(11);
1864        rdt.set_dst(Some(true), Some(false), true);
1865        assert_eq!(rdt.dst, Some(DST_SUMMER));
1866        assert_eq!(rdt.dst_count, 0);
1867        rdt.set_dst(Some(false), Some(false), true);
1868        assert_eq!(rdt.dst, Some(DST_JUMP | DST_SUMMER)); // DST jumped
1869        assert_eq!(rdt.dst_count, 0);
1870    }
1871
1872    #[test]
1873    fn test_leap_second_some_starting_no_announcement() {
1874        let mut rdt = RadioDateTimeUtils::new(7);
1875        // Simple initial minute update, no announcement:
1876        rdt.minute = Some(11);
1877        rdt.set_leap_second(Some(false), 60);
1878        assert_eq!(rdt.leap_second, Some(0)); // no flags
1879        assert_eq!(rdt.leap_second_count, 0);
1880    }
1881
1882    #[test]
1883    fn test_leap_second_starting_at_new_hour_no_announcement() {
1884        let mut rdt = RadioDateTimeUtils::new(7);
1885        // Simple initial minute update at top-of-hour, no announcement:
1886        rdt.minute = Some(0);
1887        rdt.set_leap_second(Some(false), 60);
1888        assert_eq!(rdt.leap_second, Some(0)); // no flags
1889        assert_eq!(rdt.leap_second_count, 0);
1890    }
1891
1892    #[test]
1893    fn test_leap_second_running_no_announcement() {
1894        let mut rdt = RadioDateTimeUtils::new(7);
1895        // A bit further in the hour. no announcement:
1896        rdt.minute = Some(15);
1897        rdt.minutes_running = 15;
1898        rdt.set_leap_second(Some(false), 60);
1899        assert_eq!(rdt.leap_second, Some(0)); // no flags
1900        assert_eq!(rdt.leap_second_count, 0);
1901    }
1902
1903    #[test]
1904    fn test_leap_second_spurious_announcement() {
1905        let mut rdt = RadioDateTimeUtils::new(7);
1906        // Leap second announced spuriously:
1907        rdt.minute = Some(15);
1908        rdt.minutes_running = 15;
1909        rdt.set_leap_second(Some(true), 60);
1910        assert_eq!(rdt.leap_second, Some(0)); // no flags
1911        assert_eq!(rdt.leap_second_count, 1);
1912    }
1913
1914    #[test]
1915    fn test_leap_second_announced() {
1916        let mut rdt = RadioDateTimeUtils::new(7);
1917        // Change our mind, the previous announcement was valid:
1918        // Do not cheat with self.leap_second_count:
1919        rdt.minute = Some(0);
1920        for _ in 0..10 {
1921            rdt.minute = Some(rdt.minute.unwrap() + 1);
1922            rdt.minutes_running += 1;
1923            rdt.set_leap_second(Some(true), 60);
1924        }
1925        assert_eq!(rdt.leap_second, Some(LEAP_ANNOUNCED));
1926        assert_eq!(rdt.minutes_running, 10);
1927        assert_eq!(rdt.leap_second_count, 10);
1928    }
1929
1930    #[test]
1931    fn continue2_leap_second_missing() {
1932        let mut rdt = RadioDateTimeUtils::new(7);
1933        // Missing leap second.
1934        // Announcement bit was gone, but there should be enough evidence:
1935        rdt.minute = Some(0);
1936        for _ in 0..11 {
1937            rdt.minute = Some(rdt.minute.unwrap() + 1);
1938            rdt.minutes_running += 1;
1939            rdt.set_leap_second(Some(true), 60);
1940        }
1941        assert_eq!(rdt.leap_second, Some(LEAP_ANNOUNCED));
1942        rdt.minute = Some(0);
1943        rdt.set_leap_second(Some(false), 60 /* not 61 */);
1944        // Top of hour, so announcement should be reset:
1945        assert_eq!(rdt.leap_second, Some(LEAP_PROCESSED | LEAP_MISSING));
1946        assert_eq!(rdt.leap_second_count, 0);
1947        rdt.minute = Some(1);
1948        // New hour has started:
1949        rdt.set_leap_second(Some(false), 60);
1950        assert_eq!(rdt.leap_second, Some(0));
1951    }
1952
1953    #[test]
1954    fn continue_leap_second_present() {
1955        let mut rdt = RadioDateTimeUtils::new(7);
1956        // We got a leap second.
1957        // Announcement bit was gone, but there should be enough evidence:
1958        rdt.minute = Some(0);
1959        for _ in 0..12 {
1960            rdt.minute = Some(rdt.minute.unwrap() + 1);
1961            rdt.minutes_running += 1;
1962            rdt.set_leap_second(Some(true), 60);
1963        }
1964        assert_eq!(rdt.leap_second, Some(LEAP_ANNOUNCED));
1965        rdt.minute = Some(0);
1966        rdt.set_leap_second(Some(false), 61);
1967        // Top of hour, so announcement should be reset:
1968        assert_eq!(rdt.leap_second, Some(LEAP_PROCESSED));
1969        assert_eq!(rdt.leap_second_count, 0);
1970    }
1971
1972    #[test]
1973    fn continue2_leap_second_none_minute() {
1974        let mut rdt = RadioDateTimeUtils::new(7);
1975        rdt.minute = Some(0);
1976        for _ in 0..13 {
1977            rdt.minute = Some(rdt.minute.unwrap() + 1);
1978            rdt.minutes_running += 1;
1979            rdt.set_leap_second(Some(true), 60);
1980        }
1981        assert_eq!(rdt.leap_second, Some(LEAP_ANNOUNCED));
1982        rdt.minute = Some(0);
1983        rdt.set_leap_second(Some(false), 60 /* not 61 */);
1984        // Nothing should happen because of the None minute:
1985        rdt.minute = None;
1986        rdt.set_leap_second(Some(true), 61);
1987        assert_eq!(rdt.leap_second, Some(LEAP_PROCESSED | LEAP_MISSING));
1988        assert_eq!(rdt.leap_second_count, 1);
1989    }
1990
1991    #[test]
1992    fn test_add_minute_invalid_input() {
1993        let mut rdt = RadioDateTimeUtils::new(0);
1994        // Test invalid input:
1995        assert_eq!(rdt.add_minute(), false);
1996        assert_eq!(rdt.minute, None);
1997    }
1998
1999    #[test]
2000    fn test_add_minute_century_flip() {
2001        let mut rdt = RadioDateTimeUtils::new(0);
2002        // Test the big century flip, these fields must all be set:
2003        rdt.minute = Some(59);
2004        rdt.hour = Some(23);
2005        rdt.day = Some(31);
2006        rdt.month = Some(12);
2007        rdt.year = Some(99);
2008        rdt.weekday = Some(5); // 1999-12-31 is a Friday
2009        rdt.dst = Some(0); // no flags set, i.e. daylight saving time unset
2010        assert_eq!(rdt.add_minute(), true);
2011        assert_eq!(rdt.minute, Some(0));
2012        assert_eq!(rdt.hour, Some(0));
2013        assert_eq!(rdt.day, Some(1));
2014        assert_eq!(rdt.month, Some(1));
2015        assert_eq!(rdt.year, Some(0));
2016        assert_eq!(rdt.weekday, Some(6));
2017    }
2018
2019    #[test]
2020    fn test_add_minute_set_dst() {
2021        let mut rdt = RadioDateTimeUtils::new(0);
2022        // Test DST becoming active, any hour and date are fine:
2023        rdt.minute = Some(59);
2024        rdt.hour = Some(17);
2025        rdt.day = Some(1);
2026        rdt.month = Some(1);
2027        rdt.year = Some(0);
2028        rdt.weekday = Some(6); // 2000-01-01 is a Saturday
2029        rdt.dst = Some(DST_ANNOUNCED);
2030        assert_eq!(rdt.add_minute(), true);
2031        assert_eq!(rdt.dst, Some(DST_ANNOUNCED)); // add_minute() does not change any DST flag
2032        assert_eq!(rdt.minute, Some(0));
2033        assert_eq!(rdt.hour, Some(19));
2034        assert_eq!(rdt.day, Some(1));
2035        assert_eq!(rdt.month, Some(1));
2036        assert_eq!(rdt.year, Some(0));
2037        assert_eq!(rdt.weekday, Some(6));
2038    }
2039
2040    #[test]
2041    fn test_add_minute_unset_dst() {
2042        let mut rdt = RadioDateTimeUtils::new(0);
2043        // Test DST becoming inactive:
2044        rdt.minute = Some(59);
2045        rdt.hour = Some(19);
2046        rdt.day = Some(1);
2047        rdt.month = Some(1);
2048        rdt.year = Some(0);
2049        rdt.weekday = Some(6); // 2000-01-01 is a Saturday
2050        rdt.dst = Some(DST_SUMMER | DST_ANNOUNCED);
2051        assert_eq!(rdt.add_minute(), true);
2052        assert_eq!(rdt.dst, Some(DST_SUMMER | DST_ANNOUNCED));
2053        // add_minute() does not change any DST flag
2054        assert_eq!(rdt.minute, Some(0));
2055        assert_eq!(rdt.hour, Some(19));
2056        assert_eq!(rdt.day, Some(1));
2057        assert_eq!(rdt.month, Some(1));
2058        assert_eq!(rdt.year, Some(0));
2059        assert_eq!(rdt.weekday, Some(6));
2060    }
2061
2062    #[test]
2063    fn test_add_minute_msf_saturday_sunday() {
2064        let mut rdt = RadioDateTimeUtils::new(0);
2065        // Test flipping to min_weekday (MSF), Saturday 6 -> Sunday 0:
2066        rdt.minute = Some(59);
2067        rdt.hour = Some(23);
2068        rdt.day = Some(1);
2069        rdt.month = Some(1);
2070        rdt.year = Some(0);
2071        rdt.weekday = Some(6); // 2000-01-01 is a Saturday
2072        rdt.dst = Some(0);
2073        assert_eq!(rdt.add_minute(), true);
2074        assert_eq!(rdt.minute, Some(0));
2075        assert_eq!(rdt.hour, Some(0));
2076        assert_eq!(rdt.day, Some(2));
2077        assert_eq!(rdt.month, Some(1));
2078        assert_eq!(rdt.year, Some(0));
2079        assert_eq!(rdt.weekday, Some(0));
2080    }
2081
2082    #[test]
2083    fn test_add_minute_dcf77_sunday_monday() {
2084        // Test flipping to min_weekday (DCF77), Sunday 7 -> Monday 1:
2085        let mut rdt = RadioDateTimeUtils::new(7);
2086        rdt.minute = Some(59);
2087        rdt.hour = Some(23);
2088        rdt.day = Some(2);
2089        rdt.month = Some(1);
2090        rdt.year = Some(0);
2091        rdt.weekday = Some(7); // 2000-01-02 is a Sunday
2092        rdt.dst = Some(0); // no flags set, i.e. daylight saving time unset
2093        assert_eq!(rdt.add_minute(), true);
2094        assert_eq!(rdt.minute, Some(0));
2095        assert_eq!(rdt.hour, Some(0));
2096        assert_eq!(rdt.day, Some(3));
2097        assert_eq!(rdt.month, Some(1));
2098        assert_eq!(rdt.year, Some(0));
2099        assert_eq!(rdt.weekday, Some(1));
2100    }
2101
2102    #[test]
2103    fn test_add_minute_not_25_hours() {
2104        let mut rdt = RadioDateTimeUtils::new(7);
2105        rdt.minute = Some(59);
2106        rdt.hour = Some(23);
2107        rdt.day = Some(21);
2108        rdt.month = Some(7);
2109        rdt.year = Some(24);
2110        rdt.weekday = Some(7);
2111        rdt.dst = Some(DST_ANNOUNCED); // trigger DST switch
2112        assert_eq!(rdt.add_minute(), true);
2113        assert_eq!(rdt.minute, Some(0));
2114        assert_eq!(rdt.hour, Some(1)); // just became DST
2115        assert_eq!(rdt.day, Some(22));
2116        assert_eq!(rdt.month, Some(7));
2117        assert_eq!(rdt.year, Some(24));
2118        assert_eq!(rdt.weekday, Some(1));
2119    }
2120
2121    #[test]
2122    fn continue_initial_dst_decoding() {
2123        // initial DST decoding bug from msf60_utils perhaps caused here?
2124        //   DCF77 == "240407 Su 223750 s  ", status = clear
2125        //   MSF   == "240407 Su 213750 w 0", status = clear
2126
2127        let mut rdt = RadioDateTimeUtils::new(0);
2128        rdt.minute = Some(33);
2129        rdt.hour = Some(21);
2130        rdt.day = Some(7);
2131        rdt.month = Some(10);
2132        rdt.year = Some(24);
2133        rdt.weekday = Some(1);
2134        rdt.first_minute = false;
2135
2136        // Below rdt.add_minute() and rdt.bump_minutes_running() are not strictly needed as we are
2137        // in the middle of the hour and the announcement flag to rdt.set_dst() is set to
2138        // Some(false) anyway. Calling rdt.add_minute() simulates actual usage in dcf77_utils and
2139        // msf60_utils:
2140
2141        // DCF77 and MSF apply same logic in decode_time() if minute length OK:
2142        //     self.radio_datetime.set_dst(
2143        //         dst,
2144        //         self.bit_buffer[16],
2145        //         added_minute && !self.first_minute,
2146        //     );
2147        //     self.radio_datetime.set_dst(
2148        //         self.bit_buffer_b[(58 + offset) as usize],
2149        //         self.bit_buffer_b[(53 + offset) as usize],
2150        //         added_minute && !self.first_minute,
2151        //     );
2152
2153        rdt.dst = Some(0); // no flags set
2154        assert_eq!(rdt.add_minute(), true);
2155        rdt.bump_minutes_running();
2156        assert_eq!(rdt.minute, Some(34)); // sanity check
2157        assert_eq!(rdt.dst, Some(0)); // no DST flags yet
2158        assert_eq!(rdt.first_minute, false);
2159
2160        rdt.set_dst(Some(true), Some(false), true);
2161        assert_eq!(rdt.add_minute(), true);
2162        rdt.bump_minutes_running();
2163        assert_eq!(rdt.minute, Some(35)); // sanity check
2164        assert_eq!(rdt.dst, Some(DST_JUMP)); // should stay non-DST
2165        assert_eq!(rdt.first_minute, false);
2166
2167        rdt.first_minute = true;
2168        rdt.set_dst(Some(true), Some(false), true);
2169        assert_eq!(rdt.add_minute(), true);
2170        rdt.bump_minutes_running();
2171        assert_eq!(rdt.minute, Some(36)); // sanity check
2172        assert_eq!(rdt.dst, Some(DST_SUMMER)); // valid DST because first_minute was true
2173        assert_eq!(rdt.first_minute, false);
2174
2175        rdt.set_dst(Some(false), Some(false), true);
2176        assert_eq!(rdt.add_minute(), true);
2177        rdt.bump_minutes_running();
2178        assert_eq!(rdt.minute, Some(37)); // sanity check
2179        assert_eq!(rdt.dst, Some(DST_SUMMER | DST_JUMP)); // should stay DST
2180        assert_eq!(rdt.first_minute, false);
2181
2182        rdt.first_minute = true;
2183        rdt.set_dst(Some(false), Some(false), true);
2184        assert_eq!(rdt.add_minute(), true);
2185        rdt.bump_minutes_running();
2186        assert_eq!(rdt.minute, Some(38)); // sanity check
2187        assert_eq!(rdt.dst, Some(0)); // valid non-DST
2188        assert_eq!(rdt.first_minute, false);
2189    }
2190
2191    // WWVB does not broadcast day-of-week (neither in the amplitude modulation nor in the phase
2192    // modulation format), just pick 0 here.
2193
2194    #[test]
2195    fn test_set_wvvb_jan_1() {
2196        let mut wwvb = RadioDateTimeUtils::new(0);
2197        wwvb.set_month_day(Some(1), Some(false), true, true, false);
2198        assert_eq!(wwvb.month, Some(1));
2199        assert_eq!(wwvb.day, Some(1));
2200    }
2201
2202    #[test]
2203    fn test_set_wwvb_dec_31_normal() {
2204        let mut wwvb = RadioDateTimeUtils::new(0);
2205        wwvb.set_month_day(Some(365), Some(false), true, true, false);
2206        assert_eq!(wwvb.month, Some(12));
2207        assert_eq!(wwvb.day, Some(31));
2208    }
2209
2210    #[test]
2211    fn test_set_wwvb_dec_31_leap() {
2212        let mut wwvb = RadioDateTimeUtils::new(0);
2213        wwvb.set_month_day(Some(366), Some(true), true, true, false);
2214        assert_eq!(wwvb.month, Some(12));
2215        assert_eq!(wwvb.day, Some(31));
2216    }
2217
2218    #[test]
2219    fn test_set_wwvb_0() {
2220        let mut wwvb = RadioDateTimeUtils::new(0);
2221        wwvb.set_month_day(Some(0), Some(false), true, true, false);
2222        assert_eq!(wwvb.month, None);
2223        assert_eq!(wwvb.day, None);
2224    }
2225
2226    #[test]
2227    fn test_set_wwvb_too_large() {
2228        let mut wwvb = RadioDateTimeUtils::new(0);
2229        wwvb.set_month_day(Some(366), Some(false), true, true, false);
2230        assert_eq!(wwvb.month, None);
2231        assert_eq!(wwvb.day, None);
2232    }
2233
2234    #[test]
2235    fn continue_set_wwvb_jumped() {
2236        let mut wwvb = RadioDateTimeUtils::new(0);
2237        wwvb.set_month_day(Some(311), Some(true), true, true, true);
2238        assert_eq!(wwvb.month, Some(11));
2239        assert_eq!(wwvb.day, Some(6));
2240        assert_eq!(wwvb.jump_month, false);
2241        assert_eq!(wwvb.jump_day, false);
2242        wwvb.set_month_day(Some(318), Some(true), true, true, true);
2243        assert_eq!(wwvb.month, Some(11));
2244        assert_eq!(wwvb.day, Some(13));
2245        assert_eq!(wwvb.jump_month, false);
2246        assert_eq!(wwvb.jump_day, true);
2247    }
2248
2249    #[test]
2250    fn test_set_wwvb_no_yearday() {
2251        let mut wwvb = RadioDateTimeUtils::new(0);
2252        wwvb.set_month_day(None, Some(false), true, true, false);
2253        assert_eq!(wwvb.month, None);
2254        assert_eq!(wwvb.day, None);
2255    }
2256
2257    #[test]
2258    fn test_set_wwvb_no_leap_indicator() {
2259        let mut wwvb = RadioDateTimeUtils::new(0);
2260        wwvb.set_month_day(Some(342), None, true, true, false);
2261        assert_eq!(wwvb.month, None);
2262        assert_eq!(wwvb.day, None);
2263    }
2264
2265    #[test]
2266    fn test_incr_minute_leap_regular() {
2267        let mut rdt = RadioDateTimeUtils::new(7);
2268        rdt.year = Some(24);
2269        rdt.month = Some(2);
2270        rdt.day = Some(28);
2271        rdt.weekday = Some(4);
2272        rdt.hour = Some(23);
2273        rdt.minute = Some(59);
2274        rdt.dst = Some(0); // prevent unwrapping a None
2275        let rdt2 = RadioDateTimeUtils::incr_minute(rdt, 1, 0, RadioDateTimeUtils::id_hour);
2276        assert_eq!(rdt2.minute, Some(0));
2277        assert_eq!(rdt2.hour, Some(0));
2278        assert_eq!(rdt2.weekday, Some(5));
2279        assert_eq!(rdt2.day, Some(29));
2280        assert_eq!(rdt2.month, Some(2));
2281        assert_eq!(rdt2.year, Some(24));
2282    }
2283
2284    #[test]
2285    fn test_incr_minute_leap_2000() {
2286        let mut rdt = RadioDateTimeUtils::new(7);
2287        rdt.year = Some(0);
2288        rdt.month = Some(2);
2289        rdt.day = Some(28);
2290        rdt.weekday = Some(1);
2291        rdt.hour = Some(23);
2292        rdt.minute = Some(59);
2293        rdt.dst = Some(0); // prevent unwrapping a None
2294        let rdt2 = RadioDateTimeUtils::incr_minute(rdt, 1, 0, RadioDateTimeUtils::id_hour);
2295        assert_eq!(rdt2.minute, Some(0));
2296        assert_eq!(rdt2.hour, Some(0));
2297        assert_eq!(rdt2.weekday, Some(2));
2298        assert_eq!(rdt2.day, Some(29));
2299        assert_eq!(rdt2.month, Some(2));
2300        assert_eq!(rdt2.year, Some(0));
2301    }
2302
2303    #[test]
2304    fn test_incr_minute_non_leap_2100() {
2305        let mut rdt = RadioDateTimeUtils::new(7);
2306        rdt.year = Some(0);
2307        rdt.month = Some(2);
2308        rdt.day = Some(28);
2309        rdt.weekday = Some(7);
2310        rdt.hour = Some(23);
2311        rdt.minute = Some(59);
2312        rdt.dst = Some(0); // prevent unwrapping a None
2313        let rdt2 = RadioDateTimeUtils::incr_minute(rdt, 1, 0, RadioDateTimeUtils::id_hour);
2314        assert_eq!(rdt2.minute, Some(0));
2315        assert_eq!(rdt2.hour, Some(0));
2316        assert_eq!(rdt2.weekday, Some(1));
2317        assert_eq!(rdt2.day, Some(1));
2318        assert_eq!(rdt2.month, Some(3));
2319        assert_eq!(rdt2.year, Some(0));
2320    }
2321
2322    #[test]
2323    fn test_decr_minute_century() {
2324        let mut rdt = RadioDateTimeUtils::new(7);
2325        rdt.year = Some(0);
2326        rdt.month = Some(1);
2327        rdt.day = Some(1);
2328        rdt.weekday = Some(6);
2329        rdt.hour = Some(0);
2330        rdt.minute = Some(0);
2331        rdt.dst = Some(0); // prevent unwrapping a None
2332        let rdt2 = RadioDateTimeUtils::decr_minute(rdt, 1, 0);
2333        assert_eq!(rdt2.minute, Some(59));
2334        assert_eq!(rdt2.hour, Some(23));
2335        assert_eq!(rdt2.weekday, Some(5));
2336        assert_eq!(rdt2.day, Some(31));
2337        assert_eq!(rdt2.month, Some(12));
2338        assert_eq!(rdt2.year, Some(99));
2339    }
2340
2341    #[test]
2342    fn test_decr_minute_leap_regular() {
2343        let mut rdt = RadioDateTimeUtils::new(7);
2344        rdt.year = Some(24);
2345        rdt.month = Some(3);
2346        rdt.day = Some(1);
2347        rdt.weekday = Some(5);
2348        rdt.hour = Some(0);
2349        rdt.minute = Some(0);
2350        rdt.dst = Some(0); // prevent unwrapping a None
2351        let rdt2 = RadioDateTimeUtils::decr_minute(rdt, 1, 0);
2352        assert_eq!(rdt2.minute, Some(59));
2353        assert_eq!(rdt2.hour, Some(23));
2354        assert_eq!(rdt2.weekday, Some(4));
2355        assert_eq!(rdt2.day, Some(29));
2356        assert_eq!(rdt2.month, Some(2));
2357        assert_eq!(rdt2.year, Some(24));
2358    }
2359
2360    #[test]
2361    fn test_decr_minute_leap_2000() {
2362        let mut rdt = RadioDateTimeUtils::new(7);
2363        rdt.year = Some(0);
2364        rdt.month = Some(3);
2365        rdt.day = Some(1);
2366        rdt.weekday = Some(3);
2367        rdt.hour = Some(0);
2368        rdt.minute = Some(0);
2369        rdt.dst = Some(0); // prevent unwrapping a None
2370        let rdt2 = RadioDateTimeUtils::decr_minute(rdt, 1, 0);
2371        assert_eq!(rdt2.minute, Some(59));
2372        assert_eq!(rdt2.hour, Some(23));
2373        assert_eq!(rdt2.weekday, Some(2));
2374        assert_eq!(rdt2.day, Some(29));
2375        assert_eq!(rdt2.month, Some(2));
2376        assert_eq!(rdt2.year, Some(0));
2377    }
2378
2379    #[test]
2380    fn test_decr_minute_non_leap_2100() {
2381        let mut rdt = RadioDateTimeUtils::new(7);
2382        rdt.year = Some(0);
2383        rdt.month = Some(3);
2384        rdt.day = Some(1);
2385        rdt.weekday = Some(1);
2386        rdt.hour = Some(0);
2387        rdt.minute = Some(0);
2388        rdt.dst = Some(0); // prevent unwrapping a None
2389        let rdt2 = RadioDateTimeUtils::decr_minute(rdt, 1, 0);
2390        assert_eq!(rdt2.minute, Some(59));
2391        assert_eq!(rdt2.hour, Some(23));
2392        assert_eq!(rdt2.weekday, Some(7));
2393        assert_eq!(rdt2.day, Some(28));
2394        assert_eq!(rdt2.month, Some(2));
2395        assert_eq!(rdt2.year, Some(0));
2396    }
2397
2398    #[test]
2399    fn test_decr_minute_multiple() {
2400        let mut rdt = RadioDateTimeUtils::new(7);
2401        rdt.year = Some(24);
2402        rdt.month = Some(12);
2403        rdt.day = Some(4);
2404        rdt.weekday = Some(3);
2405        rdt.hour = Some(15);
2406        rdt.minute = Some(30);
2407        rdt.dst = Some(0); // prevent unwrapping a None
2408        let rdt2 = RadioDateTimeUtils::decr_minute(rdt, 45, 5);
2409        assert_eq!(rdt2.minute, Some(45));
2410        assert_eq!(rdt2.hour, Some(9));
2411        assert_eq!(rdt2.weekday, Some(3));
2412        assert_eq!(rdt2.day, Some(4));
2413        assert_eq!(rdt2.month, Some(12));
2414        assert_eq!(rdt2.year, Some(24));
2415    }
2416
2417    #[test]
2418    fn test_decr_minute_multiple_large_minute() {
2419        let mut rdt = RadioDateTimeUtils::new(7);
2420        rdt.year = Some(24);
2421        rdt.month = Some(12);
2422        rdt.day = Some(4);
2423        rdt.weekday = Some(3);
2424        rdt.hour = Some(15);
2425        rdt.minute = Some(50);
2426        rdt.dst = Some(0); // prevent unwrapping a None
2427        let rdt2 = RadioDateTimeUtils::decr_minute(rdt, 45, 5);
2428        assert_eq!(rdt2.minute, Some(5));
2429        assert_eq!(rdt2.hour, Some(10));
2430        assert_eq!(rdt2.weekday, Some(3));
2431        assert_eq!(rdt2.day, Some(4));
2432        assert_eq!(rdt2.month, Some(12));
2433        assert_eq!(rdt2.year, Some(24));
2434    }
2435
2436    #[test]
2437    fn test_incr_minute_multiple_overflow() {
2438        let mut rdt = RadioDateTimeUtils::new(7);
2439        rdt.year = Some(24);
2440        rdt.month = Some(12);
2441        rdt.day = Some(3);
2442        rdt.weekday = Some(2);
2443        rdt.hour = Some(23);
2444        rdt.minute = Some(45);
2445        rdt.dst = Some(0); // prevent unwrapping a None
2446        let rdt2 = RadioDateTimeUtils::incr_minute(rdt, 45, 5, RadioDateTimeUtils::id_hour);
2447        assert_eq!(rdt2.minute, Some(30));
2448        assert_eq!(rdt2.hour, Some(5));
2449        assert_eq!(rdt2.weekday, Some(3));
2450        assert_eq!(rdt2.day, Some(4));
2451        assert_eq!(rdt2.month, Some(12));
2452        assert_eq!(rdt2.year, Some(24));
2453    }
2454
2455    #[test]
2456    fn test_decr_minute_multiple_underflow() {
2457        let mut rdt = RadioDateTimeUtils::new(7);
2458        rdt.year = Some(24);
2459        rdt.month = Some(12);
2460        rdt.day = Some(4);
2461        rdt.weekday = Some(3);
2462        rdt.hour = Some(5);
2463        rdt.minute = Some(30);
2464        rdt.dst = Some(0); // prevent unwrapping a None
2465        let rdt2 = RadioDateTimeUtils::decr_minute(rdt, 45, 5);
2466        assert_eq!(rdt2.minute, Some(45));
2467        assert_eq!(rdt2.hour, Some(23));
2468        assert_eq!(rdt2.weekday, Some(2));
2469        assert_eq!(rdt2.day, Some(3));
2470        assert_eq!(rdt2.month, Some(12));
2471        assert_eq!(rdt2.year, Some(24));
2472    }
2473
2474    #[test]
2475    fn test_to_utc_at_winter() {
2476        let mut rdt = RadioDateTimeUtils::new(0);
2477        rdt.set_utc_offset(0, false);
2478        rdt.year = Some(24);
2479        rdt.month = Some(12);
2480        rdt.day = Some(7);
2481        rdt.weekday = Some(6);
2482        rdt.hour = Some(23);
2483        rdt.minute = Some(36);
2484        rdt.dst = Some(0); // normal time
2485        let rdt2 = rdt.get_utc().unwrap();
2486        assert_eq!(rdt2.minute, Some(36));
2487        assert_eq!(rdt2.hour, Some(23));
2488        assert_eq!(rdt2.weekday, Some(6));
2489        assert_eq!(rdt2.day, Some(7));
2490        assert_eq!(rdt2.month, Some(12));
2491        assert_eq!(rdt2.year, Some(24));
2492        assert_eq!(rdt2.is_readonly, true);
2493        // is_utc indicates the UTC-ness of the transmitter, not of the result
2494        assert_eq!(rdt2.is_utc, Some(false));
2495    }
2496
2497    #[test]
2498    fn test_to_utc_at_summer() {
2499        let mut rdt = RadioDateTimeUtils::new(0);
2500        rdt.set_utc_offset(0, false);
2501        rdt.year = Some(24);
2502        rdt.month = Some(12);
2503        rdt.day = Some(7);
2504        rdt.weekday = Some(6);
2505        rdt.hour = Some(23);
2506        rdt.minute = Some(36);
2507        rdt.dst = Some(DST_SUMMER);
2508        let rdt2 = rdt.get_utc().unwrap();
2509        assert_eq!(rdt2.minute, Some(36));
2510        assert_eq!(rdt2.hour, Some(22));
2511        assert_eq!(rdt2.weekday, Some(6));
2512        assert_eq!(rdt2.day, Some(7));
2513        assert_eq!(rdt2.month, Some(12));
2514        assert_eq!(rdt2.year, Some(24));
2515        assert_eq!(rdt2.is_readonly, true);
2516        // is_utc indicates the UTC-ness of the transmitter, not of the result
2517        assert_eq!(rdt2.is_utc, Some(false));
2518    }
2519
2520    #[test]
2521    fn test_to_local_at_winter() {
2522        let mut rdt = RadioDateTimeUtils::new(0);
2523        rdt.set_utc_offset(0, true);
2524        rdt.year = Some(24);
2525        rdt.month = Some(12);
2526        rdt.day = Some(7);
2527        rdt.weekday = Some(6);
2528        rdt.hour = Some(23);
2529        rdt.minute = Some(36);
2530        rdt.dst = Some(0); // normal time
2531        let rdt2 = rdt.get_local_time().unwrap();
2532        assert_eq!(rdt2.minute, Some(36));
2533        assert_eq!(rdt2.hour, Some(23));
2534        assert_eq!(rdt2.weekday, Some(6));
2535        assert_eq!(rdt2.day, Some(7));
2536        assert_eq!(rdt2.month, Some(12));
2537        assert_eq!(rdt2.year, Some(24));
2538        assert_eq!(rdt2.is_readonly, true);
2539        // is_utc indicates the UTC-ness of the transmitter, not of the result
2540        assert_eq!(rdt2.is_utc, Some(true));
2541    }
2542
2543    #[test]
2544    fn test_to_local_at_summer() {
2545        let mut rdt = RadioDateTimeUtils::new(0);
2546        rdt.set_utc_offset(0, true);
2547        rdt.year = Some(24);
2548        rdt.month = Some(12);
2549        rdt.day = Some(7);
2550        rdt.weekday = Some(6);
2551        rdt.hour = Some(23);
2552        rdt.minute = Some(36);
2553        rdt.dst = Some(DST_SUMMER);
2554        let rdt2 = rdt.get_local_time().unwrap();
2555        assert_eq!(rdt2.minute, Some(36));
2556        assert_eq!(rdt2.hour, Some(0));
2557        assert_eq!(rdt2.weekday, Some(0));
2558        assert_eq!(rdt2.day, Some(8));
2559        assert_eq!(rdt2.month, Some(12));
2560        assert_eq!(rdt2.year, Some(24));
2561        assert_eq!(rdt2.is_readonly, true);
2562        // is_utc indicates the UTC-ness of the transmitter, not of the result
2563        assert_eq!(rdt2.is_utc, Some(true));
2564    }
2565
2566    // fictional time zones of -30m and +30m for UTC<->LT tests
2567
2568    #[test]
2569    fn test_to_utc_east_winter() {
2570        let mut rdt = RadioDateTimeUtils::new(0);
2571        rdt.set_utc_offset(30, false);
2572        rdt.year = Some(24);
2573        rdt.month = Some(12);
2574        rdt.day = Some(7);
2575        rdt.weekday = Some(6);
2576        rdt.hour = Some(23);
2577        rdt.minute = Some(36);
2578        rdt.dst = Some(0); // normal time
2579        let rdt2 = rdt.get_utc().unwrap();
2580        assert_eq!(rdt2.minute, Some(6));
2581        assert_eq!(rdt2.hour, Some(23));
2582        assert_eq!(rdt2.weekday, Some(6));
2583        assert_eq!(rdt2.day, Some(7));
2584        assert_eq!(rdt2.month, Some(12));
2585        assert_eq!(rdt2.year, Some(24));
2586        assert_eq!(rdt2.is_readonly, true);
2587        // is_utc indicates the UTC-ness of the transmitter, not of the result
2588        assert_eq!(rdt2.is_utc, Some(false));
2589    }
2590
2591    #[test]
2592    fn test_to_utc_east_summer() {
2593        let mut rdt = RadioDateTimeUtils::new(0);
2594        rdt.set_utc_offset(30, false);
2595        rdt.year = Some(24);
2596        rdt.month = Some(12);
2597        rdt.day = Some(7);
2598        rdt.weekday = Some(6);
2599        rdt.hour = Some(23);
2600        rdt.minute = Some(36);
2601        rdt.dst = Some(DST_SUMMER);
2602        let rdt2 = rdt.get_utc().unwrap();
2603        assert_eq!(rdt2.minute, Some(6));
2604        assert_eq!(rdt2.hour, Some(22));
2605        assert_eq!(rdt2.weekday, Some(6));
2606        assert_eq!(rdt2.day, Some(7));
2607        assert_eq!(rdt2.month, Some(12));
2608        assert_eq!(rdt2.year, Some(24));
2609        assert_eq!(rdt2.is_readonly, true);
2610        // is_utc indicates the UTC-ness of the transmitter, not of the result
2611        assert_eq!(rdt2.is_utc, Some(false));
2612    }
2613
2614    #[test]
2615    fn test_to_local_east_winter() {
2616        let mut rdt = RadioDateTimeUtils::new(0);
2617        rdt.set_utc_offset(30, true);
2618        rdt.year = Some(24);
2619        rdt.month = Some(12);
2620        rdt.day = Some(7);
2621        rdt.weekday = Some(6);
2622        rdt.hour = Some(23);
2623        rdt.minute = Some(36);
2624        rdt.dst = Some(0); // normal time
2625        let rdt2 = rdt.get_local_time().unwrap();
2626        assert_eq!(rdt2.minute, Some(6));
2627        assert_eq!(rdt2.hour, Some(0));
2628        assert_eq!(rdt2.weekday, Some(0));
2629        assert_eq!(rdt2.day, Some(8));
2630        assert_eq!(rdt2.month, Some(12));
2631        assert_eq!(rdt2.year, Some(24));
2632        assert_eq!(rdt2.is_readonly, true);
2633        // is_utc indicates the UTC-ness of the transmitter, not of the result
2634        assert_eq!(rdt2.is_utc, Some(true));
2635    }
2636
2637    #[test]
2638    fn test_to_local_east_summer() {
2639        let mut rdt = RadioDateTimeUtils::new(0);
2640        rdt.set_utc_offset(30, true);
2641        rdt.year = Some(24);
2642        rdt.month = Some(12);
2643        rdt.day = Some(7);
2644        rdt.weekday = Some(6);
2645        rdt.hour = Some(23);
2646        rdt.minute = Some(36);
2647        rdt.dst = Some(DST_SUMMER);
2648        let rdt2 = rdt.get_local_time().unwrap();
2649        assert_eq!(rdt2.minute, Some(6));
2650        assert_eq!(rdt2.hour, Some(1));
2651        assert_eq!(rdt2.weekday, Some(0));
2652        assert_eq!(rdt2.day, Some(8));
2653        assert_eq!(rdt2.month, Some(12));
2654        assert_eq!(rdt2.year, Some(24));
2655        assert_eq!(rdt2.is_readonly, true);
2656        // is_utc indicates the UTC-ness of the transmitter, not of the result
2657        assert_eq!(rdt2.is_utc, Some(true));
2658    }
2659
2660    #[test]
2661    fn test_to_utc_west_winter() {
2662        let mut rdt = RadioDateTimeUtils::new(0);
2663        rdt.set_utc_offset(-30, false);
2664        rdt.year = Some(24);
2665        rdt.month = Some(12);
2666        rdt.day = Some(7);
2667        rdt.weekday = Some(6);
2668        rdt.hour = Some(21);
2669        rdt.minute = Some(36);
2670        rdt.dst = Some(0); // normal time
2671        let rdt2 = rdt.get_utc().unwrap();
2672        assert_eq!(rdt2.minute, Some(6));
2673        assert_eq!(rdt2.hour, Some(22));
2674        assert_eq!(rdt2.weekday, Some(6));
2675        assert_eq!(rdt2.day, Some(7));
2676        assert_eq!(rdt2.month, Some(12));
2677        assert_eq!(rdt2.year, Some(24));
2678        assert_eq!(rdt2.is_readonly, true);
2679        // is_utc indicates the UTC-ness of the transmitter, not of the result
2680        assert_eq!(rdt2.is_utc, Some(false));
2681    }
2682
2683    #[test]
2684    fn test_to_utc_west_summer() {
2685        let mut rdt = RadioDateTimeUtils::new(0);
2686        rdt.set_utc_offset(-30, false);
2687        rdt.year = Some(24);
2688        rdt.month = Some(12);
2689        rdt.day = Some(7);
2690        rdt.weekday = Some(6);
2691        rdt.hour = Some(21);
2692        rdt.minute = Some(36);
2693        rdt.dst = Some(DST_SUMMER);
2694        let rdt2 = rdt.get_utc().unwrap();
2695        assert_eq!(rdt2.minute, Some(6));
2696        assert_eq!(rdt2.hour, Some(21)); // flips from increasing time to decreasing time
2697        assert_eq!(rdt2.weekday, Some(6));
2698        assert_eq!(rdt2.day, Some(7));
2699        assert_eq!(rdt2.month, Some(12));
2700        assert_eq!(rdt2.year, Some(24));
2701        assert_eq!(rdt2.is_readonly, true);
2702        // is_utc indicates the UTC-ness of the transmitter, not of the result
2703        assert_eq!(rdt2.is_utc, Some(false));
2704    }
2705
2706    #[test]
2707    fn test_to_local_west_winter() {
2708        let mut rdt = RadioDateTimeUtils::new(0);
2709        rdt.set_utc_offset(-30, true);
2710        rdt.year = Some(24);
2711        rdt.month = Some(12);
2712        rdt.day = Some(7);
2713        rdt.weekday = Some(6);
2714        rdt.hour = Some(21);
2715        rdt.minute = Some(36);
2716        rdt.dst = Some(0); // normal time
2717        let rdt2 = rdt.get_local_time().unwrap();
2718        assert_eq!(rdt2.minute, Some(6));
2719        assert_eq!(rdt2.hour, Some(21));
2720        assert_eq!(rdt2.weekday, Some(6));
2721        assert_eq!(rdt2.day, Some(7));
2722        assert_eq!(rdt2.month, Some(12));
2723        assert_eq!(rdt2.year, Some(24));
2724        assert_eq!(rdt2.is_readonly, true);
2725        // is_utc indicates the UTC-ness of the transmitter, not of the result
2726        assert_eq!(rdt2.is_utc, Some(true));
2727    }
2728
2729    #[test]
2730    fn test_to_local_west_summer() {
2731        let mut rdt = RadioDateTimeUtils::new(0);
2732        rdt.set_utc_offset(-30, true);
2733        rdt.year = Some(24);
2734        rdt.month = Some(12);
2735        rdt.day = Some(7);
2736        rdt.weekday = Some(6);
2737        rdt.hour = Some(21);
2738        rdt.minute = Some(36);
2739        rdt.dst = Some(DST_SUMMER);
2740        let rdt2 = rdt.get_local_time().unwrap();
2741        assert_eq!(rdt2.minute, Some(6));
2742        assert_eq!(rdt2.hour, Some(22)); // flips from decreasing time to increasing time
2743        assert_eq!(rdt2.weekday, Some(6));
2744        assert_eq!(rdt2.day, Some(7));
2745        assert_eq!(rdt2.month, Some(12));
2746        assert_eq!(rdt2.year, Some(24));
2747        assert_eq!(rdt2.is_readonly, true);
2748        // is_utc indicates the UTC-ness of the transmitter, not of the result
2749        assert_eq!(rdt2.is_utc, Some(true));
2750    }
2751
2752    #[test]
2753    fn test_to_utc_incomplete() {
2754        let mut rdt = RadioDateTimeUtils::new(0);
2755        rdt.set_utc_offset(0, false);
2756        assert_eq!(rdt.get_utc(), None);
2757    }
2758
2759    #[test]
2760    fn test_to_local_incomplete() {
2761        let mut rdt = RadioDateTimeUtils::new(0);
2762        rdt.set_utc_offset(0, true);
2763        assert_eq!(rdt.get_local_time(), None);
2764    }
2765
2766    #[test]
2767    fn test_utc_set_dst() {
2768        let mut rdt = RadioDateTimeUtils::new(0);
2769        rdt.set_utc_offset(0, true);
2770        assert_eq!(rdt.dst_count, 0);
2771        assert_eq!(rdt.dst, None);
2772        // sets DST flag to summer time in UTC mode, but does not affect date/time itself:
2773        let mut rdt_new = rdt;
2774        rdt_new.set_dst(Some(true), Some(true), false);
2775        // adjust original value for wholesale comparison:
2776        rdt.dst = Some(DST_SUMMER);
2777        // we did one cycle:
2778        rdt.dst_count = 1;
2779        rdt.first_minute = false;
2780        assert_eq!(rdt_new, rdt);
2781    }
2782
2783    #[test]
2784    fn test_readonly_year() {
2785        let mut rdt = RadioDateTimeUtils::new(7);
2786        rdt.is_readonly = true;
2787        rdt.set_year(Some(24), true, false);
2788        assert_eq!(rdt.is_readonly, true);
2789        assert_eq!(rdt.year, None);
2790    }
2791
2792    #[test]
2793    fn test_readonly_month() {
2794        let mut rdt = RadioDateTimeUtils::new(7);
2795        rdt.is_readonly = true;
2796        rdt.set_month(Some(12), true, false);
2797        assert_eq!(rdt.is_readonly, true);
2798        assert_eq!(rdt.month, None);
2799    }
2800
2801    #[test]
2802    fn test_readonly_weekday() {
2803        let mut rdt = RadioDateTimeUtils::new(7);
2804        rdt.is_readonly = true;
2805        rdt.set_weekday(Some(7), true, false);
2806        assert_eq!(rdt.is_readonly, true);
2807        assert_eq!(rdt.weekday, None);
2808    }
2809
2810    #[test]
2811    fn test_readonly_day() {
2812        let mut rdt = RadioDateTimeUtils::new(7);
2813        rdt.is_readonly = true;
2814        rdt.set_year(Some(24), true, false);
2815        rdt.set_month(Some(12), true, false);
2816        rdt.set_weekday(Some(7), true, false);
2817        rdt.set_day(Some(15), true, false);
2818        assert_eq!(rdt.is_readonly, true);
2819        assert_eq!(rdt.day, None);
2820    }
2821
2822    #[test]
2823    fn test_readonly_hour() {
2824        let mut rdt = RadioDateTimeUtils::new(7);
2825        rdt.is_readonly = true;
2826        rdt.set_hour(Some(22), true, false);
2827        assert_eq!(rdt.is_readonly, true);
2828        assert_eq!(rdt.hour, None);
2829    }
2830
2831    #[test]
2832    fn test_readonly_minute() {
2833        let mut rdt = RadioDateTimeUtils::new(7);
2834        rdt.is_readonly = true;
2835        rdt.set_minute(Some(50), true, false);
2836        assert_eq!(rdt.is_readonly, true);
2837        assert_eq!(rdt.minute, None);
2838    }
2839
2840    #[test]
2841    fn test_readonly_to_utc_at_summer() {
2842        let mut rdt = RadioDateTimeUtils::new(0);
2843        rdt.set_utc_offset(0, false);
2844        rdt.is_readonly = true;
2845        rdt.year = Some(24);
2846        rdt.month = Some(12);
2847        rdt.day = Some(7);
2848        rdt.weekday = Some(6);
2849        rdt.hour = Some(23);
2850        rdt.minute = Some(36);
2851        rdt.dst = Some(DST_SUMMER);
2852        let rdt2 = rdt.get_utc().unwrap();
2853        // input read-only, so output should be unchanged here:
2854        assert_eq!(rdt2.minute, Some(36));
2855        assert_eq!(rdt2.hour, Some(23));
2856        assert_eq!(rdt2.weekday, Some(6));
2857        assert_eq!(rdt2.day, Some(7));
2858        assert_eq!(rdt2.month, Some(12));
2859        assert_eq!(rdt2.year, Some(24));
2860        assert_eq!(rdt2.is_readonly, true);
2861        // is_utc indicates the UTC-ness of the transmitter, not of the result
2862        assert_eq!(rdt2.is_utc, Some(false));
2863    }
2864
2865    #[test]
2866    fn test_readonly_to_local_at_summer() {
2867        let mut rdt = RadioDateTimeUtils::new(0);
2868        rdt.set_utc_offset(0, true);
2869        rdt.is_readonly = true;
2870        rdt.year = Some(24);
2871        rdt.month = Some(12);
2872        rdt.day = Some(7);
2873        rdt.weekday = Some(6);
2874        rdt.hour = Some(23);
2875        rdt.minute = Some(36);
2876        rdt.dst = Some(DST_SUMMER);
2877        let rdt2 = rdt.get_local_time().unwrap();
2878        // input read-only, so output should be unchanged here:
2879        assert_eq!(rdt2.minute, Some(36));
2880        assert_eq!(rdt2.hour, Some(23));
2881        assert_eq!(rdt2.weekday, Some(6));
2882        assert_eq!(rdt2.day, Some(7));
2883        assert_eq!(rdt2.month, Some(12));
2884        assert_eq!(rdt2.year, Some(24));
2885        assert_eq!(rdt2.is_readonly, true);
2886        // is_utc indicates the UTC-ness of the transmitter, not of the result
2887        assert_eq!(rdt2.is_utc, Some(true));
2888    }
2889
2890    #[test]
2891    fn test_readonly_clear_jumps() {
2892        let mut rdt = RadioDateTimeUtils::new(0);
2893        rdt.is_readonly = true;
2894        rdt.jump_year = true;
2895        rdt.jump_month = true;
2896        rdt.jump_weekday = true;
2897        rdt.jump_day = true;
2898        rdt.jump_hour = true;
2899        rdt.jump_minute = true;
2900        rdt.dst = Some(DST_JUMP);
2901        rdt.clear_jumps();
2902        assert_eq!(rdt.is_readonly, true);
2903        assert_eq!(rdt.jump_year, true);
2904        assert_eq!(rdt.jump_month, true);
2905        assert_eq!(rdt.jump_weekday, true);
2906        assert_eq!(rdt.jump_day, true);
2907        assert_eq!(rdt.jump_hour, true);
2908        assert_eq!(rdt.jump_minute, true);
2909        assert_eq!(rdt.dst, Some(DST_JUMP));
2910    }
2911
2912    #[test]
2913    fn test_readonly_add_minute() {
2914        let mut rdt = RadioDateTimeUtils::new(0);
2915        rdt.is_readonly = true;
2916        assert_eq!(rdt.add_minute(), false);
2917    }
2918
2919    #[test]
2920    fn test_readonly_bump_minutes_running() {
2921        let mut rdt = RadioDateTimeUtils::new(0);
2922        rdt.is_readonly = true;
2923        rdt.bump_minutes_running();
2924        assert_eq!(rdt.minutes_running, 0);
2925    }
2926
2927    #[test]
2928    fn test_readonly_set_month_day() {
2929        let mut rdt = RadioDateTimeUtils::new(0);
2930        rdt.is_readonly = true;
2931        rdt.set_month_day(Some(60), Some(true), true, true, false);
2932        assert_eq!(rdt.is_readonly, true);
2933        assert_eq!(rdt.month, None);
2934        assert_eq!(rdt.day, None);
2935    }
2936
2937    #[test]
2938    fn test_readonly_set_dst() {
2939        let mut rdt = RadioDateTimeUtils::new(0);
2940        rdt.is_readonly = true;
2941        assert_eq!(rdt.dst_count, 0);
2942        assert_eq!(rdt.dst, None);
2943        // should do nothing
2944        let mut rdt_new = rdt;
2945        rdt_new.set_dst(Some(true), Some(true), false);
2946        assert_eq!(rdt_new, rdt);
2947        assert_eq!(rdt_new.first_minute, true);
2948    }
2949
2950    #[test]
2951    fn test_readonly_set_leap_second() {
2952        let mut rdt = RadioDateTimeUtils::new(0);
2953        rdt.is_readonly = true;
2954        assert_eq!(rdt.leap_second_count, 0);
2955        assert_eq!(rdt.leap_second, None);
2956        // should do nothing
2957        let mut rdt_new = rdt;
2958        rdt_new.set_leap_second(Some(true), 60);
2959        assert_eq!(rdt_new, rdt);
2960    }
2961}