rustversion_detect/date.rs
1//! Contains a basic [`Date`] type used for differentiating nightly versions of rust.
2//!
3//! Intentionally ignores timezone information, making it simpler than the [`time` crate]
4//!
5//! [`time` crate]: https://github.com/time-rs/time
6
7use core::fmt::{self, Display};
8
9/// Indicates the date.
10///
11/// Used for the nightly versions of rust.
12///
13/// The timezone is not explicitly specified here,
14/// and matches whatever one the rust team uses for nightly releases.
15#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
16pub struct Date {
17 /// The year
18 pub year: u16,
19 /// The month
20 pub month: u8,
21 /// The day of the month.
22 pub day: u8,
23}
24impl Date {
25 /// Create a date, using YYYY-MM-DD format (ISO 8601).
26 ///
27 /// For example, `Date::new(2018, 10, 21)`
28 ///
29 /// If possible, perform some basic validation.
30 ///
31 /// TODO: Don't make this a `const fn` function,
32 /// because doing validation is more important.
33 #[inline]
34 #[cfg_attr(has_track_caller, track_caller)]
35 pub const fn new(year: u16, month: u8, day: u8) -> Self {
36 #[cfg(has_const_panic)]
37 {
38 assert!(year >= 1, "Invalid year");
39 assert!(month >= 1 && month <= 12, "Invalid month");
40 assert!(day >= 1 && day <= 31, "Invalid day of month");
41 }
42 Date { year, month, day }
43 }
44
45 maybe_const_fn! {
46 #[cfg_const(has_const_match)]
47 /// Check if this date is equal to or after the specified start.
48 ///
49 /// Equivalent to `self >= start`,
50 /// but available as a `const` function.
51 ///
52 /// ## Example
53 /// ```
54 /// # use rustversion_detect::Date;;
55 /// assert!(Date::new(2024, 11, 16).is_since(Date::new(2024, 7, 28)));
56 /// ```
57 #[inline]
58 pub const fn is_since(&self, start: Date) -> bool {
59 self.year > start.year
60 || (self.year == start.year
61 && (self.month > start.month
62 || (self.month == start.month && self.day >= start.day)))
63 }
64
65 #[cfg_const(has_const_match)]
66 /// Check if this date is before the specified end.
67 ///
68 /// Equivalent to `self < end`,
69 /// but available as a `const` function.
70 ///
71 /// ## Example
72 /// ```
73 /// # use rustversion_detect::Date;
74 /// assert!(Date::new(2018, 12, 14).is_before(Date::new(2022, 8, 16)));
75 /// assert!(Date::new(2024, 11, 14).is_before(Date::new(2024, 12, 7)));
76 /// assert!(Date::new(2024, 11, 14).is_before(Date::new(2024, 11, 17)));
77 /// ```
78 #[inline]
79 pub const fn is_before(&self, end: Date) -> bool {
80 !self.is_since(end)
81 }
82 }
83}
84
85/// Declare a [`Date`] using the YYYY-MM-DD format (ISO 8601).
86///
87/// This is deprecated, because it cannot be implemented on Rust 1.31
88/// and the whole point of the crate is supporting old versions.
89#[macro_export]
90#[cfg(supports_macro_literal)]
91#[deprecated(note = "Cannot be supported on Rust 1.31")]
92macro_rules! date {
93 ($year:literal - $month:literal - $day:literal) => {{
94 // NOTE: The Date::new function sometimes perfroms validation
95 // It only validates if `const_panic` is stablized.
96 const DTE: $crate::date::Date = $crate::date::Date::new($year, $month, $day);
97 DTE
98 }};
99}
100
101/// Displays the date consistent with the ISO 8601 standard.
102impl Display for Date {
103 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
104 write!(
105 formatter,
106 "{:04}-{:02}-{:02}",
107 self.year, self.month, self.day,
108 )
109 }
110}
111
112#[cfg(test)]
113mod test {
114 use super::Date;
115
116 // (before, after)
117 fn test_dates() -> Vec<(Date, Date)> {
118 vec![
119 (Date::new(2018, 12, 14), Date::new(2022, 8, 16)),
120 (Date::new(2024, 11, 14), Date::new(2024, 12, 7)),
121 (Date::new(2024, 11, 14), Date::new(2024, 11, 17)),
122 ]
123 }
124
125 #[test]
126 fn test_before_after() {
127 for (before, after) in test_dates() {
128 assert!(before.is_before(after), "{} & {}", before, after);
129 assert!(after.is_since(before), "{} & {}", before, after);
130 // check equal dates
131 for &date in [before, after].iter() {
132 assert!(date.is_since(date), "{}", date);
133 assert!(!date.is_before(date), "{}", date);
134 }
135 }
136 }
137
138 #[test]
139 #[cfg_attr(has_const_panic, should_panic(expected = "Invalid year"))]
140 fn test_invalid_year() {
141 Date::new(0, 7, 18);
142 }
143
144 #[test]
145 #[cfg_attr(has_const_panic, should_panic(expected = "Invalid month"))]
146 fn test_invalid_month() {
147 Date::new(2014, 13, 18);
148 }
149
150 #[test]
151 #[cfg_attr(has_const_panic, should_panic(expected = "Invalid day of month"))]
152 fn test_invalid_date() {
153 Date::new(2014, 7, 36);
154 }
155}