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 RataDies 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}