Skip to main content

rkg_utils/header/
date.rs

1use crate::byte_handler::{ByteHandlerError, FromByteHandler};
2
3/// Errors that can occur while constructing a [`Date`].
4#[derive(thiserror::Error, Debug)]
5pub enum DateError {
6    /// The year is outside the valid range (2000–2035).
7    #[error("Year is invalid")]
8    YearInvalid,
9    /// The month is outside the valid range (1–12).
10    #[error("Month is invalid")]
11    MonthInvalid,
12    /// The day is zero, or exceeds the maximum number of days for the given month and year.
13    #[error("Day is invalid")]
14    DayInvalid,
15    /// A `ByteHandler` operation failed.
16    #[error("ByteHandler Error: {0}")]
17    ByteHandlerError(#[from] ByteHandlerError),
18}
19
20/// All valid dates on the Wii. Year is a range between 0 and 35, symbolizing 2000 and 2035
21/// respectively. Leap years are accounted for when validating February day counts.
22#[derive(Debug)]
23pub struct Date {
24    /// Year offset from 2000 (0–35), representing 2000–2035.
25    year: u8,
26    /// Month (1–12).
27    month: u8,
28    /// Day of the month.
29    day: u8,
30}
31
32impl Date {
33    /// Creates a new [`Date`] from a full year, month, and day.
34    ///
35    /// # Arguments
36    ///
37    /// * `year` - The full calendar year (2000–2035).
38    /// * `month` - The month (1–12).
39    /// * `day` - The day of the month, validated against the given month and year.
40    ///
41    /// # Errors
42    ///
43    /// Returns [`DateError::YearInvalid`] if `year` is outside the range 2000–2035.
44    /// Returns [`DateError::MonthInvalid`] if `month` is outside the range 1–12.
45    /// Returns [`DateError::DayInvalid`] if `day` exceeds the maximum for the given
46    /// month, accounting for leap years in February.
47    pub fn new(year: u16, month: u8, day: u8) -> Result<Self, DateError> {
48        let year = (year - 2000) as u8;
49
50        if year > 35 {
51            return Err(DateError::YearInvalid);
52        }
53
54        match month {
55            1 | 3 | 5 | 7 | 8 | 10 | 12 if day > 31 => Err(DateError::DayInvalid),
56            4 | 6 | 9 | 11 if day > 30 => Err(DateError::DayInvalid),
57            2 if year.is_multiple_of(4) && day > 29 => Err(DateError::DayInvalid),
58            2 if !year.is_multiple_of(4) && day > 28 => Err(DateError::DayInvalid),
59            1..=12 => Ok(Self { year, month, day }),
60            _ => Err(DateError::MonthInvalid),
61        }
62    }
63
64    /// Returns the full calendar year (2000–2035).
65    ///
66    /// The year is stored internally as an offset from 2000; this method adds
67    /// 2000 to produce the full four-digit year.
68    pub fn year(&self) -> u16 {
69        (self.year as u16) + 2000
70    }
71
72    /// Returns the month (1–12).
73    pub fn month(&self) -> u8 {
74        self.month
75    }
76
77    /// Returns the day of the month.
78    pub fn day(&self) -> u8 {
79        self.day
80    }
81}
82
83/// Deserializes a [`Date`] from 3 bytes at header offset `0x09..=0x0B`.
84///
85/// The bytes are packed as follows:
86/// ```text
87/// Byte 1: XXXXYYYY
88/// Byte 2: YYYMMMMD
89/// Byte 3: DDDDXXXX
90/// ```
91/// where `Y` = year bits, `M` = month bits, and `D` = day bits.
92impl FromByteHandler for Date {
93    type Err = DateError;
94    fn from_byte_handler<T>(handler: T) -> Result<Self, Self::Err>
95    where
96        T: TryInto<crate::byte_handler::ByteHandler>,
97        Self::Err: From<T::Error>,
98    {
99        let mut handler = handler.try_into()?;
100
101        handler.shift_right(4);
102        let year = ((handler.copy_byte(1) >> 1) as u16) + 2000;
103        let day = handler.copy_byte(2) & 0x1F;
104        handler.shift_right(5);
105        let month = handler.copy_byte(2) & 0x0F;
106
107        Self::new(year, month, day)
108    }
109}
110
111/// Formats [`Date`] as YYYY-MM-DD
112impl std::fmt::Display for Date {
113    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114        write!(f, "20{:02}-{:02}-{:02}", self.year, self.month, self.day)
115    }
116}
117
118/// Two [`Date`] values are equal if their year, month, and day are all identical.
119impl PartialEq for Date {
120    fn eq(&self, other: &Self) -> bool {
121        self.day == other.day && self.month == other.month && self.year == other.year
122    }
123}