calendrical_calculations/rata_die.rs
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
10use core::fmt;
11use core::ops::{Add, AddAssign, Sub, SubAssign};
12#[allow(unused_imports)]
13use core_maths::*;
14
15/// The *Rata Die*, or *R.D.*: number of days since January 1, 1 CE.
16///
17/// **The primary definition of this type is in the [`calendrical_calculations`](https://docs.rs/calendrical_calculations) crate.**
18///
19/// See: <https://en.wikipedia.org/wiki/Rata_Die>
20///
21/// Typically, one should obtain [`RataDie`]s from other calendrical code, rather than constructing them from integers.
22/// The valid range for direct construction is deliberately not documented as it may change.
23#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
24pub struct RataDie(i64);
25
26impl RataDie {
27 /// Create a `RataDie`
28 ///
29 /// Typically, one should obtain `RataDie`s from other calendrical code, rather than
30 /// constructing them from integers. The valid range for direct construction is
31 /// deliberately not documented as it may change.
32 pub const fn new(fixed_date: i64) -> Self {
33 let result = Self(fixed_date);
34 #[cfg(debug_assertions)]
35 result.check();
36 result
37 }
38
39 /// Check that it is in range
40 #[cfg(debug_assertions)]
41 const fn check(self) {
42 if self.0 > i64::MAX / 256 {
43 debug_assert!(
44 false,
45 "RataDie is not designed to store values near to the overflow boundary"
46 );
47 }
48 if self.0 < i64::MIN / 256 {
49 debug_assert!(
50 false,
51 "RataDie is not designed to store values near to the overflow boundary"
52 );
53 }
54 }
55
56 /// A valid `RataDie` that is intended to be below all dates representable in calendars
57 #[doc(hidden)] // for testing only
58 pub const fn big_negative() -> Self {
59 Self::new(i64::MIN / 256 / 256)
60 }
61
62 /// Convert this to an `i64` value representing the `RataDie`
63 pub const fn to_i64_date(self) -> i64 {
64 self.0
65 }
66
67 /// Convert this to an `f64` value representing the `RataDie`
68 pub(crate) const fn to_f64_date(self) -> f64 {
69 self.0 as f64
70 }
71
72 /// Calculate the number of days between two `RataDie` in a const-friendly way
73 ///
74 /// ```
75 /// use calendrical_calculations::julian::fixed_from_julian;
76 ///
77 /// assert_eq!(
78 /// fixed_from_julian(1930, 2, 2).since(fixed_from_julian(1930, 1, 1)),
79 /// 32
80 /// );
81 /// ```
82 pub const fn since(self, rhs: Self) -> i64 {
83 self.0 - rhs.0
84 }
85
86 /// Calculate the number of days between two `RataDie` in a const-friendly way
87 ///
88 /// ```
89 /// use calendrical_calculations::julian::fixed_from_julian;
90 ///
91 /// assert_eq!(
92 /// fixed_from_julian(1930, 1, 1).until(fixed_from_julian(1930, 2, 2)),
93 /// 32
94 /// );
95 /// ```
96 pub const fn until(self, rhs: Self) -> i64 {
97 rhs.0 - self.0
98 }
99
100 /// Adds a number of days to this `RataDie` in a const-friendly way
101 pub const fn add(self, rhs: i64) -> Self {
102 let result = Self(self.0 + rhs);
103 #[cfg(debug_assertions)]
104 result.check();
105 result
106 }
107
108 /// Convert this to a [`Moment`]
109 pub(crate) const fn as_moment(self) -> Moment {
110 Moment::new(self.0 as f64)
111 }
112}
113
114impl fmt::Debug for RataDie {
115 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
116 let rd = self.0;
117 if let Ok((y, m, d)) = crate::gregorian::gregorian_from_fixed(*self) {
118 write!(f, "{rd} R.D. ({y}-{m:02}-{d:02})")
119 } else {
120 write!(f, "{rd} R.D. (out of bounds)")
121 }
122 }
123}
124
125/// Shift a [`RataDie`] N days into the future
126impl Add<i64> for RataDie {
127 type Output = Self;
128 fn add(self, rhs: i64) -> Self::Output {
129 self.add(rhs)
130 }
131}
132
133impl AddAssign<i64> for RataDie {
134 fn add_assign(&mut self, rhs: i64) {
135 self.0 += rhs;
136 }
137}
138
139/// Shift a [`RataDie`] N days into the past
140impl Sub<i64> for RataDie {
141 type Output = Self;
142 fn sub(self, rhs: i64) -> Self::Output {
143 let result = Self(self.0 - rhs);
144 #[cfg(debug_assertions)]
145 result.check();
146 result
147 }
148}
149
150impl SubAssign<i64> for RataDie {
151 fn sub_assign(&mut self, rhs: i64) {
152 self.0 -= rhs;
153 }
154}
155
156/// Calculate the number of days between two [`RataDie`]
157impl Sub for RataDie {
158 type Output = i64;
159 fn sub(self, rhs: Self) -> Self::Output {
160 self.since(rhs)
161 }
162}
163
164/// A moment is a [`RataDie`] with a fractional part giving the time of day.
165///
166/// NOTE: This should not cause overflow errors for most cases, but consider
167/// alternative implementations if necessary.
168#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
169pub(crate) struct Moment(f64);
170
171/// Add a number of days to a Moment
172impl Add<f64> for Moment {
173 type Output = Self;
174 fn add(self, rhs: f64) -> Self::Output {
175 Self(self.0 + rhs)
176 }
177}
178
179impl AddAssign<f64> for Moment {
180 fn add_assign(&mut self, rhs: f64) {
181 self.0 += rhs;
182 }
183}
184
185/// Subtract a number of days from a Moment
186impl Sub<f64> for Moment {
187 type Output = Self;
188 fn sub(self, rhs: f64) -> Self::Output {
189 Self(self.0 - rhs)
190 }
191}
192
193impl SubAssign<f64> for Moment {
194 fn sub_assign(&mut self, rhs: f64) {
195 self.0 -= rhs;
196 }
197}
198
199/// Calculate the number of days between two moments
200impl Sub for Moment {
201 type Output = f64;
202 fn sub(self, rhs: Self) -> Self::Output {
203 self.0 - rhs.0
204 }
205}
206
207impl Moment {
208 /// Create a new moment
209 pub const fn new(value: f64) -> Moment {
210 Moment(value)
211 }
212
213 /// Get the inner field of a Moment
214 pub const fn inner(self) -> f64 {
215 self.0
216 }
217
218 /// Get the [`RataDie`] of a Moment
219 pub fn as_rata_die(self) -> RataDie {
220 RataDie::new(self.0.floor() as i64)
221 }
222}
223
224#[test]
225fn test_moment_to_rata_die_conversion() {
226 for i in -1000..=1000 {
227 let moment = Moment::new(i as f64);
228 let rata_die = moment.as_rata_die();
229 assert_eq!(rata_die.to_i64_date(), i);
230 }
231}