1use failure::Fail;
4use lazy_static::*;
5use serde::de::{Deserialize, Deserializer};
6use serde::ser::{Serialize, Serializer};
7
8const DAYS_IN_YEAR: u32 = 365;
9const DAYS_IN_LEAP_YEAR: u32 = 366;
10const MAX_YEAR: u32 = 5_000_000;
11
12macro_rules! leap_years_till {
13 ($year:expr) => {
14 if $year == 0 {
15 0
16 } else {
17 ($year - 1) / 4 - ($year - 1) / 100 + ($year - 1) / 400 + 1
18 }
19 };
20}
21
22macro_rules! days_till {
23 ($year:expr) => {
24 DAYS_IN_YEAR * $year + leap_years_till!($year)
25 };
26}
27
28macro_rules! M {
29 ($a:expr, $b:expr) => {
30 (($a << 5) | $b)
31 };
32}
33const MONTH_DATE_FROM_YEAR_DAY: [u32; 366] = [
34 M!(0, 1),
35 M!(0, 2),
36 M!(0, 3),
37 M!(0, 4),
38 M!(0, 5),
39 M!(0, 6),
40 M!(0, 7),
41 M!(0, 8),
42 M!(0, 9),
43 M!(0, 10),
44 M!(0, 11),
45 M!(0, 12),
46 M!(0, 13),
47 M!(0, 14),
48 M!(0, 15),
49 M!(0, 16),
50 M!(0, 17),
51 M!(0, 18),
52 M!(0, 19),
53 M!(0, 20),
54 M!(0, 21),
55 M!(0, 22),
56 M!(0, 23),
57 M!(0, 24),
58 M!(0, 25),
59 M!(0, 26),
60 M!(0, 27),
61 M!(0, 28),
62 M!(0, 29),
63 M!(0, 30),
64 M!(0, 31),
65 M!(1, 1),
66 M!(1, 2),
67 M!(1, 3),
68 M!(1, 4),
69 M!(1, 5),
70 M!(1, 6),
71 M!(1, 7),
72 M!(1, 8),
73 M!(1, 9),
74 M!(1, 10),
75 M!(1, 11),
76 M!(1, 12),
77 M!(1, 13),
78 M!(1, 14),
79 M!(1, 15),
80 M!(1, 16),
81 M!(1, 17),
82 M!(1, 18),
83 M!(1, 19),
84 M!(1, 20),
85 M!(1, 21),
86 M!(1, 22),
87 M!(1, 23),
88 M!(1, 24),
89 M!(1, 25),
90 M!(1, 26),
91 M!(1, 27),
92 M!(1, 28),
93 M!(1, 29),
94 M!(2, 1),
95 M!(2, 2),
96 M!(2, 3),
97 M!(2, 4),
98 M!(2, 5),
99 M!(2, 6),
100 M!(2, 7),
101 M!(2, 8),
102 M!(2, 9),
103 M!(2, 10),
104 M!(2, 11),
105 M!(2, 12),
106 M!(2, 13),
107 M!(2, 14),
108 M!(2, 15),
109 M!(2, 16),
110 M!(2, 17),
111 M!(2, 18),
112 M!(2, 19),
113 M!(2, 20),
114 M!(2, 21),
115 M!(2, 22),
116 M!(2, 23),
117 M!(2, 24),
118 M!(2, 25),
119 M!(2, 26),
120 M!(2, 27),
121 M!(2, 28),
122 M!(2, 29),
123 M!(2, 30),
124 M!(2, 31),
125 M!(3, 1),
126 M!(3, 2),
127 M!(3, 3),
128 M!(3, 4),
129 M!(3, 5),
130 M!(3, 6),
131 M!(3, 7),
132 M!(3, 8),
133 M!(3, 9),
134 M!(3, 10),
135 M!(3, 11),
136 M!(3, 12),
137 M!(3, 13),
138 M!(3, 14),
139 M!(3, 15),
140 M!(3, 16),
141 M!(3, 17),
142 M!(3, 18),
143 M!(3, 19),
144 M!(3, 20),
145 M!(3, 21),
146 M!(3, 22),
147 M!(3, 23),
148 M!(3, 24),
149 M!(3, 25),
150 M!(3, 26),
151 M!(3, 27),
152 M!(3, 28),
153 M!(3, 29),
154 M!(3, 30),
155 M!(4, 1),
156 M!(4, 2),
157 M!(4, 3),
158 M!(4, 4),
159 M!(4, 5),
160 M!(4, 6),
161 M!(4, 7),
162 M!(4, 8),
163 M!(4, 9),
164 M!(4, 10),
165 M!(4, 11),
166 M!(4, 12),
167 M!(4, 13),
168 M!(4, 14),
169 M!(4, 15),
170 M!(4, 16),
171 M!(4, 17),
172 M!(4, 18),
173 M!(4, 19),
174 M!(4, 20),
175 M!(4, 21),
176 M!(4, 22),
177 M!(4, 23),
178 M!(4, 24),
179 M!(4, 25),
180 M!(4, 26),
181 M!(4, 27),
182 M!(4, 28),
183 M!(4, 29),
184 M!(4, 30),
185 M!(4, 31),
186 M!(5, 1),
187 M!(5, 2),
188 M!(5, 3),
189 M!(5, 4),
190 M!(5, 5),
191 M!(5, 6),
192 M!(5, 7),
193 M!(5, 8),
194 M!(5, 9),
195 M!(5, 10),
196 M!(5, 11),
197 M!(5, 12),
198 M!(5, 13),
199 M!(5, 14),
200 M!(5, 15),
201 M!(5, 16),
202 M!(5, 17),
203 M!(5, 18),
204 M!(5, 19),
205 M!(5, 20),
206 M!(5, 21),
207 M!(5, 22),
208 M!(5, 23),
209 M!(5, 24),
210 M!(5, 25),
211 M!(5, 26),
212 M!(5, 27),
213 M!(5, 28),
214 M!(5, 29),
215 M!(5, 30),
216 M!(6, 1),
217 M!(6, 2),
218 M!(6, 3),
219 M!(6, 4),
220 M!(6, 5),
221 M!(6, 6),
222 M!(6, 7),
223 M!(6, 8),
224 M!(6, 9),
225 M!(6, 10),
226 M!(6, 11),
227 M!(6, 12),
228 M!(6, 13),
229 M!(6, 14),
230 M!(6, 15),
231 M!(6, 16),
232 M!(6, 17),
233 M!(6, 18),
234 M!(6, 19),
235 M!(6, 20),
236 M!(6, 21),
237 M!(6, 22),
238 M!(6, 23),
239 M!(6, 24),
240 M!(6, 25),
241 M!(6, 26),
242 M!(6, 27),
243 M!(6, 28),
244 M!(6, 29),
245 M!(6, 30),
246 M!(6, 31),
247 M!(7, 1),
248 M!(7, 2),
249 M!(7, 3),
250 M!(7, 4),
251 M!(7, 5),
252 M!(7, 6),
253 M!(7, 7),
254 M!(7, 8),
255 M!(7, 9),
256 M!(7, 10),
257 M!(7, 11),
258 M!(7, 12),
259 M!(7, 13),
260 M!(7, 14),
261 M!(7, 15),
262 M!(7, 16),
263 M!(7, 17),
264 M!(7, 18),
265 M!(7, 19),
266 M!(7, 20),
267 M!(7, 21),
268 M!(7, 22),
269 M!(7, 23),
270 M!(7, 24),
271 M!(7, 25),
272 M!(7, 26),
273 M!(7, 27),
274 M!(7, 28),
275 M!(7, 29),
276 M!(7, 30),
277 M!(7, 31),
278 M!(8, 1),
279 M!(8, 2),
280 M!(8, 3),
281 M!(8, 4),
282 M!(8, 5),
283 M!(8, 6),
284 M!(8, 7),
285 M!(8, 8),
286 M!(8, 9),
287 M!(8, 10),
288 M!(8, 11),
289 M!(8, 12),
290 M!(8, 13),
291 M!(8, 14),
292 M!(8, 15),
293 M!(8, 16),
294 M!(8, 17),
295 M!(8, 18),
296 M!(8, 19),
297 M!(8, 20),
298 M!(8, 21),
299 M!(8, 22),
300 M!(8, 23),
301 M!(8, 24),
302 M!(8, 25),
303 M!(8, 26),
304 M!(8, 27),
305 M!(8, 28),
306 M!(8, 29),
307 M!(8, 30),
308 M!(9, 1),
309 M!(9, 2),
310 M!(9, 3),
311 M!(9, 4),
312 M!(9, 5),
313 M!(9, 6),
314 M!(9, 7),
315 M!(9, 8),
316 M!(9, 9),
317 M!(9, 10),
318 M!(9, 11),
319 M!(9, 12),
320 M!(9, 13),
321 M!(9, 14),
322 M!(9, 15),
323 M!(9, 16),
324 M!(9, 17),
325 M!(9, 18),
326 M!(9, 19),
327 M!(9, 20),
328 M!(9, 21),
329 M!(9, 22),
330 M!(9, 23),
331 M!(9, 24),
332 M!(9, 25),
333 M!(9, 26),
334 M!(9, 27),
335 M!(9, 28),
336 M!(9, 29),
337 M!(9, 30),
338 M!(9, 31),
339 M!(10, 1),
340 M!(10, 2),
341 M!(10, 3),
342 M!(10, 4),
343 M!(10, 5),
344 M!(10, 6),
345 M!(10, 7),
346 M!(10, 8),
347 M!(10, 9),
348 M!(10, 10),
349 M!(10, 11),
350 M!(10, 12),
351 M!(10, 13),
352 M!(10, 14),
353 M!(10, 15),
354 M!(10, 16),
355 M!(10, 17),
356 M!(10, 18),
357 M!(10, 19),
358 M!(10, 20),
359 M!(10, 21),
360 M!(10, 22),
361 M!(10, 23),
362 M!(10, 24),
363 M!(10, 25),
364 M!(10, 26),
365 M!(10, 27),
366 M!(10, 28),
367 M!(10, 29),
368 M!(10, 30),
369 M!(11, 1),
370 M!(11, 2),
371 M!(11, 3),
372 M!(11, 4),
373 M!(11, 5),
374 M!(11, 6),
375 M!(11, 7),
376 M!(11, 8),
377 M!(11, 9),
378 M!(11, 10),
379 M!(11, 11),
380 M!(11, 12),
381 M!(11, 13),
382 M!(11, 14),
383 M!(11, 15),
384 M!(11, 16),
385 M!(11, 17),
386 M!(11, 18),
387 M!(11, 19),
388 M!(11, 20),
389 M!(11, 21),
390 M!(11, 22),
391 M!(11, 23),
392 M!(11, 24),
393 M!(11, 25),
394 M!(11, 26),
395 M!(11, 27),
396 M!(11, 28),
397 M!(11, 29),
398 M!(11, 30),
399 M!(11, 31),
400];
401
402const ACCUM_JAN: u32 = 0;
403const ACCUM_FEB: u32 = ACCUM_JAN + 31;
404const ACCUM_MAR: u32 = ACCUM_FEB + 29;
405const ACCUM_APR: u32 = ACCUM_MAR + 31;
406const ACCUM_MAY: u32 = ACCUM_APR + 30;
407const ACCUM_JUN: u32 = ACCUM_MAY + 31;
408const ACCUM_JUL: u32 = ACCUM_JUN + 30;
409const ACCUM_AUG: u32 = ACCUM_JUL + 31;
410const ACCUM_SEP: u32 = ACCUM_AUG + 31;
411const ACCUM_OCT: u32 = ACCUM_SEP + 30;
412const ACCUM_NOV: u32 = ACCUM_OCT + 31;
413const ACCUM_DEC: u32 = ACCUM_NOV + 30;
414
415fn accumulated_days_for_month(month: u32) -> Result<u32, DateError> {
416 match month {
417 0 => Ok(ACCUM_JAN),
418 1 => Ok(ACCUM_FEB),
419 2 => Ok(ACCUM_MAR),
420 3 => Ok(ACCUM_APR),
421 4 => Ok(ACCUM_MAY),
422 5 => Ok(ACCUM_JUN),
423 6 => Ok(ACCUM_JUL),
424 7 => Ok(ACCUM_AUG),
425 8 => Ok(ACCUM_SEP),
426 9 => Ok(ACCUM_OCT),
427 10 => Ok(ACCUM_NOV),
428 11 => Ok(ACCUM_DEC),
429 _ => Err(DateError::MonthOutOfRange { month }),
430 }
431}
432
433lazy_static! {
435 static ref MAX_DAY: u32 = (days_till!(MAX_YEAR + 1) - 1);
436}
437
438#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
440pub struct Date(u32);
441
442impl std::fmt::Display for Date {
443 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
444 let (y, m, d) = self.to_ymd();
445 write!(f, "{:04}-{:02}-{:02}", y, m + 1, d)
446 }
447}
448
449impl std::fmt::Debug for Date {
450 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
451 let (y, m, d) = self.to_ymd();
452 write!(f, "{:04}-{:02}-{:02}", y, m + 1, d)
453 }
454}
455
456#[derive(Copy, Clone, Eq, PartialEq, Debug, Fail)]
457pub enum DateError {
458 #[fail(display = "the date {} is out of range", date)]
459 DateOutOfRange { date: u32 },
460 #[fail(
461 display = "the day {} is out of range in the month {} of {}",
462 day, month, year
463 )]
464 DayOutOfRange { day: u32, month: u32, year: u32 },
465 #[fail(display = "month {} is out of range", month)]
466 MonthOutOfRange { month: u32 },
467 #[fail(display = "year {} is out of range", year)]
468 YearOutOfRange { year: u32 },
469}
470
471impl Date {
472 pub fn to_openttd_date(self) -> u32 {
474 self.0
475 }
476
477 pub fn from_openttd_date(date: u32) -> Result<Date, DateError> {
479 if date > *MAX_DAY {
480 Err(DateError::DateOutOfRange { date })
481 } else {
482 Ok(Date(date))
483 }
484 }
485
486 pub fn to_ymd(self) -> (u32, u32, u32) {
489 let days = self.to_openttd_date();
490
491 let mut yr = 400 * (days / (DAYS_IN_YEAR * 400 + 97));
497 let mut rem = days % (DAYS_IN_YEAR * 400 + 97);
498
499 if rem >= DAYS_IN_YEAR * 100 + 25 {
500 yr += 100;
503 rem -= DAYS_IN_YEAR * 100 + 25;
504
505 yr += 100 * (rem / (DAYS_IN_YEAR * 100 + 24));
507 rem %= DAYS_IN_YEAR * 100 + 24;
508 }
509
510 if !Date::is_leap_year(yr) && rem >= DAYS_IN_YEAR * 4 {
511 yr += 4;
513 rem -= DAYS_IN_YEAR * 4;
514 }
515
516 yr += 4 * (rem / (DAYS_IN_YEAR * 4 + 1));
518 rem %= DAYS_IN_YEAR * 4 + 1;
519
520 while rem >= Date::days_in_year(yr) {
523 rem -= Date::days_in_year(yr);
524 yr += 1;
525 }
526
527 if !Date::is_leap_year(yr) && rem >= ACCUM_MAR - 1 {
529 rem += 1;
530 }
531
532 let x = MONTH_DATE_FROM_YEAR_DAY[rem as usize];
533 (yr, x >> 5, x & 0x1F)
534 }
535
536 pub fn from_ymd(year: u32, month: u32, day: u32) -> Result<Date, DateError> {
539 if year > MAX_YEAR {
540 return Err(DateError::YearOutOfRange { year });
541 } else if month > 11 {
542 return Err(DateError::MonthOutOfRange { month });
543 } else if day == 0 || day > Date::days_in_month(year, month).unwrap() {
544 return Err(DateError::DayOutOfRange { year, month, day });
545 }
546
547 let mut days = accumulated_days_for_month(month).unwrap() + day - 1;
549
550 if !Date::is_leap_year(year) && days >= ACCUM_MAR {
552 days -= 1;
553 }
554
555 Ok(Date(days_till!(year) + days))
556 }
557
558 fn is_leap_year(yr: u32) -> bool {
560 yr % 4 == 0 && (yr % 100 != 0 || yr % 400 == 0)
561 }
562
563 fn days_in_year(yr: u32) -> u32 {
565 if Date::is_leap_year(yr) {
566 DAYS_IN_LEAP_YEAR
567 } else {
568 DAYS_IN_YEAR
569 }
570 }
571
572 fn days_in_month(year: u32, month: u32) -> Result<u32, DateError> {
573 match month {
574 0 => Ok(31),
575 1 => {
576 if Date::is_leap_year(year) {
577 Ok(29)
578 } else {
579 Ok(28)
580 }
581 }
582 2 => Ok(31),
583 3 => Ok(30),
584 4 => Ok(31),
585 5 => Ok(30),
586 6 => Ok(31),
587 7 => Ok(31),
588 8 => Ok(30),
589 9 => Ok(31),
590 10 => Ok(30),
591 11 => Ok(31),
592 _ => Err(DateError::MonthOutOfRange { month }),
593 }
594 }
595}
596
597impl Serialize for Date {
598 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
599 where
600 S: Serializer,
601 {
602 serializer.serialize_u32(self.to_openttd_date())
603 }
604}
605
606impl<'de> Deserialize<'de> for Date {
607 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
608 where
609 D: Deserializer<'de>,
610 {
611 u32::deserialize(deserializer)
612 .and_then(|num| Date::from_openttd_date(num).map_err(serde::de::Error::custom))
613 }
614}
615
616#[cfg(test)]
617mod test {
618
619 use super::*;
620 use proptest::*;
621
622 #[test]
623 fn to_ymd() {
624 assert_eq!(Date::from_openttd_date(0).unwrap().to_ymd(), (0, 0, 1));
625 }
626
627 #[test]
628 fn from_ymd() {
629 assert_eq!(
630 Date::from_ymd(0, 0, 1).unwrap(),
631 Date::from_openttd_date(0).unwrap()
632 );
633 }
634
635 proptest! {
636 #[test]
639 fn openttd_conversion(openttd_date in 0..(days_till!(MAX_YEAR + 1) - 1)) {
640 assert_eq!(
641 Date::from_openttd_date(openttd_date)
642 .unwrap()
643 .to_openttd_date(),
644 openttd_date
645 );
646 }
647 }
648
649 proptest! {
650 #[test]
652 fn ymd_conversion(openttd_date in 0..(days_till!(MAX_YEAR + 1) - 1)) {
653 let date = Date::from_openttd_date(openttd_date).unwrap();
654 let (y, m, d) = date.to_ymd();
655 let new_date = Date::from_ymd(y, m, d).unwrap();
656 assert_eq!(new_date, date);
657 }
658 }
659}