dos_date_time/
dos_date.rs1mod cmp;
10mod consts;
11mod convert;
12mod fmt;
13
14use time::Month;
15
16use crate::error::{DateRangeError, DateRangeErrorKind};
17
18#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
29#[repr(transparent)]
30pub struct Date(u16);
31
32impl Date {
33 #[expect(clippy::missing_panics_doc)]
34 #[must_use]
50 pub fn new(date: u16) -> Option<Self> {
51 let (year, month, day) = (
52 (1980 + (date >> 9)).into(),
53 u8::try_from((date >> 5) & 0x0F)
54 .expect("month should be in the range of `u8`")
55 .try_into()
56 .ok()?,
57 (date & 0x1F)
58 .try_into()
59 .expect("day should be in the range of `u8`"),
60 );
61 let date = time::Date::from_calendar_date(year, month, day).ok()?;
62 Self::from_date(date).ok()
63 }
64
65 #[must_use]
71 pub const unsafe fn new_unchecked(date: u16) -> Self {
72 Self(date)
73 }
74
75 #[expect(clippy::missing_panics_doc)]
76 pub fn from_date(date: time::Date) -> Result<Self, DateRangeError> {
96 match date.year() {
97 ..=1979 => Err(DateRangeErrorKind::Negative.into()),
98 2108.. => Err(DateRangeErrorKind::Overflow.into()),
99 year => {
100 let (year, month, day) = (
101 u16::try_from(year - 1980).expect("year should be in the range of `u16`"),
102 u16::from(u8::from(date.month())),
103 u16::from(date.day()),
104 );
105 let date = (year << 9) | (month << 5) | day;
106 let date = unsafe { Self::new_unchecked(date) };
108 Ok(date)
109 }
110 }
111 }
112
113 #[must_use]
116 pub fn is_valid(self) -> bool {
117 Self::new(self.to_raw()).is_some()
118 }
119
120 #[must_use]
131 pub const fn to_raw(self) -> u16 {
132 self.0
133 }
134
135 #[must_use]
146 pub const fn year(self) -> u16 {
147 1980 + (self.to_raw() >> 9)
148 }
149
150 #[expect(clippy::missing_panics_doc)]
151 #[must_use]
162 pub fn month(self) -> Month {
163 u8::try_from((self.to_raw() >> 5) & 0x0F)
164 .expect("month should be in the range of `u8`")
165 .try_into()
166 .expect("month should be in the range of `Month`")
167 }
168
169 #[expect(clippy::missing_panics_doc)]
170 #[must_use]
181 pub fn day(self) -> u8 {
182 (self.to_raw() & 0x1F)
183 .try_into()
184 .expect("day should be in the range of `u8`")
185 }
186}
187
188impl Default for Date {
189 fn default() -> Self {
199 Self::MIN
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use core::mem;
206 #[cfg(feature = "std")]
207 use std::{
208 collections::hash_map::DefaultHasher,
209 hash::{Hash, Hasher},
210 };
211
212 use time::macros::date;
213
214 use super::*;
215
216 #[test]
217 fn size_of() {
218 assert_eq!(mem::size_of::<Date>(), mem::size_of::<u16>());
219 }
220
221 #[test]
222 fn align_of() {
223 assert_eq!(mem::align_of::<Date>(), mem::align_of::<u16>());
224 }
225
226 #[test]
227 fn clone() {
228 assert_eq!(Date::MIN.clone(), Date::MIN);
229 }
230
231 #[test]
232 fn copy() {
233 let a = Date::MIN;
234 let b = a;
235 assert_eq!(a, b);
236 }
237
238 #[cfg(feature = "std")]
239 #[test]
240 fn hash() {
241 assert_ne!(
242 {
243 let mut hasher = DefaultHasher::new();
244 Date::MIN.hash(&mut hasher);
245 hasher.finish()
246 },
247 {
248 let mut hasher = DefaultHasher::new();
249 Date::MAX.hash(&mut hasher);
250 hasher.finish()
251 }
252 );
253 }
254
255 #[test]
256 fn new() {
257 assert_eq!(Date::new(0b0000_0000_0010_0001).unwrap(), Date::MIN);
258 assert_eq!(Date::new(0b1111_1111_1001_1111).unwrap(), Date::MAX);
259 }
260
261 #[test]
262 fn new_with_invalid_date() {
263 assert!(Date::new(0b0000_0000_0010_0000).is_none());
265 assert!(Date::new(0b0000_0000_0101_1110).is_none());
267 assert!(Date::new(0b0000_0000_0000_0001).is_none());
269 assert!(Date::new(0b0000_0001_1010_0001).is_none());
271 }
272
273 #[test]
274 fn new_unchecked() {
275 assert_eq!(
276 unsafe { Date::new_unchecked(0b0000_0000_0010_0001) },
277 Date::MIN
278 );
279 assert_eq!(
280 unsafe { Date::new_unchecked(0b1111_1111_1001_1111) },
281 Date::MAX
282 );
283 }
284
285 #[test]
286 const fn new_unchecked_is_const_fn() {
287 const _: Date = unsafe { Date::new_unchecked(0b0000_0000_0010_0001) };
288 }
289
290 #[test]
291 fn from_date_before_dos_date_epoch() {
292 assert_eq!(
293 Date::from_date(date!(1979-12-31)).unwrap_err(),
294 DateRangeErrorKind::Negative.into()
295 );
296 assert_eq!(
297 Date::from_date(date!(1979-12-31)).unwrap_err(),
298 DateRangeErrorKind::Negative.into()
299 );
300 }
301
302 #[test]
303 fn from_date() {
304 assert_eq!(Date::from_date(date!(1980-01-01)).unwrap(), Date::MIN);
305 assert_eq!(Date::from_date(date!(1980-01-01)).unwrap(), Date::MIN);
306 assert_eq!(
308 Date::from_date(date!(2002-11-26)).unwrap(),
309 Date::new(0b0010_1101_0111_1010).unwrap()
310 );
311 assert_eq!(
313 Date::from_date(date!(2018-11-17)).unwrap(),
314 Date::new(0b0100_1101_0111_0001).unwrap()
315 );
316 assert_eq!(Date::from_date(date!(2107-12-31)).unwrap(), Date::MAX);
317 assert_eq!(Date::from_date(date!(2107-12-31)).unwrap(), Date::MAX);
318 }
319
320 #[test]
321 fn from_date_with_too_big_date() {
322 assert_eq!(
323 Date::from_date(date!(2108-01-01)).unwrap_err(),
324 DateRangeErrorKind::Overflow.into()
325 );
326 }
327
328 #[test]
329 fn is_valid() {
330 assert!(Date::MIN.is_valid());
331 assert!(Date::new(0b0010_1101_0111_1010).unwrap().is_valid());
333 assert!(Date::new(0b0100_1101_0111_0001).unwrap().is_valid());
335 assert!(Date::MAX.is_valid());
336 }
337
338 #[test]
339 fn is_valid_with_invalid_date() {
340 assert!(!unsafe { Date::new_unchecked(0b0000_0000_0010_0000) }.is_valid());
342 assert!(!unsafe { Date::new_unchecked(0b0000_0000_0101_1110) }.is_valid());
344 assert!(!unsafe { Date::new_unchecked(0b0000_0000_0000_0001) }.is_valid());
346 assert!(!unsafe { Date::new_unchecked(0b0000_0001_1010_0001) }.is_valid());
348 }
349
350 #[test]
351 fn to_raw() {
352 assert_eq!(Date::MIN.to_raw(), 0b0000_0000_0010_0001);
353 assert_eq!(
355 Date::new(0b0010_1101_0111_1010).unwrap().to_raw(),
356 0b0010_1101_0111_1010
357 );
358 assert_eq!(
360 Date::new(0b0100_1101_0111_0001).unwrap().to_raw(),
361 0b0100_1101_0111_0001
362 );
363 assert_eq!(Date::MAX.to_raw(), 0b1111_1111_1001_1111);
364 }
365
366 #[test]
367 const fn to_raw_is_const_fn() {
368 const _: u16 = Date::MIN.to_raw();
369 }
370
371 #[test]
372 fn year() {
373 assert_eq!(Date::MIN.year(), 1980);
374 assert_eq!(Date::new(0b0010_1101_0111_1010).unwrap().year(), 2002);
376 assert_eq!(Date::new(0b0100_1101_0111_0001).unwrap().year(), 2018);
378 assert_eq!(Date::MAX.year(), 2107);
379 }
380
381 #[test]
382 const fn year_is_const_fn() {
383 const _: u16 = Date::MIN.year();
384 }
385
386 #[test]
387 fn month() {
388 assert_eq!(Date::MIN.month(), Month::January);
389 assert_eq!(
391 Date::new(0b0010_1101_0111_1010).unwrap().month(),
392 Month::November
393 );
394 assert_eq!(
396 Date::new(0b0100_1101_0111_0001).unwrap().month(),
397 Month::November
398 );
399 assert_eq!(Date::MAX.month(), Month::December);
400 }
401
402 #[test]
403 fn day() {
404 assert_eq!(Date::MIN.day(), 1);
405 assert_eq!(Date::new(0b0010_1101_0111_1010).unwrap().day(), 26);
407 assert_eq!(Date::new(0b0100_1101_0111_0001).unwrap().day(), 17);
409 assert_eq!(Date::MAX.day(), 31);
410 }
411
412 #[test]
413 fn default() {
414 assert_eq!(Date::default(), Date::MIN);
415 }
416}