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