affs_read/
date.rs

1//! Date/time handling for Amiga format.
2
3/// Amiga date representation.
4///
5/// Amiga stores dates as days since January 1, 1978,
6/// minutes since midnight, and ticks (1/50 second).
7#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
8pub struct AmigaDate {
9    /// Days since January 1, 1978.
10    pub days: i32,
11    /// Minutes since midnight.
12    pub mins: i32,
13    /// Ticks (1/50 second).
14    pub ticks: i32,
15}
16
17impl AmigaDate {
18    /// Create a new Amiga date from raw values.
19    #[inline]
20    pub const fn new(days: i32, mins: i32, ticks: i32) -> Self {
21        Self { days, mins, ticks }
22    }
23
24    /// Convert to a more usable date format.
25    #[inline]
26    pub fn to_date_time(self) -> DateTime {
27        let (year, month, day) = days_to_date(self.days);
28        let hour = (self.mins / 60) as u8;
29        let minute = (self.mins % 60) as u8;
30        let second = (self.ticks / 50) as u8;
31
32        DateTime {
33            year,
34            month,
35            day,
36            hour,
37            minute,
38            second,
39        }
40    }
41}
42
43/// Decoded date and time.
44#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
45pub struct DateTime {
46    /// Year (e.g., 1978-2100).
47    pub year: u16,
48    /// Month (1-12).
49    pub month: u8,
50    /// Day of month (1-31).
51    pub day: u8,
52    /// Hour (0-23).
53    pub hour: u8,
54    /// Minute (0-59).
55    pub minute: u8,
56    /// Second (0-59).
57    pub second: u8,
58}
59
60/// Convert days since 1978-01-01 to (year, month, day).
61fn days_to_date(mut days: i32) -> (u16, u8, u8) {
62    const DAYS_IN_MONTH: [i32; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
63
64    let mut year = 1978u16;
65
66    // Find year
67    loop {
68        let days_in_year = if is_leap_year(year) { 366 } else { 365 };
69        if days < days_in_year {
70            break;
71        }
72        days -= days_in_year;
73        year += 1;
74    }
75
76    // Find month
77    let mut month = 1u8;
78    let leap = is_leap_year(year);
79    for (i, &days_in_month) in DAYS_IN_MONTH.iter().enumerate() {
80        let dim = if i == 1 && leap { 29 } else { days_in_month };
81        if days < dim {
82            break;
83        }
84        days -= dim;
85        month += 1;
86    }
87
88    (year, month, (days + 1) as u8)
89}
90
91/// Check if a year is a leap year.
92#[inline]
93const fn is_leap_year(year: u16) -> bool {
94    if year.is_multiple_of(100) {
95        year.is_multiple_of(400)
96    } else {
97        year.is_multiple_of(4)
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    #[test]
106    fn test_epoch() {
107        let date = AmigaDate::new(0, 0, 0);
108        let dt = date.to_date_time();
109        assert_eq!(dt.year, 1978);
110        assert_eq!(dt.month, 1);
111        assert_eq!(dt.day, 1);
112        assert_eq!(dt.hour, 0);
113        assert_eq!(dt.minute, 0);
114        assert_eq!(dt.second, 0);
115    }
116
117    #[test]
118    fn test_known_date() {
119        // 1997-02-18 is day 6988
120        let date = AmigaDate::new(6988, 0, 0);
121        let dt = date.to_date_time();
122        assert_eq!(dt.year, 1997);
123        assert_eq!(dt.month, 2);
124        assert_eq!(dt.day, 18);
125    }
126
127    #[test]
128    fn test_time() {
129        let date = AmigaDate::new(0, 754, 150); // 12:34:03
130        let dt = date.to_date_time();
131        assert_eq!(dt.hour, 12);
132        assert_eq!(dt.minute, 34);
133        assert_eq!(dt.second, 3);
134    }
135
136    #[test]
137    fn test_leap_year() {
138        assert!(is_leap_year(2000));
139        assert!(!is_leap_year(1900));
140        assert!(is_leap_year(1984));
141        assert!(!is_leap_year(1983));
142    }
143}