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 #[allow(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 #[allow(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 #[allow(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 #[allow(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 {
202 Self::MIN
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use core::mem;
209 #[cfg(feature = "std")]
210 use std::{
211 collections::hash_map::DefaultHasher,
212 hash::{Hash, Hasher},
213 };
214
215 use time::macros::date;
216
217 use super::*;
218
219 #[test]
220 fn size_of() {
221 assert_eq!(mem::size_of::<Date>(), mem::size_of::<u16>());
222 }
223
224 #[test]
225 fn align_of() {
226 assert_eq!(mem::align_of::<Date>(), mem::align_of::<u16>());
227 }
228
229 #[test]
230 fn clone() {
231 assert_eq!(Date::MIN.clone(), Date::MIN);
232 }
233
234 #[test]
235 fn copy() {
236 let a = Date::MIN;
237 let b = a;
238 assert_eq!(a, b);
239 }
240
241 #[cfg(feature = "std")]
242 #[test]
243 fn hash() {
244 assert_ne!(
245 {
246 let mut hasher = DefaultHasher::new();
247 Date::MIN.hash(&mut hasher);
248 hasher.finish()
249 },
250 {
251 let mut hasher = DefaultHasher::new();
252 Date::MAX.hash(&mut hasher);
253 hasher.finish()
254 }
255 );
256 }
257
258 #[test]
259 fn new() {
260 assert_eq!(Date::new(0b0000_0000_0010_0001).unwrap(), Date::MIN);
261 assert_eq!(Date::new(0b1111_1111_1001_1111).unwrap(), Date::MAX);
262 }
263
264 #[test]
265 fn new_with_invalid_date() {
266 assert!(Date::new(0b0000_0000_0010_0000).is_none());
268 assert!(Date::new(0b0000_0000_0101_1110).is_none());
270 assert!(Date::new(0b0000_0000_0000_0001).is_none());
272 assert!(Date::new(0b0000_0001_1010_0001).is_none());
274 }
275
276 #[test]
277 fn new_unchecked() {
278 assert_eq!(
279 unsafe { Date::new_unchecked(0b0000_0000_0010_0001) },
280 Date::MIN
281 );
282 assert_eq!(
283 unsafe { Date::new_unchecked(0b1111_1111_1001_1111) },
284 Date::MAX
285 );
286 }
287
288 #[test]
289 const fn new_unchecked_is_const_fn() {
290 const _: Date = unsafe { Date::new_unchecked(0b0000_0000_0010_0001) };
291 }
292
293 #[test]
294 fn from_date_before_dos_date_epoch() {
295 assert_eq!(
296 Date::from_date(date!(1979-12-31)).unwrap_err(),
297 DateRangeErrorKind::Negative.into()
298 );
299 assert_eq!(
300 Date::from_date(date!(1979-12-31)).unwrap_err(),
301 DateRangeErrorKind::Negative.into()
302 );
303 }
304
305 #[test]
306 fn from_date() {
307 assert_eq!(Date::from_date(date!(1980-01-01)).unwrap(), Date::MIN);
308 assert_eq!(Date::from_date(date!(1980-01-01)).unwrap(), Date::MIN);
309 assert_eq!(
311 Date::from_date(date!(2002-11-26)).unwrap(),
312 Date::new(0b0010_1101_0111_1010).unwrap()
313 );
314 assert_eq!(
316 Date::from_date(date!(2018-11-17)).unwrap(),
317 Date::new(0b0100_1101_0111_0001).unwrap()
318 );
319 assert_eq!(Date::from_date(date!(2107-12-31)).unwrap(), Date::MAX);
320 assert_eq!(Date::from_date(date!(2107-12-31)).unwrap(), Date::MAX);
321 }
322
323 #[test]
324 fn from_date_with_too_big_date() {
325 assert_eq!(
326 Date::from_date(date!(2108-01-01)).unwrap_err(),
327 DateRangeErrorKind::Overflow.into()
328 );
329 }
330
331 #[test]
332 fn is_valid() {
333 assert!(Date::MIN.is_valid());
334 assert!(Date::new(0b0010_1101_0111_1010).unwrap().is_valid());
336 assert!(Date::new(0b0100_1101_0111_0001).unwrap().is_valid());
338 assert!(Date::MAX.is_valid());
339 }
340
341 #[test]
342 fn is_valid_with_invalid_date() {
343 assert!(!unsafe { Date::new_unchecked(0b0000_0000_0010_0000) }.is_valid());
345 assert!(!unsafe { Date::new_unchecked(0b0000_0000_0101_1110) }.is_valid());
347 assert!(!unsafe { Date::new_unchecked(0b0000_0000_0000_0001) }.is_valid());
349 assert!(!unsafe { Date::new_unchecked(0b0000_0001_1010_0001) }.is_valid());
351 }
352
353 #[test]
354 fn to_raw() {
355 assert_eq!(Date::MIN.to_raw(), 0b0000_0000_0010_0001);
356 assert_eq!(
358 Date::new(0b0010_1101_0111_1010).unwrap().to_raw(),
359 0b0010_1101_0111_1010
360 );
361 assert_eq!(
363 Date::new(0b0100_1101_0111_0001).unwrap().to_raw(),
364 0b0100_1101_0111_0001
365 );
366 assert_eq!(Date::MAX.to_raw(), 0b1111_1111_1001_1111);
367 }
368
369 #[test]
370 const fn to_raw_is_const_fn() {
371 const _: u16 = Date::MIN.to_raw();
372 }
373
374 #[test]
375 fn year() {
376 assert_eq!(Date::MIN.year(), 1980);
377 assert_eq!(Date::new(0b0010_1101_0111_1010).unwrap().year(), 2002);
379 assert_eq!(Date::new(0b0100_1101_0111_0001).unwrap().year(), 2018);
381 assert_eq!(Date::MAX.year(), 2107);
382 }
383
384 #[test]
385 const fn year_is_const_fn() {
386 const _: u16 = Date::MIN.year();
387 }
388
389 #[test]
390 fn month() {
391 assert_eq!(Date::MIN.month(), Month::January);
392 assert_eq!(
394 Date::new(0b0010_1101_0111_1010).unwrap().month(),
395 Month::November
396 );
397 assert_eq!(
399 Date::new(0b0100_1101_0111_0001).unwrap().month(),
400 Month::November
401 );
402 assert_eq!(Date::MAX.month(), Month::December);
403 }
404
405 #[test]
406 fn day() {
407 assert_eq!(Date::MIN.day(), 1);
408 assert_eq!(Date::new(0b0010_1101_0111_1010).unwrap().day(), 26);
410 assert_eq!(Date::new(0b0100_1101_0111_0001).unwrap().day(), 17);
412 assert_eq!(Date::MAX.day(), 31);
413 }
414
415 #[test]
416 fn default() {
417 assert_eq!(Date::default(), Date::MIN);
418 }
419}