1
2use crate::error::Error;
3use crate::epoch::Epoch;
4use crate::instant::Instant;
5
6#[cfg(feature = "serde")]
7use serde::{Serialize, Deserialize};
8
9pub trait Calendar {
18
19 fn is_gregorian() -> bool;
22
23 #[must_use]
25 fn name() -> &'static str {
26 if <Self as Calendar>::is_gregorian() {
27 "Gregorian"
28 } else {
29 "Julian"
30 }
31 }
32
33 #[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 #[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 #[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 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 m0 = (m0 + 10) % 12;
76
77 let y: i64 = i64::from(year) - m0/10;
80
81 let mut day = {
83 365*y
84
85 + y/4
87
88 + (y>>63)
94
95 + (m0*306 + 5)/10
98
99 + d0
101 };
102
103 if <Self as Calendar>::is_gregorian() {
104 day = day
105 - y/100
107 + y/400;
109 }
110
111 Ok(day - 306)
113 }
114
115 #[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 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 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 let mut offset_year: i64 = (10_000 * day_number + 14780) / days_in_year_times_10000;
165
166 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 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 #[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 let dnj = Julian::day_number(1582,10,5).unwrap();
243 let dng = Gregorian::day_number(1582,10,15).unwrap();
244 assert_eq!(dnj - 2, dng);
246
247 let dnj = Julian::day_number(-4713,1,1).unwrap();
253 let dng = Gregorian::day_number(-4714,11,24).unwrap();
254 assert_eq!(dnj - 2, dng);
256
257 }
263
264 #[test]
265 fn test_calendar_gregorian_day_numbers() {
266 crate::setup_logging();
267
268 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 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 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 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 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 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 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 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 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 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 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 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 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 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}