/home/a220/proj/radnelac/src/calendar/holocene.rs
Line | Count | Source |
1 | | // This Source Code Form is subject to the terms of the Mozilla Public |
2 | | // License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | | // file, You can obtain one at https://mozilla.org/MPL/2.0/. |
4 | | |
5 | | use crate::calendar::gregorian::Gregorian; |
6 | | use crate::calendar::gregorian::GregorianMonth; |
7 | | use crate::calendar::prelude::CommonDate; |
8 | | use crate::calendar::prelude::CommonWeekOfYear; |
9 | | use crate::calendar::prelude::GuaranteedMonth; |
10 | | use crate::calendar::prelude::HasLeapYears; |
11 | | use crate::calendar::prelude::Quarter; |
12 | | use crate::calendar::prelude::ToFromCommonDate; |
13 | | use crate::calendar::AllowYearZero; |
14 | | use crate::calendar::CalendarMoment; |
15 | | use crate::calendar::OrdinalDate; |
16 | | use crate::calendar::ToFromOrdinalDate; |
17 | | use crate::common::error::CalendarError; |
18 | | use crate::day_count::CalculatedBounds; |
19 | | use crate::day_count::Epoch; |
20 | | use crate::day_count::Fixed; |
21 | | use crate::day_count::FromFixed; |
22 | | use crate::day_count::ToFixed; |
23 | | use std::num::NonZero; |
24 | | |
25 | | const HOLOCENE_YEAR_OFFSET: i32 = -10000; |
26 | | |
27 | | /// Represents a month in the Holocene calendar |
28 | | pub type HoloceneMonth = GregorianMonth; |
29 | | |
30 | | /// Represents a date in the Holocene calendar |
31 | | /// |
32 | | /// ## Introduction |
33 | | /// |
34 | | /// The Holocene calendar was proposed by Cesare Emiliani. It is identical to the proleptic |
35 | | /// Gregorian calendar, but with an extra 10000 years added to each date. Thus 2016 in the |
36 | | /// Gregorian calendar is 12016 in the Holocene calendar. |
37 | | /// |
38 | | /// ## Epoch |
39 | | /// |
40 | | /// Years are numbered based on a very rough estimate of the invention of agriculture. |
41 | | /// The first year of the Holocene calendar starts 10000 years before the first year of the |
42 | | /// proleptic Gregorian calendar. |
43 | | /// |
44 | | /// This epoch is called the Human Era. |
45 | | /// |
46 | | /// ## Representation and Examples |
47 | | /// |
48 | | /// ### Months |
49 | | /// |
50 | | /// The months are represented in this crate as [`HoloceneMonth`]. |
51 | | /// |
52 | | /// ``` |
53 | | /// use radnelac::calendar::*; |
54 | | /// use radnelac::day_count::*; |
55 | | /// |
56 | | /// let c_1_1 = CommonDate::new(12025, 1, 1); |
57 | | /// let a_1_1 = Holocene::try_from_common_date(c_1_1).unwrap(); |
58 | | /// assert_eq!(a_1_1.month(), HoloceneMonth::January); |
59 | | /// ``` |
60 | | /// |
61 | | /// ### Conversion from Gregorian |
62 | | /// |
63 | | /// For dates from other systems, it might be necessary to convert from the Gregorian system. |
64 | | /// |
65 | | /// ``` |
66 | | /// use radnelac::calendar::*; |
67 | | /// use radnelac::day_count::*; |
68 | | /// |
69 | | /// let g = Gregorian::try_new(1752, JulianMonth::September, 14).unwrap(); |
70 | | /// let h = g.convert::<Holocene>(); |
71 | | /// assert_eq!(h, Holocene::try_new(11752, GregorianMonth::September, 14).unwrap()); |
72 | | /// ``` |
73 | | /// |
74 | | /// ## Inconsistencies with Other Implementations |
75 | | /// |
76 | | /// Since this crate uses a proleptic Gregorian calendar with a year 0, some of the |
77 | | /// Gregorian conversions for dates before 1 Common Era may differ from other implementations. |
78 | | /// |
79 | | /// For example, Wikipedia claims that 1 Human Era corresponds to "10000 BC" in the Gregorian |
80 | | /// calendar and "-9999" in ISO-8601. However since this crate uses a proleptic Gregorian |
81 | | /// calendar, "-9999" (or 9999 Before Common Era) is the Gregorian year corresponding to 1 |
82 | | /// Human Era as per the functions in this crate. |
83 | | /// |
84 | | /// ## Further reading |
85 | | /// + [Wikipedia](https://en.wikipedia.org/wiki/Holocene_calendar) |
86 | | /// + [Kurzgesagt](https://www.youtube.com/watch?v=czgOWmtGVGs) |
87 | | #[derive(Debug, PartialEq, PartialOrd, Clone, Copy)] |
88 | | pub struct Holocene(CommonDate); |
89 | | |
90 | | impl AllowYearZero for Holocene {} |
91 | | |
92 | | impl ToFromOrdinalDate for Holocene { |
93 | 1.28k | fn valid_ordinal(ord: OrdinalDate) -> Result<(), CalendarError> { |
94 | 1.28k | Gregorian::valid_ordinal(ord) |
95 | 1.28k | } |
96 | | |
97 | 512 | fn ordinal_from_fixed(fixed_date: Fixed) -> OrdinalDate { |
98 | 512 | let g_ord = Gregorian::ordinal_from_fixed(fixed_date); |
99 | 512 | OrdinalDate { |
100 | 512 | year: (g_ord.year - HOLOCENE_YEAR_OFFSET), |
101 | 512 | day_of_year: g_ord.day_of_year, |
102 | 512 | } |
103 | 512 | } |
104 | | |
105 | 1.79k | fn to_ordinal(self) -> OrdinalDate { |
106 | 1.79k | let g = Gregorian::try_from_common_date(self.to_common_date()) |
107 | 1.79k | .expect("Same month/day validity"); |
108 | 1.79k | g.to_ordinal() |
109 | 1.79k | } |
110 | | |
111 | 256 | fn from_ordinal_unchecked(ord: OrdinalDate) -> Self { |
112 | 256 | let e = Gregorian::from_ordinal_unchecked(ord); |
113 | 256 | Holocene::try_from_common_date(e.to_common_date()).expect("Same month/day validity") |
114 | 256 | } |
115 | | } |
116 | | |
117 | | impl HasLeapYears for Holocene { |
118 | 512 | fn is_leap(h_year: i32) -> bool { |
119 | 512 | Gregorian::is_leap(h_year) //10000 is divisible by 400, so it's ok |
120 | 512 | } |
121 | | } |
122 | | |
123 | | impl CalculatedBounds for Holocene {} |
124 | | |
125 | | impl Epoch for Holocene { |
126 | 1.79k | fn epoch() -> Fixed { |
127 | 1.79k | Gregorian::try_year_start(HOLOCENE_YEAR_OFFSET + 1) |
128 | 1.79k | .expect("Year known to be valid") |
129 | 1.79k | .to_fixed() |
130 | 1.79k | } |
131 | | } |
132 | | |
133 | | impl FromFixed for Holocene { |
134 | 16.9k | fn from_fixed(date: Fixed) -> Holocene { |
135 | 16.9k | let result = Gregorian::from_fixed(date).to_common_date(); |
136 | 16.9k | Holocene(CommonDate::new( |
137 | 16.9k | result.year - (HOLOCENE_YEAR_OFFSET as i32), |
138 | 16.9k | result.month, |
139 | 16.9k | result.day, |
140 | 16.9k | )) |
141 | 16.9k | } |
142 | | } |
143 | | |
144 | | impl ToFixed for Holocene { |
145 | 9.73k | fn to_fixed(self) -> Fixed { |
146 | 9.73k | let g = Gregorian::try_from_common_date(CommonDate::new( |
147 | 9.73k | self.0.year + (HOLOCENE_YEAR_OFFSET as i32), |
148 | 9.73k | self.0.month, |
149 | 9.73k | self.0.day, |
150 | | )) |
151 | 9.73k | .expect("Same month/day rules"); |
152 | 9.73k | g.to_fixed() |
153 | 9.73k | } |
154 | | } |
155 | | |
156 | | impl ToFromCommonDate<HoloceneMonth> for Holocene { |
157 | 42.0k | fn to_common_date(self) -> CommonDate { |
158 | 42.0k | self.0 |
159 | 42.0k | } |
160 | | |
161 | 7.33k | fn from_common_date_unchecked(date: CommonDate) -> Self { |
162 | 7.33k | debug_assert!(Self::valid_ymd(date).is_ok()); |
163 | 7.33k | Self(date) |
164 | 7.33k | } |
165 | | |
166 | 16.2k | fn valid_ymd(date: CommonDate) -> Result<(), CalendarError> { |
167 | 16.2k | Gregorian::valid_ymd(date) |
168 | 16.2k | } |
169 | | |
170 | 256 | fn year_end_date(year: i32) -> CommonDate { |
171 | 256 | Gregorian::year_end_date(year) |
172 | 256 | } |
173 | | |
174 | 256 | fn month_length(year: i32, month: HoloceneMonth) -> u8 { |
175 | 256 | Gregorian::month_length(year, month) |
176 | 256 | } |
177 | | } |
178 | | |
179 | | impl Quarter for Holocene { |
180 | 2.56k | fn quarter(self) -> NonZero<u8> { |
181 | 2.56k | NonZero::new(((self.to_common_date().month - 1) / 3) + 1).expect("(m-1)/3 > -1") |
182 | 2.56k | } |
183 | | } |
184 | | |
185 | | impl GuaranteedMonth<HoloceneMonth> for Holocene {} |
186 | | impl CommonWeekOfYear<HoloceneMonth> for Holocene {} |
187 | | |
188 | | /// Represents a date *and time* in the Holocen Calendar |
189 | | pub type HoloceneMoment = CalendarMoment<Holocene>; |
190 | | |
191 | | #[cfg(test)] |
192 | | mod tests { |
193 | | use super::*; |
194 | | |
195 | | #[test] |
196 | 1 | fn h_epoch() { |
197 | 1 | let dh = CommonDate { |
198 | 1 | year: 1, |
199 | 1 | month: 1, |
200 | 1 | day: 1, |
201 | 1 | }; |
202 | 1 | let dg = CommonDate { |
203 | 1 | year: -9999, |
204 | 1 | month: 1, |
205 | 1 | day: 1, |
206 | 1 | }; |
207 | 1 | let fh = Holocene::try_from_common_date(dh).unwrap().to_fixed(); |
208 | 1 | let fg = Gregorian::try_from_common_date(dg).unwrap().to_fixed(); |
209 | 1 | assert_eq!(fh, fg); |
210 | 1 | assert_eq!(fh, Holocene::epoch()); |
211 | 1 | } |
212 | | |
213 | | #[test] |
214 | 1 | fn g_epoch() { |
215 | 1 | let dh = CommonDate { |
216 | 1 | year: 10001, |
217 | 1 | month: 1, |
218 | 1 | day: 1, |
219 | 1 | }; |
220 | 1 | let dg = CommonDate { |
221 | 1 | year: 1, |
222 | 1 | month: 1, |
223 | 1 | day: 1, |
224 | 1 | }; |
225 | 1 | let fh = Holocene::try_from_common_date(dh).unwrap().to_fixed(); |
226 | 1 | let fg = Gregorian::try_from_common_date(dg).unwrap().to_fixed(); |
227 | 1 | assert_eq!(fh, fg); |
228 | 1 | assert_eq!(fh, Gregorian::epoch()); |
229 | 1 | } |
230 | | |
231 | | #[test] |
232 | 1 | fn date_of_proposal() { |
233 | 1 | let dh = CommonDate { |
234 | 1 | year: 11993, |
235 | 1 | month: 12, |
236 | 1 | day: 30, |
237 | 1 | }; |
238 | 1 | let dg = CommonDate { |
239 | 1 | year: 1993, |
240 | 1 | month: 12, |
241 | 1 | day: 30, |
242 | 1 | }; |
243 | 1 | let fh = Holocene::try_from_common_date(dh).unwrap().to_fixed(); |
244 | 1 | let fg = Gregorian::try_from_common_date(dg).unwrap().to_fixed(); |
245 | 1 | assert_eq!(fh, fg); |
246 | 1 | } |
247 | | } |