1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
use core::fmt::{self, Debug, Formatter};
use crate::calendar::Iso;
use crate::{Calendar, Date, YearMonth};
/// A **year** on a calendar.
///
/// This type does not store or represent a month, day, time, or time-zone.
///
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Year<C: Calendar = Iso> {
calendar: C,
// absolute year number
// for ISO, “-15” would refer to “16 BCE”
number: i32,
}
/// Obtains a [`Year`] from a statically known number.
///
/// Validation is done at compile-time.
///
/// Values may be used in a `const` or `static` declaration.
///
/// ```rust
/// # use ako::{Year, year};
/// # use ako::calendar::Iso;
/// #
/// const COPYRIGHT: Year<Iso> = year!(2024);
/// #
/// # assert_eq!(COPYRIGHT.number(), 2024);
/// ```
///
// #[macro_export]
macro_rules! year {
($number:expr) => {
const {
match $crate::Year::iso($number) {
Ok(year) => year,
Err(error) => error.panic(),
}
}
};
}
// Construction: ISO
impl Year<Iso> {
/// Obtains an ISO year.
pub const fn iso(number: i32) -> crate::Result<Self> {
Iso.year(number)
}
}
// Construction
impl<C: Calendar> Year<C> {
/// Obtains a year on the given calendar.
pub fn of(calendar: C, number: i32) -> crate::Result<Self> {
calendar.year(number)
}
/// Obtains a year on the given calendar, without any checks.
#[allow(unsafe_code)]
#[must_use]
pub(crate) const unsafe fn unchecked_of(calendar: C, number: i32) -> Self {
debug_assert!(number <= 5_000_000);
debug_assert!(number >= -5_000_000);
Self { calendar, number }
}
}
// Components
impl<C: Calendar> Year<C> {
/// Gets the calendar that this year is interpreted in.
#[must_use]
pub const fn calendar(self) -> C {
self.calendar
}
/// Gets the year number.
#[must_use]
pub const fn number(self) -> i32 {
self.number
}
// TODO: pub const fn era(self) -> Era { ... }
// TODO: pub const fn era_relative(self) -> u32 { ... }
}
// Queries
impl<C: Calendar> Year<C> {
// TODO: Return YearDayIter (ExactSize, BiDirectional)
/// Gets the number of days in this year.
#[must_use]
pub fn days(self) -> u16 {
self.calendar.year_days(self.number)
}
// TODO: Return YearMonthIter (ExactSize, BiDirectional)
/// Gets the number of months in this year.
#[must_use]
pub fn months(self) -> u8 {
self.calendar.year_months(self.number)
}
/// Determines if this year is a leap year.
///
/// # Examples
///
/// ```rust
/// # use ako::Year;
/// # fn main() -> ako::Result<()> {
/// assert!(Year::iso(2024)?.is_leap());
/// assert!(!Year::iso(2025)?.is_leap());
/// assert!(Year::iso(2000)?.is_leap());
/// # Ok(()) }
/// ```
///
#[must_use]
pub fn is_leap(self) -> bool {
self.calendar.year_is_leap(self.number)
}
}
// Astronomy
#[cfg(feature = "astronomy")]
impl<C: Calendar> Year<C> {
/// Gets the near-exact date-time of the [northward equinox] for this year.
///
/// The [northward equinox] (or March equinox)
/// is the moment when the Sun appears to cross the celestial equator, heading northward.
///
/// [northward equinox]: https://en.wikipedia.org/wiki/March_equinox
///
// TODO: Return ZonedDateTime to UTC
#[allow(unsafe_code)]
#[doc(alias = "march_equinox")]
#[doc(alias = "march")]
pub fn northward_equinox(self) -> crate::DateTime<C> {
let jde = crate::astronomy::solstice::march(self.number);
// SAFETY: all values returned from dates within supported ranges are guaranteed to be valid
let moment = unsafe { crate::Moment::from_julian_ephemeris_day(jde).unwrap_unchecked() };
moment.on(self.calendar)
}
/// Gets the near-exact date-time of the [northern solstice] for this year.
///
/// The [northern solstice] (or June solstice)
/// is the moment when the Sun is directly over the Tropic of Cancer,
/// located in the Northern Hemisphere.
///
/// [northern solstice]: https://en.wikipedia.org/wiki/June_solstice
///
// TODO: Return ZonedDateTime to UTC
#[allow(unsafe_code)]
#[doc(alias = "june_solstice")]
#[doc(alias = "june")]
pub fn northern_solstice(self) -> crate::DateTime<C> {
let jde = crate::astronomy::solstice::june(self.number);
// SAFETY: all values returned from dates within supported ranges are guaranteed to be valid
let moment = unsafe { crate::Moment::from_julian_ephemeris_day(jde).unwrap_unchecked() };
moment.on(self.calendar)
}
/// Gets the near-exact date-time of the [southward equinox] for this year.
///
/// The [southward equinox] (or September equinox)
/// is the moment when the Sun appears to cross the celestial equator, heading southward.
///
/// [southward equinox]: https://en.wikipedia.org/wiki/September_equinox
///
// TODO: Return ZonedDateTime to UTC
#[allow(unsafe_code)]
#[doc(alias = "september_equinox")]
#[doc(alias = "september")]
pub fn southward_equinox(self) -> crate::DateTime<C> {
let jde = crate::astronomy::solstice::september(self.number);
// SAFETY: all values returned from dates within supported ranges are guaranteed to be valid
let moment = unsafe { crate::Moment::from_julian_ephemeris_day(jde).unwrap_unchecked() };
moment.on(self.calendar)
}
/// Gets the near-exact date-time of the [southern solstice] for this year.
///
/// The [southern solstice] (or December solstice)
/// is the moment when the Sun is directly over the Tropic of Capricorn,
/// located in the Southern Hemisphere.
///
/// [southern solstice]: https://en.wikipedia.org/wiki/December_solstice
///
// TODO: Return ZonedDateTime to UTC
#[allow(unsafe_code)]
#[doc(alias = "december_solstice")]
#[doc(alias = "december")]
pub fn southern_solstice(self) -> crate::DateTime<C> {
let jde = crate::astronomy::solstice::december(self.number);
// SAFETY: all values returned from dates within supported ranges are guaranteed to be valid
let moment = unsafe { crate::Moment::from_julian_ephemeris_day(jde).unwrap_unchecked() };
moment.on(self.calendar)
}
}
// Composition
impl<C: Calendar> Year<C> {
/// Combines this year with the given month to create a [`YearMonth`].
pub fn with_month(self, month: u8) -> crate::Result<YearMonth<C>> {
YearMonth::of(self.calendar, self.number, month)
}
/// Combines this year with the given day of the year to create a [`Date`].
pub fn with_day(self, day: u16) -> crate::Result<Date<C>> {
Date::from_ordinal_date(self.calendar, self.number, day)
}
}
impl<C: Calendar> Debug for Year<C> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.pad(&self.format_rfc3339())
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "astronomy")]
#[test]
fn expect_year_solstice() {
use crate::year;
// TODO: the solstice functions should return a ZonedDateTime in UTC
let southern_solstice_2024 = year!(2024).southern_solstice();
let southern_solstice_2036 = year!(2036).southern_solstice();
let southern_solstice_2020 = year!(2020).southern_solstice();
// be careful when confirming these values
// many online sources are +/- one second due to the confusion between UT1 and UTC
// https://www.timeanddate.com/calendar/december-solstice.html
assert_eq!(
southern_solstice_2020.format_rfc3339(),
"2020-12-21T10:02:16.414185047Z"
);
assert_eq!(
southern_solstice_2024.format_rfc3339(),
"2024-12-21T09:20:28.207420766Z"
);
assert_eq!(
southern_solstice_2036.format_rfc3339(),
"2036-12-21T07:12:38.636344254Z"
);
}
}