1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/// Things that impl this can tell you the current time.
pub trait TimeSource {
    /// Returns the current time
    fn get_timestamp(&self) -> Timestamp;
}

/// Represents an instant in time, in the local time zone. TODO: Consider
/// replacing this with POSIX time as a `u32`, which would save two bytes at
/// the expense of some maths.
#[cfg_attr(feature = "defmt-log", derive(defmt::Format))]
#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq)]
pub struct Timestamp {
    /// Add 1970 to this file to get the calendar year
    pub year_since_1970: u8,
    /// Add one to this value to get the calendar month
    pub zero_indexed_month: u8,
    /// Add one to this value to get the calendar day
    pub zero_indexed_day: u8,
    /// The number of hours past midnight
    pub hours: u8,
    /// The number of minutes past the hour
    pub minutes: u8,
    /// The number of seconds past the minute
    pub seconds: u8,
}

impl Timestamp {
    /// Create a `Timestamp` from the 16-bit FAT date and time fields.
    pub fn from_fat(date: u16, time: u16) -> Timestamp {
        let year = 1980 + (date >> 9);
        let month = ((date >> 5) & 0x000F) as u8;
        let day = (date & 0x001F) as u8;
        let hours = ((time >> 11) & 0x001F) as u8;
        let minutes = ((time >> 5) & 0x0003F) as u8;
        let seconds = ((time << 1) & 0x0003F) as u8;
        // Volume labels have a zero for month/day, so tolerate that...
        Timestamp {
            year_since_1970: (year - 1970) as u8,
            zero_indexed_month: if month == 0 { 0 } else { month - 1 },
            zero_indexed_day: if day == 0 { 0 } else { day - 1 },
            hours,
            minutes,
            seconds,
        }
    }

    // TODO add tests for the method
    /// Serialize a `Timestamp` to FAT format
    pub fn serialize_to_fat(self) -> [u8; 4] {
        let mut data = [0u8; 4];

        let hours = (u16::from(self.hours) << 11) & 0xF800;
        let minutes = (u16::from(self.minutes) << 5) & 0x07E0;
        let seconds = (u16::from(self.seconds / 2)) & 0x001F;
        data[..2].copy_from_slice(&(hours | minutes | seconds).to_le_bytes()[..]);

        let year = if self.year_since_1970 < 10 {
            0
        } else {
            (u16::from(self.year_since_1970 - 10) << 9) & 0xFE00
        };
        let month = (u16::from(self.zero_indexed_month + 1) << 5) & 0x01E0;
        let day = u16::from(self.zero_indexed_day + 1) & 0x001F;
        data[2..].copy_from_slice(&(year | month | day).to_le_bytes()[..]);
        data
    }

    /// Create a `Timestamp` from year/month/day/hour/minute/second.
    ///
    /// Values should be given as you'd write then (i.e. 1980, 01, 01, 13, 30,
    /// 05) is 1980-Jan-01, 1:30:05pm.
    pub fn from_calendar(
        year: u16,
        month: u8,
        day: u8,
        hours: u8,
        minutes: u8,
        seconds: u8,
    ) -> Result<Timestamp, &'static str> {
        Ok(Timestamp {
            year_since_1970: if (1970..=(1970 + 255)).contains(&year) {
                (year - 1970) as u8
            } else {
                return Err("Bad year");
            },
            zero_indexed_month: if (1..=12).contains(&month) {
                month - 1
            } else {
                return Err("Bad month");
            },
            zero_indexed_day: if (1..=31).contains(&day) {
                day - 1
            } else {
                return Err("Bad day");
            },
            hours: if hours <= 23 {
                hours
            } else {
                return Err("Bad hours");
            },
            minutes: if minutes <= 59 {
                minutes
            } else {
                return Err("Bad minutes");
            },
            seconds: if seconds <= 59 {
                seconds
            } else {
                return Err("Bad seconds");
            },
        })
    }
}

impl core::fmt::Debug for Timestamp {
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        write!(f, "Timestamp({})", self)
    }
}

impl core::fmt::Display for Timestamp {
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        write!(
            f,
            "{}-{:02}-{:02} {:02}:{:02}:{:02}",
            u16::from(self.year_since_1970) + 1970,
            self.zero_indexed_month + 1,
            self.zero_indexed_day + 1,
            self.hours,
            self.minutes,
            self.seconds
        )
    }
}