rust_openttd_admin/types/
date.rs

1// Much of this code is based on https://github.com/OpenTTD/OpenTTD/blob/master/src/date.cpp.
2
3use 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
433/// The number of days till the last day.
434lazy_static! {
435    static ref MAX_DAY: u32 = (days_till!(MAX_YEAR + 1) - 1);
436}
437
438/// An OpenTTD date.
439#[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    /// Returns the OpenTTD value this date represents.
473    pub fn to_openttd_date(self) -> u32 {
474        self.0
475    }
476
477    /// Returns the date this OpenTTD value represents.
478    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    /// Convert a date to a year, month and day. The year will range from 0 to
487    /// 5.000.000, the month from 0 to 11 and the day from 0 to 31.
488    pub fn to_ymd(self) -> (u32, u32, u32) {
489        let days = self.to_openttd_date();
490
491        /* Year determination in multiple steps to account for leap
492         * years. First do the large steps, then the smaller ones.
493         */
494
495        /* There are 97 leap years in 400 years */
496        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            /* There are 25 leap years in the first 100 years after
501             * every 400th year, as every 400th year is a leap year */
502            yr += 100;
503            rem -= DAYS_IN_YEAR * 100 + 25;
504
505            /* There are 24 leap years in the next couple of 100 years */
506            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            /* The first 4 year of the century are not always a leap year */
512            yr += 4;
513            rem -= DAYS_IN_YEAR * 4;
514        }
515
516        /* There is 1 leap year every 4 years */
517        yr += 4 * (rem / (DAYS_IN_YEAR * 4 + 1));
518        rem %= DAYS_IN_YEAR * 4 + 1;
519
520        /* The last (max 3) years to account for; the first one
521         * can be, but is not necessarily a leap year */
522        while rem >= Date::days_in_year(yr) {
523            rem -= Date::days_in_year(yr);
524            yr += 1;
525        }
526
527        /* Skip the 29th of February in non-leap years */
528        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    /// Convert a year, month and day to a date. The year should be in the
537    /// range 0 to 5.000.000, and the date should exist.
538    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        /* Day-offset in a leap year */
548        let mut days = accumulated_days_for_month(month).unwrap() + day - 1;
549
550        /* Account for the missing of the 29th of February in non-leap years */
551        if !Date::is_leap_year(year) && days >= ACCUM_MAR {
552            days -= 1;
553        }
554
555        Ok(Date(days_till!(year) + days))
556    }
557
558    /// Returns true if the year is a leap year.
559    fn is_leap_year(yr: u32) -> bool {
560        yr % 4 == 0 && (yr % 100 != 0 || yr % 400 == 0)
561    }
562
563    /// Returns the number of days in the year
564    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        /// The inner representation should not be tested, just that it converts
637        /// losslessly.
638        #[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 whether ymd conversion is lossless
651        #[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}