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