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