Coverage Report

Created: 2025-10-19 21:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}