rtc_hal/
datetime.rs

1//! # DateTime Module
2//!
3//! This module defines a `DateTime` struct and helper functions for representing,
4//! validating, and working with calendar date and time values in embedded systems.
5//!
6//! ## Features
7//! - Stores year, month, day, hour, minute, second
8//! - Built-in validation for all fields (including leap years and month lengths)
9//! - Setter and getter methods that enforce validity
10//! - Utility functions for leap year detection, days in a month, and weekday calculation
11//!
12//! ## Year Range
13//! The default supported range is **year >= 1970**, which covers the widest set of
14//! popular RTC chips. For example:
15//!
16//! - DS1307, DS3231: 2000-2099
17//!
18//! Drivers are responsible for checking and enforcing the *exact* year range of the
19//! underlying hardware. The `DateTime` type itself only enforces the lower bound (1970)
20//! to remain reusable in contexts outside RTCs.
21//!
22//! ## Weekday Format
23//! - This module uses **1=Sunday to 7=Saturday**
24//! - Drivers must handle conversion if required
25
26/// Errors that can occur when working with DateTime
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28#[cfg_attr(feature = "defmt", derive(defmt::Format))]
29pub enum DateTimeError {
30    /// Invalid month value
31    InvalidMonth,
32    /// Invalid day value
33    InvalidDay,
34    /// Invalid hour value
35    InvalidHour,
36    /// Invalid minute value
37    InvalidMinute,
38    /// Invalid second value
39    InvalidSecond,
40    /// Invalid weekday value
41    InvalidWeekday,
42    /// Invalid Year value
43    InvalidYear,
44}
45
46impl core::fmt::Display for DateTimeError {
47    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
48        match self {
49            DateTimeError::InvalidMonth => write!(f, "invalid month"),
50            DateTimeError::InvalidDay => write!(f, "invalid day"),
51            DateTimeError::InvalidHour => write!(f, "invalid hour"),
52            DateTimeError::InvalidMinute => write!(f, "invalid minute"),
53            DateTimeError::InvalidSecond => write!(f, "invalid second"),
54            DateTimeError::InvalidWeekday => write!(f, "invalid weekday"),
55            DateTimeError::InvalidYear => write!(f, "invalid year"),
56        }
57    }
58}
59
60impl core::error::Error for DateTimeError {}
61
62/// Date and time representation used across RTC drivers.
63///
64/// This type represents calendar date and time in a general-purpose way,
65/// independent of any specific RTC hardware.
66///
67/// - Validates that `year >= 1970`
68/// - Other limits (e.g., 2000-2099) must be enforced by individual drivers
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub struct DateTime {
71    /// Year (full year, e.g., 2024)
72    year: u16,
73    /// Month (1-12)
74    month: u8,
75    /// Day of the month (1-31 depending on month/year)
76    day_of_month: u8,
77    /// Hour (0-23)
78    hour: u8,
79    /// Minute (0-59)
80    minute: u8,
81    /// Second (0-59)
82    second: u8,
83}
84
85impl DateTime {
86    /// Create a new `DateTime` instance with validation.
87    ///
88    /// # Errors
89    ///
90    /// Returns a `DateTimeError` if any component is out of valid range.
91    pub fn new(
92        year: u16,
93        month: u8,
94        day_of_month: u8,
95        hour: u8,
96        minute: u8,
97        second: u8,
98    ) -> Result<Self, DateTimeError> {
99        let dt = DateTime {
100            year,
101            month,
102            day_of_month,
103            hour,
104            minute,
105            second,
106        };
107        dt.validate()?;
108        Ok(dt)
109    }
110
111    /// Validate all datetime components.
112    ///
113    /// # Errors
114    ///
115    /// Returns the first `DateTimeError` encountered.
116    pub fn validate(&self) -> Result<(), DateTimeError> {
117        Self::validate_year(self.year)?;
118        Self::validate_month(self.month)?;
119        Self::validate_day(self.year, self.month, self.day_of_month)?;
120        Self::validate_hour(self.hour)?;
121        Self::validate_minute(self.minute)?;
122        Self::validate_second(self.second)?;
123        Ok(())
124    }
125
126    /// Validate the year (must be >= 1970).
127    fn validate_year(year: u16) -> Result<(), DateTimeError> {
128        if year < 1970 {
129            return Err(DateTimeError::InvalidYear);
130        }
131        Ok(())
132    }
133
134    /// Validate the month (must be 1-12).
135    fn validate_month(month: u8) -> Result<(), DateTimeError> {
136        if month == 0 || month > 12 {
137            return Err(DateTimeError::InvalidMonth);
138        }
139        Ok(())
140    }
141
142    /// Validate the day (must be within the valid range for the month/year).
143    fn validate_day(year: u16, month: u8, day: u8) -> Result<(), DateTimeError> {
144        let max_day = days_in_month(year, month);
145        if day == 0 || day > max_day {
146            return Err(DateTimeError::InvalidDay);
147        }
148        Ok(())
149    }
150
151    /// Validate the hour (must be 0-23).
152    fn validate_hour(hour: u8) -> Result<(), DateTimeError> {
153        if hour > 23 {
154            return Err(DateTimeError::InvalidHour);
155        }
156        Ok(())
157    }
158
159    /// Validate the minute (must be 0-59).
160    fn validate_minute(minute: u8) -> Result<(), DateTimeError> {
161        if minute > 59 {
162            return Err(DateTimeError::InvalidMinute);
163        }
164        Ok(())
165    }
166
167    /// Validate the second (must be 0-59).
168    fn validate_second(second: u8) -> Result<(), DateTimeError> {
169        if second > 59 {
170            return Err(DateTimeError::InvalidSecond);
171        }
172        Ok(())
173    }
174
175    /// Get the year (e.g. 2025).
176    pub fn year(&self) -> u16 {
177        self.year
178    }
179
180    /// Get the month number (1-12).
181    pub fn month(&self) -> u8 {
182        self.month
183    }
184
185    /// Get the day of the month (1-31).
186    pub fn day_of_month(&self) -> u8 {
187        self.day_of_month
188    }
189
190    /// Get the hour (0-23).
191    pub fn hour(&self) -> u8 {
192        self.hour
193    }
194
195    /// Get the minute (0-59).
196    pub fn minute(&self) -> u8 {
197        self.minute
198    }
199
200    /// Get the second (0-59).
201    pub fn second(&self) -> u8 {
202        self.second
203    }
204
205    /// Set year with validation.
206    ///
207    /// Re-validates the day in case of leap-year or February issues.
208    pub fn set_year(&mut self, year: u16) -> Result<(), DateTimeError> {
209        Self::validate_year(year)?;
210        Self::validate_day(year, self.month, self.day_of_month)?;
211        self.year = year;
212        Ok(())
213    }
214
215    /// Set month with validation.
216    ///
217    /// Re-validates the day in case month/day mismatch occurs.
218    pub fn set_month(&mut self, month: u8) -> Result<(), DateTimeError> {
219        Self::validate_month(month)?;
220        Self::validate_day(self.year, month, self.day_of_month)?;
221        self.month = month;
222        Ok(())
223    }
224
225    /// Set day with validation.
226    pub fn set_day_of_month(&mut self, day_of_month: u8) -> Result<(), DateTimeError> {
227        Self::validate_day(self.year, self.month, day_of_month)?;
228        self.day_of_month = day_of_month;
229        Ok(())
230    }
231
232    /// Set hour with validation.
233    pub fn set_hour(&mut self, hour: u8) -> Result<(), DateTimeError> {
234        Self::validate_hour(hour)?;
235        self.hour = hour;
236        Ok(())
237    }
238
239    /// Set minute with validation.
240    pub fn set_minute(&mut self, minute: u8) -> Result<(), DateTimeError> {
241        Self::validate_minute(minute)?;
242        self.minute = minute;
243        Ok(())
244    }
245
246    /// Set second with validation.
247    pub fn set_second(&mut self, second: u8) -> Result<(), DateTimeError> {
248        Self::validate_second(second)?;
249        self.second = second;
250        Ok(())
251    }
252
253    /// Calculate weekday for this DateTime
254    pub fn calculate_weekday(&self) -> Result<Weekday, DateTimeError> {
255        calculate_weekday(self.year, self.month, self.day_of_month)
256    }
257}
258
259/// Day of the week (1 = Sunday .. 7 = Saturday)
260#[derive(Debug, Clone, Copy, PartialEq, Eq)]
261#[repr(u8)]
262pub enum Weekday {
263    /// Sunday starts with 1
264    Sunday = 1,
265    /// Monday
266    Monday = 2,
267    /// Tuesday
268    Tuesday = 3,
269    /// Wednesday
270    Wednesday = 4,
271    /// Thursday
272    Thursday = 5,
273    /// Friday
274    Friday = 6,
275    /// Saturday
276    Saturday = 7,
277}
278
279impl Weekday {
280    /// Create a Weekday from a raw u8 (1 = Sunday .. 7 = Saturday).
281    pub fn from_number(n: u8) -> Result<Self, DateTimeError> {
282        match n {
283            1 => Ok(Self::Sunday),
284            2 => Ok(Self::Monday),
285            3 => Ok(Self::Tuesday),
286            4 => Ok(Self::Wednesday),
287            5 => Ok(Self::Thursday),
288            6 => Ok(Self::Friday),
289            7 => Ok(Self::Saturday),
290            _ => Err(DateTimeError::InvalidWeekday),
291        }
292    }
293
294    /// Get the number form (1 = Sunday .. 7 = Saturday).
295    pub fn to_number(self) -> u8 {
296        self as u8
297    }
298
299    /// Get the weekday name as a string slice
300    pub fn as_str(&self) -> &'static str {
301        match self {
302            Weekday::Sunday => "Sunday",
303            Weekday::Monday => "Monday",
304            Weekday::Tuesday => "Tuesday",
305            Weekday::Wednesday => "Wednesday",
306            Weekday::Thursday => "Thursday",
307            Weekday::Friday => "Friday",
308            Weekday::Saturday => "Saturday",
309        }
310    }
311}
312
313/// Check if a year is a leap year
314pub fn is_leap_year(year: u16) -> bool {
315    (year % 4 == 0) && (year % 100 != 0 || year % 400 == 0)
316}
317
318/// Get the number of days in a month
319pub fn days_in_month(year: u16, month: u8) -> u8 {
320    match month {
321        1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
322        4 | 6 | 9 | 11 => 30,
323        2 => {
324            if is_leap_year(year) {
325                29
326            } else {
327                28
328            }
329        }
330        _ => 0,
331    }
332}
333
334/// Calculate the day of the week using Zeller's congruence algorithm
335/// Returns 1=Sunday, 2=Monday, ..., 7=Saturday
336pub fn calculate_weekday(year: u16, month: u8, day_of_month: u8) -> Result<Weekday, DateTimeError> {
337    let (year, month) = if month < 3 {
338        (year - 1, month + 12)
339    } else {
340        (year, month)
341    };
342
343    let k = year % 100;
344    let j = year / 100;
345
346    let h =
347        (day_of_month as u16 + ((13 * (month as u16 + 1)) / 5) + k + (k / 4) + (j / 4) - 2 * j) % 7;
348
349    // Convert Zeller's result (0=Saturday) to our format (1=Sunday)
350    let weekday_num = ((h + 6) % 7) + 1;
351
352    // This should never fail since we're calculating a valid weekday
353    Weekday::from_number(weekday_num as u8)
354}
355
356#[cfg(test)]
357mod tests {
358    use super::*;
359
360    #[test]
361    fn test_valid_datetime_creation() {
362        let dt = DateTime::new(2024, 3, 15, 14, 30, 45).unwrap();
363        assert_eq!(dt.year(), 2024);
364        assert_eq!(dt.month(), 3);
365        assert_eq!(dt.day_of_month(), 15);
366        assert_eq!(dt.hour(), 14);
367        assert_eq!(dt.minute(), 30);
368        assert_eq!(dt.second(), 45);
369    }
370
371    #[test]
372    fn test_invalid_year() {
373        let result = DateTime::new(1969, 1, 1, 0, 0, 0);
374        assert_eq!(result.unwrap_err(), DateTimeError::InvalidYear);
375    }
376
377    #[test]
378    fn test_invalid_month() {
379        assert_eq!(
380            DateTime::new(2024, 0, 1, 0, 0, 0).unwrap_err(),
381            DateTimeError::InvalidMonth
382        );
383        assert_eq!(
384            DateTime::new(2024, 13, 1, 0, 0, 0).unwrap_err(),
385            DateTimeError::InvalidMonth
386        );
387    }
388
389    #[test]
390    fn test_invalid_day() {
391        // Test February 30th (invalid)
392        assert_eq!(
393            DateTime::new(2024, 2, 30, 0, 0, 0).unwrap_err(),
394            DateTimeError::InvalidDay
395        );
396
397        // Test day 0
398        assert_eq!(
399            DateTime::new(2024, 1, 0, 0, 0, 0).unwrap_err(),
400            DateTimeError::InvalidDay
401        );
402
403        // Test April 31st (invalid - April has 30 days)
404        assert_eq!(
405            DateTime::new(2024, 4, 31, 0, 0, 0).unwrap_err(),
406            DateTimeError::InvalidDay
407        );
408    }
409
410    #[test]
411    fn test_invalid_hour() {
412        assert_eq!(
413            DateTime::new(2024, 1, 1, 24, 0, 0).unwrap_err(),
414            DateTimeError::InvalidHour
415        );
416    }
417
418    #[test]
419    fn test_invalid_minute() {
420        assert_eq!(
421            DateTime::new(2024, 1, 1, 0, 60, 0).unwrap_err(),
422            DateTimeError::InvalidMinute
423        );
424    }
425
426    #[test]
427    fn test_invalid_second() {
428        assert_eq!(
429            DateTime::new(2024, 1, 1, 0, 0, 60).unwrap_err(),
430            DateTimeError::InvalidSecond
431        );
432    }
433
434    #[test]
435    fn test_leap_year_february_29() {
436        // 2024 is a leap year - February 29th should be valid
437        assert!(DateTime::new(2024, 2, 29, 0, 0, 0).is_ok());
438
439        // 2023 is not a leap year - February 29th should be invalid
440        assert_eq!(
441            DateTime::new(2023, 2, 29, 0, 0, 0).unwrap_err(),
442            DateTimeError::InvalidDay
443        );
444    }
445
446    #[test]
447    fn test_setters_with_validation() {
448        let mut dt = DateTime::new(2024, 1, 1, 0, 0, 0).unwrap();
449
450        // Valid operations
451        assert!(dt.set_year(2025).is_ok());
452        assert_eq!(dt.year(), 2025);
453
454        assert!(dt.set_month(12).is_ok());
455        assert_eq!(dt.month(), 12);
456
457        assert!(dt.set_hour(23).is_ok());
458        assert_eq!(dt.hour(), 23);
459
460        // Invalid operations
461        assert_eq!(dt.set_year(1969), Err(DateTimeError::InvalidYear));
462        assert_eq!(dt.set_month(13), Err(DateTimeError::InvalidMonth));
463        assert_eq!(dt.set_hour(24), Err(DateTimeError::InvalidHour));
464    }
465
466    #[test]
467    fn test_leap_year_edge_cases_in_setters() {
468        let mut dt = DateTime::new(2024, 2, 29, 0, 0, 0).unwrap(); // Leap year
469
470        // Changing to non-leap year should fail because Feb 29 becomes invalid
471        assert_eq!(dt.set_year(2023), Err(DateTimeError::InvalidDay));
472
473        // Original value should remain unchanged after failed operation
474        assert_eq!(dt.year(), 2024);
475        assert_eq!(dt.day_of_month(), 29);
476    }
477
478    #[test]
479    fn test_month_day_validation_in_setters() {
480        let mut dt = DateTime::new(2024, 1, 31, 0, 0, 0).unwrap(); // January 31st
481
482        // Changing to February should fail because Feb doesn't have 31 days
483        assert_eq!(dt.set_month(2), Err(DateTimeError::InvalidDay));
484
485        // Original value should remain unchanged
486        assert_eq!(dt.month(), 1);
487        assert_eq!(dt.day_of_month(), 31);
488
489        // But changing to March should work (March has 31 days)
490        assert!(dt.set_month(3).is_ok());
491        assert_eq!(dt.month(), 3);
492    }
493
494    #[test]
495    fn test_weekday_calculation() {
496        let dt = DateTime::new(2024, 1, 1, 0, 0, 0).unwrap(); // New Year 2024
497        let weekday = dt.calculate_weekday().unwrap();
498        assert_eq!(weekday, Weekday::Monday); // January 1, 2024 was a Monday
499
500        let dt = DateTime::new(2024, 12, 25, 0, 0, 0).unwrap();
501        let weekday = dt.calculate_weekday().unwrap();
502        assert_eq!(weekday, Weekday::Wednesday); // December 25, 2024 is a Wednesday
503    }
504
505    #[test]
506    fn test_weekday_from_number() {
507        assert_eq!(Weekday::from_number(1).unwrap(), Weekday::Sunday);
508        assert_eq!(Weekday::from_number(2).unwrap(), Weekday::Monday);
509        assert_eq!(Weekday::from_number(7).unwrap(), Weekday::Saturday);
510        assert_eq!(Weekday::from_number(3).unwrap(), Weekday::Tuesday);
511
512        assert_eq!(
513            Weekday::from_number(0).unwrap_err(),
514            DateTimeError::InvalidWeekday
515        );
516        assert_eq!(
517            Weekday::from_number(8).unwrap_err(),
518            DateTimeError::InvalidWeekday
519        );
520    }
521
522    #[test]
523    fn test_weekday_to_number() {
524        assert_eq!(Weekday::Sunday.to_number(), 1);
525        assert_eq!(Weekday::Monday.to_number(), 2);
526        assert_eq!(Weekday::Saturday.to_number(), 7);
527    }
528
529    #[test]
530    fn test_weekday_as_str() {
531        assert_eq!(Weekday::Sunday.as_str(), "Sunday");
532        assert_eq!(Weekday::Monday.as_str(), "Monday");
533        assert_eq!(Weekday::Tuesday.as_str(), "Tuesday");
534        assert_eq!(Weekday::Wednesday.as_str(), "Wednesday");
535        assert_eq!(Weekday::Thursday.as_str(), "Thursday");
536        assert_eq!(Weekday::Friday.as_str(), "Friday");
537        assert_eq!(Weekday::Saturday.as_str(), "Saturday");
538    }
539
540    #[test]
541    fn test_calculate_weekday_known_dates() {
542        // Test some known dates
543        assert_eq!(calculate_weekday(2000, 1, 1).unwrap(), Weekday::Saturday);
544        assert_eq!(calculate_weekday(2024, 1, 1).unwrap(), Weekday::Monday);
545        assert_eq!(calculate_weekday(2025, 8, 15).unwrap(), Weekday::Friday);
546
547        // Test leap year boundary
548        assert_eq!(calculate_weekday(2024, 2, 29).unwrap(), Weekday::Thursday); // Leap day 2024
549    }
550
551    #[test]
552    fn test_is_leap_year() {
553        // Regular leap years (divisible by 4)
554        assert!(is_leap_year(2024));
555        assert!(is_leap_year(2020));
556        assert!(is_leap_year(1996));
557
558        // Non-leap years
559        assert!(!is_leap_year(2023));
560        assert!(!is_leap_year(2021));
561        assert!(!is_leap_year(1999));
562
563        // Century years (divisible by 100 but not 400)
564        assert!(!is_leap_year(1900));
565        assert!(!is_leap_year(2100));
566
567        // Century years divisible by 400
568        assert!(is_leap_year(2000));
569        assert!(is_leap_year(1600));
570    }
571
572    #[test]
573    fn test_days_in_month() {
574        // January (31 days)
575        assert_eq!(days_in_month(2024, 1), 31);
576
577        // February leap year (29 days)
578        assert_eq!(days_in_month(2024, 2), 29);
579
580        // February non-leap year (28 days)
581        assert_eq!(days_in_month(2023, 2), 28);
582
583        // April (30 days)
584        assert_eq!(days_in_month(2024, 4), 30);
585
586        // December (31 days)
587        assert_eq!(days_in_month(2024, 12), 31);
588
589        // Invalid month
590        assert_eq!(days_in_month(2024, 13), 0);
591        assert_eq!(days_in_month(2024, 0), 0);
592    }
593
594    #[test]
595    fn test_setter_interdependency_edge_cases() {
596        // January 31 → February (invalid because Feb max is 28/29)
597        let mut dt = DateTime::new(2023, 1, 31, 0, 0, 0).unwrap();
598        assert_eq!(dt.set_month(2), Err(DateTimeError::InvalidDay));
599
600        // March 31 → April (invalid because April max is 30)
601        let mut dt = DateTime::new(2023, 3, 31, 0, 0, 0).unwrap();
602        assert_eq!(dt.set_month(4), Err(DateTimeError::InvalidDay));
603
604        // Leap year Feb 29 → non-leap year
605        let mut dt = DateTime::new(2024, 2, 29, 0, 0, 0).unwrap();
606        assert_eq!(dt.set_year(2023), Err(DateTimeError::InvalidDay));
607
608        // Non-leap year Feb 28 → leap year (should work)
609        let mut dt = DateTime::new(2023, 2, 28, 0, 0, 0).unwrap();
610        assert!(dt.set_year(2024).is_ok());
611    }
612
613    #[test]
614    fn test_display_datetime_error() {
615        assert_eq!(format!("{}", DateTimeError::InvalidMonth), "invalid month");
616        assert_eq!(format!("{}", DateTimeError::InvalidDay), "invalid day");
617        assert_eq!(format!("{}", DateTimeError::InvalidHour), "invalid hour");
618        assert_eq!(
619            format!("{}", DateTimeError::InvalidMinute),
620            "invalid minute"
621        );
622        assert_eq!(
623            format!("{}", DateTimeError::InvalidSecond),
624            "invalid second"
625        );
626        assert_eq!(
627            format!("{}", DateTimeError::InvalidWeekday),
628            "invalid weekday"
629        );
630        assert_eq!(format!("{}", DateTimeError::InvalidYear), "invalid year");
631    }
632
633    #[test]
634    fn test_datetime_error_trait() {
635        let error = DateTimeError::InvalidMonth;
636        let _: &dyn core::error::Error = &error;
637    }
638
639    #[test]
640    fn test_boundary_values() {
641        // Test minimum valid year
642        assert!(DateTime::new(1970, 1, 1, 0, 0, 0).is_ok());
643
644        // Test maximum valid time values
645        assert!(DateTime::new(2024, 12, 31, 23, 59, 59).is_ok());
646
647        // Test minimum valid day/month
648        assert!(DateTime::new(2024, 1, 1, 0, 0, 0).is_ok());
649    }
650
651    #[test]
652    fn test_february_edge_cases() {
653        // Test February 28 in leap year
654        assert!(DateTime::new(2024, 2, 28, 0, 0, 0).is_ok());
655
656        // Test February 28 in non-leap year
657        assert!(DateTime::new(2023, 2, 28, 0, 0, 0).is_ok());
658
659        // Test February 29 in non-leap year (should fail)
660        assert_eq!(
661            DateTime::new(2023, 2, 29, 0, 0, 0).unwrap_err(),
662            DateTimeError::InvalidDay
663        );
664    }
665
666    #[test]
667    fn test_all_month_max_days() {
668        let year = 2023; // Non-leap year
669
670        // 31-day months
671        for month in [1, 3, 5, 7, 8, 10, 12] {
672            assert!(DateTime::new(year, month, 31, 0, 0, 0).is_ok());
673            assert_eq!(
674                DateTime::new(year, month, 32, 0, 0, 0).unwrap_err(),
675                DateTimeError::InvalidDay
676            );
677        }
678
679        // 30-day months
680        for month in [4, 6, 9, 11] {
681            assert!(DateTime::new(year, month, 30, 0, 0, 0).is_ok());
682            assert_eq!(
683                DateTime::new(year, month, 31, 0, 0, 0).unwrap_err(),
684                DateTimeError::InvalidDay
685            );
686        }
687    }
688
689    #[test]
690    fn test_set_day_of_month() {
691        let mut dt = DateTime::new(2024, 5, 15, 12, 30, 45).unwrap();
692
693        assert!(dt.set_day_of_month(10).is_ok());
694        assert_eq!(dt.day_of_month, 10);
695    }
696
697    #[test]
698    fn test_all_setters_preserve_state_on_error() {
699        let mut dt = DateTime::new(2024, 5, 15, 12, 30, 45).unwrap();
700        let original = dt;
701
702        // Test each setter preserves state when error occurs
703        assert!(dt.set_day_of_month(40).is_err());
704        assert_eq!(dt, original);
705
706        assert!(dt.set_minute(70).is_err());
707        assert_eq!(dt, original);
708
709        assert!(dt.set_second(70).is_err());
710        assert_eq!(dt, original);
711    }
712
713    #[test]
714    fn test_set_minute() {
715        let mut dt = DateTime::new(2024, 5, 15, 12, 30, 45).unwrap();
716
717        assert!(dt.set_minute(10).is_ok());
718        assert_eq!(dt.minute, 10);
719    }
720
721    #[test]
722    fn test_set_second() {
723        let mut dt = DateTime::new(2024, 5, 15, 12, 30, 45).unwrap();
724        assert!(dt.set_second(10).is_ok());
725        assert_eq!(dt.second, 10);
726    }
727}