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 Ok(unsafe { Self::new_unchecked(date) })
108 }
109 }
110 }
111
112 #[must_use]
115 pub fn is_valid(self) -> bool {
116 Self::new(self.to_raw()).is_some()
117 }
118
119 #[must_use]
130 pub const fn to_raw(self) -> u16 {
131 self.0
132 }
133
134 #[must_use]
145 pub const fn year(self) -> u16 {
146 1980 + (self.to_raw() >> 9)
147 }
148
149 #[expect(clippy::missing_panics_doc)]
150 #[must_use]
161 pub fn month(self) -> Month {
162 u8::try_from((self.to_raw() >> 5) & 0x0F)
163 .expect("month should be in the range of `u8`")
164 .try_into()
165 .expect("month should be in the range of `Month`")
166 }
167
168 #[expect(clippy::missing_panics_doc)]
169 #[must_use]
180 pub fn day(self) -> u8 {
181 (self.to_raw() & 0x1F)
182 .try_into()
183 .expect("day should be in the range of `u8`")
184 }
185}
186
187impl Default for Date {
188 fn default() -> Self {
198 Self::MIN
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use core::mem;
205 #[cfg(feature = "std")]
206 use std::{
207 collections::hash_map::DefaultHasher,
208 hash::{Hash, Hasher},
209 };
210
211 use time::macros::date;
212
213 use super::*;
214
215 #[test]
216 fn size_of() {
217 assert_eq!(mem::size_of::<Date>(), mem::size_of::<u16>());
218 }
219
220 #[test]
221 fn align_of() {
222 assert_eq!(mem::align_of::<Date>(), mem::align_of::<u16>());
223 }
224
225 #[test]
226 fn clone() {
227 assert_eq!(Date::MIN.clone(), Date::MIN);
228 }
229
230 #[test]
231 fn copy() {
232 let a = Date::MIN;
233 let b = a;
234 assert_eq!(a, b);
235 }
236
237 #[cfg(feature = "std")]
238 #[test]
239 fn hash() {
240 assert_ne!(
241 {
242 let mut hasher = DefaultHasher::new();
243 Date::MIN.hash(&mut hasher);
244 hasher.finish()
245 },
246 {
247 let mut hasher = DefaultHasher::new();
248 Date::MAX.hash(&mut hasher);
249 hasher.finish()
250 }
251 );
252 }
253
254 #[test]
255 fn new() {
256 assert_eq!(Date::new(0b0000_0000_0010_0001).unwrap(), Date::MIN);
257 assert_eq!(Date::new(0b1111_1111_1001_1111).unwrap(), Date::MAX);
258 }
259
260 #[test]
261 fn new_with_invalid_date() {
262 assert!(Date::new(0b0000_0000_0010_0000).is_none());
264 assert!(Date::new(0b0000_0000_0101_1110).is_none());
266 assert!(Date::new(0b0000_0000_0000_0001).is_none());
268 assert!(Date::new(0b0000_0001_1010_0001).is_none());
270 }
271
272 #[test]
273 fn new_unchecked() {
274 assert_eq!(
275 unsafe { Date::new_unchecked(0b0000_0000_0010_0001) },
276 Date::MIN
277 );
278 assert_eq!(
279 unsafe { Date::new_unchecked(0b1111_1111_1001_1111) },
280 Date::MAX
281 );
282 }
283
284 #[test]
285 const fn new_unchecked_is_const_fn() {
286 const _: Date = unsafe { Date::new_unchecked(0b0000_0000_0010_0001) };
287 }
288
289 #[test]
290 fn from_date_before_dos_date_epoch() {
291 assert_eq!(
292 Date::from_date(date!(1979-12-31)).unwrap_err(),
293 DateRangeErrorKind::Negative.into()
294 );
295 assert_eq!(
296 Date::from_date(date!(1979-12-31)).unwrap_err(),
297 DateRangeErrorKind::Negative.into()
298 );
299 }
300
301 #[test]
302 fn from_date() {
303 assert_eq!(Date::from_date(date!(1980-01-01)).unwrap(), Date::MIN);
304 assert_eq!(Date::from_date(date!(1980-01-01)).unwrap(), Date::MIN);
305 assert_eq!(
307 Date::from_date(date!(2002-11-26)).unwrap(),
308 Date::new(0b0010_1101_0111_1010).unwrap()
309 );
310 assert_eq!(
312 Date::from_date(date!(2018-11-17)).unwrap(),
313 Date::new(0b0100_1101_0111_0001).unwrap()
314 );
315 assert_eq!(Date::from_date(date!(2107-12-31)).unwrap(), Date::MAX);
316 assert_eq!(Date::from_date(date!(2107-12-31)).unwrap(), Date::MAX);
317 }
318
319 #[test]
320 fn from_date_with_too_big_date() {
321 assert_eq!(
322 Date::from_date(date!(2108-01-01)).unwrap_err(),
323 DateRangeErrorKind::Overflow.into()
324 );
325 }
326
327 #[test]
328 fn is_valid() {
329 assert!(Date::MIN.is_valid());
330 assert!(Date::new(0b0010_1101_0111_1010).unwrap().is_valid());
332 assert!(Date::new(0b0100_1101_0111_0001).unwrap().is_valid());
334 assert!(Date::MAX.is_valid());
335 }
336
337 #[test]
338 fn is_valid_with_invalid_date() {
339 assert!(!unsafe { Date::new_unchecked(0b0000_0000_0010_0000) }.is_valid());
341 assert!(!unsafe { Date::new_unchecked(0b0000_0000_0101_1110) }.is_valid());
343 assert!(!unsafe { Date::new_unchecked(0b0000_0000_0000_0001) }.is_valid());
345 assert!(!unsafe { Date::new_unchecked(0b0000_0001_1010_0001) }.is_valid());
347 }
348
349 #[test]
350 fn to_raw() {
351 assert_eq!(Date::MIN.to_raw(), 0b0000_0000_0010_0001);
352 assert_eq!(
354 Date::new(0b0010_1101_0111_1010).unwrap().to_raw(),
355 0b0010_1101_0111_1010
356 );
357 assert_eq!(
359 Date::new(0b0100_1101_0111_0001).unwrap().to_raw(),
360 0b0100_1101_0111_0001
361 );
362 assert_eq!(Date::MAX.to_raw(), 0b1111_1111_1001_1111);
363 }
364
365 #[test]
366 const fn to_raw_is_const_fn() {
367 const _: u16 = Date::MIN.to_raw();
368 }
369
370 #[test]
371 fn year() {
372 assert_eq!(Date::MIN.year(), 1980);
373 assert_eq!(Date::new(0b0010_1101_0111_1010).unwrap().year(), 2002);
375 assert_eq!(Date::new(0b0100_1101_0111_0001).unwrap().year(), 2018);
377 assert_eq!(Date::MAX.year(), 2107);
378 }
379
380 #[test]
381 const fn year_is_const_fn() {
382 const _: u16 = Date::MIN.year();
383 }
384
385 #[test]
386 fn month() {
387 assert_eq!(Date::MIN.month(), Month::January);
388 assert_eq!(
390 Date::new(0b0010_1101_0111_1010).unwrap().month(),
391 Month::November
392 );
393 assert_eq!(
395 Date::new(0b0100_1101_0111_0001).unwrap().month(),
396 Month::November
397 );
398 assert_eq!(Date::MAX.month(), Month::December);
399 }
400
401 #[test]
402 fn day() {
403 assert_eq!(Date::MIN.day(), 1);
404 assert_eq!(Date::new(0b0010_1101_0111_1010).unwrap().day(), 26);
406 assert_eq!(Date::new(0b0100_1101_0111_0001).unwrap().day(), 17);
408 assert_eq!(Date::MAX.day(), 31);
409 }
410
411 #[test]
412 fn default() {
413 assert_eq!(Date::default(), Date::MIN);
414 }
415}