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
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
//! Types in this module represent linear
use crate::{YearType, traits::CalendarDate};
macro_rules! scalar {
(
$(#[$attr:meta])*
name: $name:ident;
unit: $unit:ident;
base: $t:ty;
min: $min:expr;
max: $max:expr;
future: $next:ident;
past: $previous:ident;
) => {
$(#[$attr])*
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct $name($t);
impl $name {
/// Earliest representable date (in its native integer type).
pub const MIN_INT: $t = $min;
/// Latest representable value (in its native integer type).
pub const MAX_INT: $t = $max;
/// Earliest representable date
pub const MIN: Self = Self(Self::MIN_INT);
/// Latest representable value
pub const MAX: Self = Self(Self::MAX_INT);
#[doc = concat!("Creates a new ", stringify!($name), ".","\n\n", "Returns `None` for invalid values. Valid values are between (and including) [`MIN_INT`](Self::MIN_INT) (A000-01-01) and [`MAX_INT`](Self::MAX_INT) (Z999-13-29).")]
#[inline]
#[must_use]
pub const fn new(value: $t) -> Option<Self> {
if value >= Self::MIN_INT && value <= Self::MAX_INT {
Some(Self(value))
} else {
None
}
}
/// Returns the underlying integer value.
#[inline(always)]
#[must_use]
pub const fn value(&self) -> $t {
self.0
}
#[doc = concat!("Returns the next ", stringify!($unit), ".")]
#[doc = concat!("Returns `None` if the next ", stringify!($unit), " is outside the valid SAC13 range.")]
#[inline]
#[must_use]
pub const fn $next(&self) -> Option<Self> {
if self.0 < Self::MAX_INT {
Some(Self(self.0+1))
} else {
None
}
}
#[doc = concat!("Returns the previous ", stringify!($unit), ".")]
#[doc = concat!("Returns `None` if the previous ", stringify!($unit), " is outside the valid SAC13 range.")]
#[inline]
#[must_use]
pub const fn $previous(&self) -> Option<Self> {
if self.0 > Self::MIN_INT {
Some(Self(self.0-1))
} else {
None
}
}
}
};
}
macro_rules! scalar_day {
(
$(#[$attr:meta])*
name: $name:ident;
base: $t:ty;
min: $min:literal;
) => {
scalar!(
$(#[$attr])*
name: $name;
unit: day;
base: $t;
min: $min;
max: ($min + 9496300);
future: tomorrow;
past: yesterday;
);
impl core::fmt::Display for $name {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f,"{}", self.0)
}
}
impl $name {
const JULIAN_OFFSET: i32 = -1931284 - $min;
}
impl CalendarDate for $name {
const MIN: Self = Self::MIN;
const MAX: Self = Self::MAX;
fn as_julian(&self) -> i32 {
(self.0 as i32) + Self::JULIAN_OFFSET
}
fn from_julian(value: i32) -> Option<Self> {
Self::new((value - Self::JULIAN_OFFSET) as $t)
}
}
};
}
scalar_day!(
/// Days since the Unix Epoch. 1970-01-01 (Gregorian) is day zero.
///
/// _Important: "Unix Day" is **not** a Unix timestamp! Neither in seconds nor milliseconds._
///
/// # Example
///
/// ```
/// use sac13::prelude::*;
/// use sac13::day_counts::UnixDay;
///
/// const SECONDS_PER_DAY : i32 = 86400;
///
/// // This example completely ignores time zones
/// // and will give you the UnixDay for the timestamp in UTC;
/// // which technically isn't a time zone, but I think you get the gist.
/// let unix_timestamp_seconds = 1355313600;
/// let unix_day = unix_timestamp_seconds / SECONDS_PER_DAY;
///
/// let date : Date = UnixDay::new(unix_day).unwrap().convert();
/// ```
///
/// # About Unix Days
/// The name is actually made up, but the concept is simple. It's the number of days since
/// the Unix Epoch, so 1.1.1970 (Gregorian) is day zero.
/// Java calls this [`EPOCH_DAY`](https://docs.oracle.com/javase/10/docs/api/java/time/temporal/ChronoField.html#EPOCH_DAY)
/// but because `epoch` is a bit too unspecific for my taste I named it [`UnixDay`](crate::day_counts::UnixDay).
///
/// ## Why do I have to manually convert timestamps?
/// It was actually a very deliberate choice to not allow construction with unix timestamps directly because
/// if you have a timestamp, you have to think about time zones, or you will get subtle bugs.
/// To force you to think about the conversion we let you do it manually.
///
name: UnixDay;
base: i32;
min: -4371872;
// 5124428
);
scalar_day!(
/// Julian Day Number. Day count since the beginning of the Julian period.
///
/// [Wikipedia: Julian day]((https://en.wikipedia.org/wiki/Julian_day))
name: JulianDay;
base: i32;
min: -1931284;
);
scalar_day!(
/// SAC13 Year Cycle Epoch Day Number.
///
/// Day count since the beginning of the first SAC13 leap year cycle;
/// this is distinct from [Sac13Day](crate::day_counts::Sac13Day)!
/// [CycleEpochDay](crate::day_counts::Sac13Day)s day zero is 199 years
/// before A000-01-01 because of the offset in SAC13s leap year rule.
name: CycleEpochDay;
base: u32;
min: 72683;
);
scalar_day!(
/// SAC13 Day Number. Days since A000-01-01.
name: Sac13Day;
base: u32;
min: 0;
);
scalar!(
/// SAC13 year. It roughly corresponds to the Gregorian Year + 10'000.
/// Roughly, because the Gregorian Calendar starts its year with January and
/// SAC13 with March.
///
/// The year can have any value from 0 to 25'999 (both inclusive).
///
/// # Examples
///
/// ```
/// use sac13::prelude::*;
///
/// // Preferred method for hard-coded / compile-time years:
/// let year = year!(M024);
///
/// // From &str (e.g. user input):
/// let year = Year::try_from_str("M024").unwrap();
///
/// // From an integer (via `TryFrom` trait):
/// let year = Year::new(12_024).unwrap();
///
/// // Year construction via const compile-time helper function:
/// // Prefer that method if you know the year at compile-time.
///
/// ```
///
/// # About the limits
/// Even though the SAC13 calendar system design could easily support negative years
/// and years beyond 26'000 we intentionally chose not to for the following reasons:
///
/// - The 10'000 year offset compared to the Gregorian Calendar the year zero
/// is so far in the past, there aren't really any applications for exact dates around
/// that time. Maybe astronomers, but they already use linear time-scales like JDN
/// instead of civil calendars.
///
/// - The year 25'999 is so far in the future it's highly unlikely, that this calendar
/// would survive unaltered for that long anyway. I hope that humans (or our AI overlords)
/// are no longer interested in Earth-based solar calendars at that point.
///
/// - SAC13 years are typically written with a millennium indicator letter
/// (A=0, B=1, ..., Z=25) to disambiguate between SAC13 and the Gregorian Calendar.
/// So the year 12'020 is written as M020 and the year 25'999 would be Z999.
///
/// All SAC13 implementations have to respect those limits and handle edge cases accordingly.
/// Having different limits than zero and 25'999 is considered a bug. Limits should be handled
/// as graceful as possible.
///
/// For example:
///
/// - If you have a UI, prevent the user from switching to dates outside the limit.
/// - If you process data, or handle requests, return an error if the request contains
/// invalid dates (dates outside the limits are invalid!).
/// - As a last resort you can also log, silently drop or abort the process in those cases.
///
/// The benefit of such strict rules is that everybody knows what to expect and what other
/// systems consider valid or invalid. Unlike with the Gregorian Calender where everybody
/// decides for themselves what the limits are. Because of that, every software out there handles
/// the cases differently on arbitrary limits.
///
/// If you are implementing SAC13 according to the specification you know for a fact that
/// using a 16 bit integer (signed or unsigned doesn't matter) would be enough.
name: Year;
unit: year;
base: u16;
min: 0;
max: 25999;
future: next;
past: previous;
);
impl core::fmt::Display for Year {
/// Displays the year with prefixed millennium indicator.
///
/// ```
/// use sac13::year;
///
/// let formatted_year = format!("{}", year!(M020));
/// assert_eq!(formatted_year, "M020");
/// ```
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let millennium = (self.0 / 1000) as u8;
let sub_mill = self.0 % 1000;
let m = (b'A' + millennium) as char;
write!(f, "{m}{sub_mill:03}")
}
}
macro_rules! parse_digits {
($var:ident, $min:literal, $max:literal) => {
if $var < $min || $var > $max {
return None;
} else {
($var - $min) as u16
}
};
}
impl Year {
/// Returns the year, given four ASCII digits
#[inline(always)]
const fn parse_year_digits(d0: u8, d1: u8, d2: u8, d3: u8) -> Option<Self> {
let d0 = parse_digits!(d0, b'A', b'Z');
let d1 = parse_digits!(d1, b'0', b'9');
let d2 = parse_digits!(d2, b'0', b'9');
let d3 = parse_digits!(d3, b'0', b'9');
let year_value = d0 * 1000 + d1 * 100 + d2 * 10 + d3;
Self::new(year_value)
}
/// Parses a given `&str` as a SAC13 year.
///
/// ```rust
/// use sac13::prelude::*;
///
/// assert_eq!(Year::try_from_str("M020"), Some(year!(M020)));
/// assert_eq!(Year::try_from_str("M021"), Year::new(12021));
///
/// // trailing and leading whitespace is not allowed
/// assert_eq!(Year::try_from_str("M021 "), None);
/// assert_eq!(Year::try_from_str(" M021"), None);
///
/// // lower-case millenium letter is also not allowed
/// assert_eq!(Year::try_from_str("m021"), None);
/// ```
#[must_use]
pub const fn try_from_str(year: &str) -> Option<Self> {
let year_bytes = year.as_bytes();
if year_bytes.len() != 4 {
return None;
}
Self::parse_year_digits(year_bytes[0], year_bytes[1], year_bytes[2], year_bytes[3])
}
/// Returns the type of the year (leap year or common year).
#[must_use]
pub const fn year_type(&self) -> YearType {
match (self.0 + 199) % 293 % 33 % 4 {
1 => YearType::Leap,
_ => YearType::Common,
}
}
#[must_use]
/// Returns `true` if the year is a leap year and `false` if it's a common year. This is the opposite of [`Year::is_common`].
pub const fn is_leap(&self) -> bool {
matches!(self.year_type(), YearType::Leap)
}
#[must_use]
/// Returns `true` if the year is a common year and `false` if it's a leap year. This is the opposite of [`Year::is_leap`].
pub const fn is_common(&self) -> bool {
matches!(self.year_type(), YearType::Common)
}
/// Returns the number of days the year has.
///
/// Can only be 365 or 366.
#[must_use]
pub const fn days(&self) -> u16 {
match self.year_type() {
YearType::Common => 365,
YearType::Leap => 366,
}
}
}