rust_toolchain/
date.rs

1use std::fmt;
2
3/// A release date for a Rust toolchain.
4///
5/// Nightly toolchains use a date as version instead of a semver number.
6/// This Date should be regarded as a form of a version number, just like semver.
7///
8/// For full-featured dates, it is recommended to use a dedicated library
9/// like [`time`], [`chrono`] or [`jiff`].
10///
11/// [`time`]: https://docs.rs/time/latest/time/
12/// [`chrono`]: https://docs.rs/chrono/latest/chrono/
13/// [`jiff`]:https://docs.rs/jiff/latest/jiff/
14#[derive(Clone, Debug, Eq, Hash, PartialEq, Ord, PartialOrd)]
15pub struct Date {
16    date: DateImpl,
17}
18
19impl Date {
20    /// Create a new `Date` instance.
21    ///
22    /// While it is called a `date`, it is merely a shallow representation of
23    /// a day in time where a new release was cut. This release date
24    /// should not be used to perform operations on, and may not even be
25    /// valid in the Gregorian calendar. For example, a date `0-0-0` will
26    /// be accepted, but is not a valid month nor year for most parsers
27    /// which parse a Gregorian date.
28    ///
29    /// It is up to the caller to make sure that the given date is valid.
30    /// This library just takes a date representation "as-is".
31    pub fn new(year: u16, month: u8, day: u8) -> Self {
32        Self {
33            date: DateImpl { year, month, day },
34        }
35    }
36
37    /// The year
38    pub fn year(&self) -> u16 {
39        self.date.year
40    }
41
42    /// The month
43    pub fn month(&self) -> u8 {
44        self.date.month
45    }
46
47    /// The day
48    pub fn day(&self) -> u8 {
49        self.date.day
50    }
51
52    /// Prints a yyyy-mm-dd representation of a release date.
53    ///
54    /// This representation may, just like [`Date`], be not a valid date
55    /// in the Gregorian calendar. The date is merely a representation.
56    ///
57    /// Year, month, and day will all be pre-filled with 0's.
58    /// For year, at least four numbers are shown. For
59    /// month and day, two.
60    ///
61    /// Note that, a representation of `9999-200-200` is still possible, while
62    /// not valid as a Gregorian date.
63    pub fn ymd(&self) -> impl fmt::Display {
64        let y = self.date.year;
65        let m = self.date.month;
66        let d = self.date.day;
67
68        format!("{y:04}-{m:02}-{d:02}")
69    }
70}
71
72/// A compact date consisting of a four number year, and a two number month and day.
73/// Up to the caller to ensure it matches with their reality of a 'valid date'.
74///
75/// Not intended to be compatible with common date standards
76#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Ord, PartialOrd)]
77struct DateImpl {
78    year: u16,
79    month: u8,
80    day: u8,
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use std::cmp::Ordering;
87
88    #[yare::parameterized(
89        zeroes = { Date::new(0, 0, 0), 0, 0, 0  },
90        one_two_three = { Date::new(1, 2, 3), 1, 2, 3 },
91        max = { Date::new(u16::MAX, u8::MAX, u8::MAX), u16::MAX, u8::MAX, u8::MAX },
92    )]
93    fn create_release_date(date: Date, year: u16, month: u8, day: u8) {
94        let expected = Date {
95            date: DateImpl { year, month, day },
96        };
97
98        assert_eq!(date, expected);
99    }
100
101    #[test]
102    fn compare_year() {
103        let smaller = Date::new(2000, 1, 1);
104        let bigger = Date::new(2001, 1, 1);
105
106        assert!(smaller < bigger);
107        assert!(smaller <= bigger);
108    }
109
110    #[test]
111    fn compare_month() {
112        let smaller = Date::new(2000, 1, 1);
113        let bigger = Date::new(2000, 2, 1);
114
115        assert!(smaller < bigger);
116        assert!(smaller <= bigger);
117    }
118
119    #[test]
120    fn compare_day() {
121        let smaller = Date::new(2000, 1, 1);
122        let bigger = Date::new(2000, 1, 2);
123
124        assert!(smaller < bigger);
125        assert!(smaller <= bigger);
126    }
127
128    #[yare::parameterized(
129        zeroes = { Date::new(0, 0, 0), "0000-00-00"  },
130        fill_y = { Date::new(1, 10, 10), "0001-10-10"  },
131        fill_m = { Date::new(1000, 1, 10), "1000-01-10"  },
132        fill_d = { Date::new(1000, 10, 1), "1000-10-01"  },
133        invalid_month_is_not_rejected = { Date::new(1000, 100, 1), "1000-100-01"  },
134        invalid_day_is_not_rejected = { Date::new(1000, 1, 100), "1000-01-100"  },
135    )]
136    fn to_string(date: Date, expected: &str) {
137        assert_eq!(date.ymd().to_string(), expected.to_string());
138    }
139
140    #[test]
141    fn newer_date() {
142        let newer = Date::new(2000, 1, 1);
143        let older = Date::new(1999, 1, 1);
144
145        assert_eq!(newer.cmp(&older), Ordering::Greater);
146    }
147}