astrotime/
calendar.rs

1
2use crate::error::Error;
3use crate::epoch::Epoch;
4use crate::instant::Instant;
5
6#[cfg(feature = "serde")]
7use serde::{Serialize, Deserialize};
8
9/// This specifies traditional Calendar settings that use the traditional 12 months
10/// and have leap years. This is implemented for `Gregorian` and `Julian`. It does
11/// not handle more esoteric calendars.
12///
13/// Zero and Negative years are handled in accordance with ISO 8601, such that
14/// year 0 is 1 B.C., and year -1 is 2 B.C., etc. In general:
15/// * _n_ B.C. is represented by year 1-_n_
16/// * Year _-y_ represents year _y_+1 B.C. (for positive y).
17pub trait Calendar {
18
19    /// If the calendar is Gregorian (since we only handle Julian and Gregorian, this
20    /// is all that needs to be defined to differentiate them)
21    fn is_gregorian() -> bool;
22
23    /// The name of the calendar
24    #[must_use]
25    fn name() -> &'static str {
26        if <Self as Calendar>::is_gregorian() {
27            "Gregorian"
28        } else {
29            "Julian"
30        }
31    }
32
33    /// Epoch this calendar starts from
34    #[must_use]
35    fn epoch() -> Instant {
36        if <Self as Calendar>::is_gregorian() {
37            Epoch::GregorianCalendar.as_instant()
38        } else {
39            Epoch::JulianCalendar.as_instant()
40        }
41    }
42
43    /// Answers the question: is this year a leap year?
44    #[must_use]
45    fn is_year_leap(year: i32) -> bool {
46        if <Self as Calendar>::is_gregorian() {
47            (year%4==0) && ((year%100!=0) || (year%400==0))
48        } else {
49            year%4==0
50        }
51    }
52
53    /// Converts a `year`, `month` and (month)`day` into a day number which counts the number
54    /// of days from the start of the calendar epoch
55    ///
56    /// `year` may range from -2147483648 .. 2147483647 covering every possible i32.
57    ///
58    /// `month` must be in the range 1 .. 12
59    ///
60    /// `day` may be out of the normal bounds. It will be adjusted.
61    ///
62    /// # Errors
63    ///
64    /// Will return a `Error::RangeError` if `month` or `day` are out of range.
65    #[allow(clippy::manual_range_contains)]
66    fn day_number(year: i32, month: u8, day: i64) -> Result<i64, Error> {
67        if month<1 || month>12 { return Err(Error::RangeError); }
68
69        // Zero basis days and months
70        let mut m0 = i64::from(month).checked_sub(1).ok_or(Error::RangeError)?;
71        let d0 = day.checked_sub(1).ok_or(Error::RangeError)?;
72
73        // Change our zero point to 1 B.C. (year 0) March 1st (Feb now being month 11 pushing the
74        // leap year day to the very end of the year)
75        m0 = (m0 + 10) % 12;
76
77        // Use a larger type for years so we can handle the entire range without
78        // numerical overflows.  Also adjust for starting on March 1st.
79        let y: i64 = i64::from(year) - m0/10;
80
81        // Main calculation
82        let mut day = {
83            365*y
84
85            // leap year first approximation
86                + y/4
87
88            // For dates before 1 B.C. (year 0) March 1st, we need to subtract 1 more day since
89            // 1 B.C. (year 0) is a leap year that our calculations above didn't catch.
90            // (To be branchless, we just use the sign bit from the year; i64 type stays negative)
91
92                //- (if y<0 { 1 } else { 0 })
93                + (y>>63)
94
95            // The number of days between march 1st and the start of the mth month
96            // after march (brilliant!) (306 is the days in the 10 months from mar-dec)
97                + (m0*306 + 5)/10
98
99            // and dont forget the day of the month itself (zero basis)
100                + d0
101        };
102
103        if <Self as Calendar>::is_gregorian() {
104            day = day
105            // leap year second approximation, Gregorian
106                - y/100
107            // leap year third approximation, Gregorian
108                + y/400;
109        }
110
111        // revert back to january 1 basis (we were at march 1st, we need to move ahead)
112        Ok(day - 306)
113    }
114
115    /// Converts a day number which counts the number of days from the start of
116    /// the calendar epoch into a year, month and day
117    ///
118    /// For the Gregorian calendar, `day_number` must fall in the range
119    /// `-784_352_296_671` .. `784_352_295_938`
120    /// which represent calendar dates `-2147483648-01-01` ..  `2147483647-12-31`
121    /// respectively.
122    ///
123    /// For the Julian calendar, `day_number` must fall in the range
124    /// `-784_368_402_798` .. `784_368_402_065`
125    /// which represent calendar dates `-2147483648-01-01` ..  `2147483647-12-31`
126    /// respectively.
127    ///
128    /// Returns a (year, month, day)
129    ///
130    /// # Errors
131    ///
132    /// Will return a `Error::RangeError` if `day_number` is out of range.
133    ///
134    /// # Panics
135    ///
136    /// Panics on assertions that should only fail if there is a bug.
137    #[allow(clippy::cast_sign_loss)]
138    #[allow(clippy::cast_possible_truncation)]
139    fn from_day_number(mut day_number: i64) -> Result<(i32, u8, u8), Error> {
140
141        // These extreme values have been checked, so we don't have to use
142        // checked math operations in the main function (which are slower)
143        let (min,max) = if <Self as Calendar>::is_gregorian() {
144            (-784_352_296_671, 784_352_295_938)
145        } else {
146            (-784_368_402_798,784_368_402_065)
147        };
148        if day_number < min || day_number > max {
149            return Err(Error::RangeError);
150        }
151
152        // Change to a March 1st basis, year 0 (back about 9 months from the epoch)
153        // The leap day will be at the very end rather than somewhere annoyingly in the
154        // middle.
155        day_number += 306;
156
157        let days_in_year_times_10000 = if <Self as Calendar>::is_gregorian() {
158            365_2425
159        } else {
160            365_2500
161        };
162
163        // Calculate the year (march 1st basis)
164        let mut offset_year: i64 = (10_000 * day_number + 14780) / days_in_year_times_10000;
165
166        // Caculate the remaining days
167        let calc_remaining_days = |day_number: i64, offset_year: i64| -> i64 {
168            let zeroeth_year = offset_year>>63;
169            let mut remaining_days = day_number - 365*offset_year - offset_year/4 - zeroeth_year;
170            if <Self as Calendar>::is_gregorian() {
171                remaining_days = remaining_days + offset_year/100 - offset_year/400;
172            }
173            remaining_days
174        };
175        let mut remaining_days = calc_remaining_days(day_number, offset_year);
176        if remaining_days < 0 {
177            offset_year -= 1;
178            remaining_days = calc_remaining_days(day_number, offset_year);
179        }
180
181        let offset_month = (100*remaining_days + 52)/3060;
182
183        // come back from our march-1st basis
184
185        let year = offset_year + (offset_month + 2)/12;
186
187        let month = (offset_month + 2)%12;
188        assert!(month >= 0);
189        assert!(month < 12);
190
191        let day = remaining_days - (offset_month*306 + 5)/10;
192        assert!(day < 31);
193        assert!(day >= 0);
194
195        Ok((year as i32, (month+1) as u8, (day+1) as u8))
196    }
197
198    /// Returns the number of days in a given month (year is required for leap year calculations)
199    #[must_use]
200    fn month_days(month: u8, year: i32) -> u8 {
201        assert!(month>=1);
202        assert!(month<=12);
203        match month {
204            1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
205            2 => if <Self as Calendar>::is_year_leap(year + i32::from((month-1)/12)) { 29 } else { 28 },
206            4 | 6 | 9 | 11 => 30,
207            _ => unreachable!()
208        }
209    }
210}
211
212#[derive(Debug, Clone, Copy)]
213#[cfg_attr(feature ="serde", derive(Serialize, Deserialize))]
214pub struct Julian;
215
216impl Calendar for Julian {
217    fn is_gregorian() -> bool {
218        false
219    }
220}
221
222#[derive(Debug, Clone, Copy)]
223#[cfg_attr(feature ="serde", derive(Serialize, Deserialize))]
224pub struct Gregorian;
225
226impl Calendar for Gregorian {
227    fn is_gregorian() -> bool {
228        true
229    }
230}
231
232#[cfg(test)]
233mod test {
234    use super::{Calendar, Gregorian, Julian};
235
236    #[test]
237    fn test_gregorian_julian_date_matches() {
238        crate::setup_logging();
239
240        // JULIAN: October 5, 1582     (JD 2299161)
241        // GREGORIAN: October 15, 1582 (JD 2299161)
242        let dnj = Julian::day_number(1582,10,5).unwrap();
243        let dng = Gregorian::day_number(1582,10,15).unwrap();
244        // Julian day numbers are offset from Gregorian day numbers by 2
245        assert_eq!(dnj - 2, dng);
246
247        // The Julian Epoch (Julian Day 0) should match
248        // [Wikipedia](https://en.wikipedia.org/wiki/Julian_day)
249        // January 1, 4713 BCE, proleptic Julian calendar
250        // November 24, 4714 BCE, in the proleptic Gregorian calendar)
251
252        let dnj = Julian::day_number(-4713,1,1).unwrap();
253        let dng = Gregorian::day_number(-4714,11,24).unwrap();
254        // Julian day numbers are offset from Gregorian day numbers by 2
255        assert_eq!(dnj - 2, dng);
256
257        // Note: julian day numbers
258        //   1 Jan 4713 BCE (Julian Calendar) -- 0
259        //   1 Jan 1 CE (Julian Calendar) -- 1721424
260        //   1 Jan 2000 CE (Julian Calendar) -- 2451558
261        //   1 Jan 2000 CE (Gregorian Calendar) -- 2451545
262    }
263
264    #[test]
265    fn test_calendar_gregorian_day_numbers() {
266        crate::setup_logging();
267
268        // Epoch (year 1)
269        let dn = Gregorian::day_number(1,1,1).unwrap();
270        assert_eq!(dn, 0);
271        let (y,m,d) = Gregorian::from_day_number(0).unwrap();
272        assert_eq!( (y,m,d), (1,1,1) );
273
274        // One day earlier
275        let dn = Gregorian::day_number(0,12,31).unwrap();
276        assert_eq!(dn, -1);
277        let (y,m,d) = Gregorian::from_day_number(-1).unwrap();
278        assert_eq!( (y,m,d), (0,12,31) );
279
280        // Days around the leap day in 1 BCE
281        let mar1 = Gregorian::day_number(0,3,1).unwrap();
282        assert_eq!(mar1, -306);
283        let (y,m,d) = Gregorian::from_day_number(mar1).unwrap();
284        assert_eq!( (y,m,d), (0,3,1) );
285
286        let feb29 = Gregorian::day_number(0,2,29).unwrap();
287        assert_eq!(feb29, -307);
288        let (y,m,d) = Gregorian::from_day_number(feb29).unwrap();
289        assert_eq!( (y,m,d), (0,2,29) );
290
291        let feb28 = Gregorian::day_number(0,2,28).unwrap();
292        assert_eq!(feb28, -308);
293        let (y,m,d) = Gregorian::from_day_number(feb28).unwrap();
294        assert_eq!( (y,m,d), (0,2,28) );
295
296        // Epoch (year 5)
297        let dn = Gregorian::day_number(4,1,1).unwrap();
298        assert_eq!(dn, 365*3);
299        let (y,m,d) = Gregorian::from_day_number(dn).unwrap();
300        assert_eq!( (y,m,d), (4,1,1) );
301
302        // year 1582
303        let dn = Gregorian::day_number(1582,1,1).unwrap();
304        assert_eq!(dn, 365*(1582-1) + (1582-1)/4 - (1582-1)/100 + (1582-1)/400);
305        let (y,m,d) = Gregorian::from_day_number(dn).unwrap();
306        assert_eq!( (y,m,d), (1582,1,1) );
307
308        // year 1582,  1st of march
309        let dn = Gregorian::day_number(1582,3,1).unwrap();
310        assert_eq!(dn, 365*(1582-1) + (1582-1)/4 - (1582-1)/100 + (1582-1)/400 + 31 + 28);
311        let (y,m,d) = Gregorian::from_day_number(dn).unwrap();
312        assert_eq!( (y,m,d), (1582,3,1) );
313
314        // year 1582, 15th of october
315        let dn = Gregorian::day_number(1582,10,15).unwrap();
316        assert_eq!(dn, 365*(1582-1) + (1582-1)/4 - (1582-1)/100 + (1582-1)/400
317                   + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 14);
318        let (y,m,d) = Gregorian::from_day_number(dn).unwrap();
319        assert_eq!( (y,m,d), (1582,10,15) );
320
321        // Year 2000
322        let dn = Gregorian::day_number(2000,1,1).unwrap();
323        assert_eq!(dn, 730119);
324        let (y,m,d) = Gregorian::from_day_number(730119).unwrap();
325        assert_eq!( (y,m,d) , (2000,1,1) );
326
327        // Minimum
328        let dn = Gregorian::day_number(-2147483648,1,1).unwrap();
329        assert_eq!(dn, -784_352_296_671);
330
331        let (y,m,d) = Gregorian::from_day_number(-784_352_296_671).unwrap();
332        assert_eq!(y,-2147483648);
333        assert_eq!(m,1);
334        assert_eq!(d,1);
335
336        // Maximum
337        let dn = Gregorian::day_number(2147483647,12,31).unwrap();
338        assert_eq!(dn, 784_352_295_938);
339
340        let (y,m,d) = Gregorian::from_day_number(784_352_295_938).unwrap();
341        assert_eq!(y,2147483647);
342        assert_eq!(m,12);
343        assert_eq!(d,31);
344    }
345
346    #[test]
347    fn test_calendar_julian_day_numbers() {
348        crate::setup_logging();
349
350        // Epoch (year 1)
351        let dn = Julian::day_number(1,1,1).unwrap();
352        assert_eq!(dn, 0);
353        let (y,m,d) = Julian::from_day_number(0).unwrap();
354        assert_eq!(y,1);
355        assert_eq!(m,1);
356        assert_eq!(d,1);
357
358        // Year 2000
359        let dn = Julian::day_number(2000,1,1).unwrap();
360        assert_eq!(dn, 730134);
361        let (y,m,d) = Julian::from_day_number(dn).unwrap();
362        assert_eq!(y,2000);
363        assert_eq!(m,1);
364        assert_eq!(d,1);
365
366        // Minimum
367        let dn = Julian::day_number(-2147483648,1,1).unwrap();
368        assert_eq!(dn, -784_368_402_798);
369        let (y,m,d) = Julian::from_day_number(dn).unwrap();
370        assert_eq!(y,-2147483648);
371        assert_eq!(m,1);
372        assert_eq!(d,1);
373
374        // Maximum
375        let dn = Julian::day_number(2147483647,12,31).unwrap();
376        assert_eq!(dn, 784_368_402_065);
377        let (y,m,d) = Julian::from_day_number(dn).unwrap();
378        assert_eq!(y,2147483647);
379        assert_eq!(m,12);
380        assert_eq!(d,31);
381    }
382}