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!(fixed_from_julian(1930, 2, 2).since(fixed_from_julian(1930, 1, 1)), 32);
78    /// ```
79    pub const fn since(self, rhs: Self) -> i64 {
80        self.0 - rhs.0
81    }
82
83    /// Calculate the number of days between two `RataDie` in a const-friendly way
84    ///
85    /// ```
86    /// use calendrical_calculations::julian::fixed_from_julian;
87    ///
88    /// assert_eq!(fixed_from_julian(1930, 1, 1).until(fixed_from_julian(1930, 2, 2)), 32);
89    /// ```
90    pub const fn until(self, rhs: Self) -> i64 {
91        rhs.0 - self.0
92    }
93
94    /// Adds a number of days to this `RataDie` in a const-friendly way
95    pub const fn add(self, rhs: i64) -> Self {
96        let result = Self(self.0 + rhs);
97        #[cfg(debug_assertions)]
98        result.check();
99        result
100    }
101
102    /// Convert this to a [`Moment`]
103    pub(crate) const fn as_moment(self) -> Moment {
104        Moment::new(self.0 as f64)
105    }
106}
107
108impl fmt::Debug for RataDie {
109    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
110        let rd = self.0;
111        if let Ok((y, m, d)) = crate::iso::iso_from_fixed(*self) {
112            write!(f, "{rd} R.D. ({y}-{m:02}-{d:02})")
113        } else {
114            write!(f, "{rd} R.D. (out of bounds)")
115        }
116    }
117}
118
119/// Shift a RataDie N days into the future
120impl Add<i64> for RataDie {
121    type Output = Self;
122    fn add(self, rhs: i64) -> Self::Output {
123        self.add(rhs)
124    }
125}
126
127impl AddAssign<i64> for RataDie {
128    fn add_assign(&mut self, rhs: i64) {
129        self.0 += rhs;
130    }
131}
132
133/// Shift a RataDie N days into the past
134impl Sub<i64> for RataDie {
135    type Output = Self;
136    fn sub(self, rhs: i64) -> Self::Output {
137        let result = Self(self.0 - rhs);
138        #[cfg(debug_assertions)]
139        result.check();
140        result
141    }
142}
143
144impl SubAssign<i64> for RataDie {
145    fn sub_assign(&mut self, rhs: i64) {
146        self.0 -= rhs;
147    }
148}
149
150/// Calculate the number of days between two RataDie
151impl Sub for RataDie {
152    type Output = i64;
153    fn sub(self, rhs: Self) -> Self::Output {
154        self.since(rhs)
155    }
156}
157
158/// A moment is a RataDie with a fractional part giving the time of day.
159///
160/// NOTE: This should not cause overflow errors for most cases, but consider
161/// alternative implementations if necessary.
162#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
163pub(crate) struct Moment(f64);
164
165/// Add a number of days to a Moment
166impl Add<f64> for Moment {
167    type Output = Self;
168    fn add(self, rhs: f64) -> Self::Output {
169        Self(self.0 + rhs)
170    }
171}
172
173impl AddAssign<f64> for Moment {
174    fn add_assign(&mut self, rhs: f64) {
175        self.0 += rhs;
176    }
177}
178
179/// Subtract a number of days from a Moment
180impl Sub<f64> for Moment {
181    type Output = Self;
182    fn sub(self, rhs: f64) -> Self::Output {
183        Self(self.0 - rhs)
184    }
185}
186
187impl SubAssign<f64> for Moment {
188    fn sub_assign(&mut self, rhs: f64) {
189        self.0 -= rhs;
190    }
191}
192
193/// Calculate the number of days between two moments
194impl Sub for Moment {
195    type Output = f64;
196    fn sub(self, rhs: Self) -> Self::Output {
197        self.0 - rhs.0
198    }
199}
200
201impl Moment {
202    /// Create a new moment
203    pub const fn new(value: f64) -> Moment {
204        Moment(value)
205    }
206
207    /// Get the inner field of a Moment
208    pub const fn inner(self) -> f64 {
209        self.0
210    }
211
212    /// Get the RataDie of a Moment
213    pub fn as_rata_die(self) -> RataDie {
214        RataDie::new(self.0.floor() as i64)
215    }
216}
217
218#[test]
219fn test_moment_to_rata_die_conversion() {
220    for i in -1000..=1000 {
221        let moment = Moment::new(i as f64);
222        let rata_die = moment.as_rata_die();
223        assert_eq!(rata_die.to_i64_date(), i);
224    }
225}