/rust/registry/src/index.crates.io-1949cf8c6b5b557f/calendrical_calculations-0.1.2/src/iso.rs
Line | Count | Source |
1 | | // This file is part of ICU4X. |
2 | | // |
3 | | // The contents of this file implement algorithms from Calendrical Calculations |
4 | | // by Reingold & Dershowitz, Cambridge University Press, 4th edition (2018), |
5 | | // which have been released as Lisp code at <https://github.com/EdReingold/calendar-code2/> |
6 | | // under the Apache-2.0 license. Accordingly, this file is released under |
7 | | // the Apache License, Version 2.0 which can be found at the calendrical_calculations |
8 | | // package root or at http://www.apache.org/licenses/LICENSE-2.0. |
9 | | |
10 | | use crate::helpers::{i64_to_i32, I32CastError}; |
11 | | use crate::rata_die::RataDie; |
12 | | |
13 | | // The Gregorian epoch is equivalent to first day in fixed day measurement |
14 | | const EPOCH: RataDie = RataDie::new(1); |
15 | | |
16 | | /// Whether or not `year` is a leap year |
17 | 0 | pub fn is_leap_year(year: i32) -> bool { |
18 | 0 | year % 4 == 0 && (year % 400 == 0 || year % 100 != 0) |
19 | 0 | } |
20 | | |
21 | | // Fixed is day count representation of calendars starting from Jan 1st of year 1. |
22 | | // The fixed calculations algorithms are from the Calendrical Calculations book. |
23 | | // |
24 | | /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/1ee51ecfaae6f856b0d7de3e36e9042100b4f424/calendar.l#L1167-L1189> |
25 | 0 | pub fn fixed_from_iso(year: i32, month: u8, day: u8) -> RataDie { |
26 | 0 | let prev_year = (year as i64) - 1; |
27 | | // Calculate days per year |
28 | 0 | let mut fixed: i64 = (EPOCH.to_i64_date() - 1) + 365 * prev_year; |
29 | | // Calculate leap year offset |
30 | 0 | let offset = prev_year.div_euclid(4) - prev_year.div_euclid(100) + prev_year.div_euclid(400); |
31 | | // Adjust for leap year logic |
32 | 0 | fixed += offset; |
33 | | // Days of current year |
34 | 0 | fixed += (367 * (month as i64) - 362).div_euclid(12); |
35 | | // Leap year adjustment for the current year |
36 | 0 | fixed += if month <= 2 { |
37 | 0 | 0 |
38 | 0 | } else if is_leap_year(year) { |
39 | 0 | -1 |
40 | | } else { |
41 | 0 | -2 |
42 | | }; |
43 | | // Days passed in current month |
44 | 0 | fixed += day as i64; |
45 | 0 | RataDie::new(fixed) |
46 | 0 | } |
47 | | |
48 | | /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/1ee51ecfaae6f856b0d7de3e36e9042100b4f424/calendar.l#L1191-L1217> |
49 | 0 | pub(crate) fn iso_year_from_fixed(date: RataDie) -> i64 { |
50 | | // Shouldn't overflow because it's not possbile to construct extreme values of RataDie |
51 | 0 | let date = date - EPOCH; |
52 | | |
53 | | // 400 year cycles have 146097 days |
54 | 0 | let (n_400, date) = (date.div_euclid(146097), date.rem_euclid(146097)); |
55 | | |
56 | | // 100 year cycles have 36524 days |
57 | 0 | let (n_100, date) = (date.div_euclid(36524), date.rem_euclid(36524)); |
58 | | |
59 | | // 4 year cycles have 1461 days |
60 | 0 | let (n_4, date) = (date.div_euclid(1461), date.rem_euclid(1461)); |
61 | | |
62 | 0 | let n_1 = date.div_euclid(365); |
63 | | |
64 | 0 | let year = 400 * n_400 + 100 * n_100 + 4 * n_4 + n_1; |
65 | | |
66 | 0 | if n_100 == 4 || n_1 == 4 { |
67 | 0 | year |
68 | | } else { |
69 | 0 | year + 1 |
70 | | } |
71 | 0 | } |
72 | | |
73 | 0 | fn iso_new_year(year: i32) -> RataDie { |
74 | 0 | fixed_from_iso(year, 1, 1) |
75 | 0 | } |
76 | | |
77 | | /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/1ee51ecfaae6f856b0d7de3e36e9042100b4f424/calendar.l#L1525-L1540> |
78 | 0 | pub fn iso_from_fixed(date: RataDie) -> Result<(i32, u8, u8), I32CastError> { |
79 | 0 | let year = iso_year_from_fixed(date); |
80 | 0 | let year = i64_to_i32(year)?; |
81 | | // Calculates the prior days of the adjusted year, then applies a correction based on leap year conditions for the correct ISO date conversion. |
82 | 0 | let prior_days = date - iso_new_year(year); |
83 | 0 | let correction = if date < fixed_from_iso(year, 3, 1) { |
84 | 0 | 0 |
85 | 0 | } else if is_leap_year(year) { |
86 | 0 | 1 |
87 | | } else { |
88 | 0 | 2 |
89 | | }; |
90 | 0 | let month = (12 * (prior_days + correction) + 373).div_euclid(367) as u8; // in 1..12 < u8::MAX |
91 | 0 | let day = (date - fixed_from_iso(year, month, 1) + 1) as u8; // <= days_in_month < u8::MAX |
92 | 0 | Ok((year, month, day)) |
93 | 0 | } |