1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
// SPDX-License-Identifier: MIT
// Copyright 2023 IROX Contributors

//!
//! Contains [`JulianDate`] and others - ways of measuring a discrete amount of days from a specific
//! Julian [`Epoch`]
//!

use core::marker::PhantomData;
use core::ops::{Add, AddAssign, Sub, SubAssign};
use irox_units::units::duration::{Duration, DurationUnit};

use crate::epoch::{PrimeEpoch, PRIME_EPOCH};
use crate::{
    epoch::{Epoch, UnixTimestamp, COMMON_ERA_EPOCH, GREGORIAN_EPOCH},
    gregorian::Date,
    SECONDS_IN_DAY,
};

//
/// The Julian Epoch, 01-JAN 4713 BC (Gregorian)
pub const JULIAN_EPOCH: Epoch = Epoch(Date {
    year: -4712,
    day_of_year: 0,
});

///
/// The Reduced Julian Epoch, 16-NOV-1858
///
/// 2400000 JD after the [`JULIAN_EPOCH`]
pub const REDUCED_JULIAN_EPOCH: Epoch = Epoch(Date {
    year: 1858,
    day_of_year: 320,
});

///
/// The Truncated Julian Epoch, used by NASA, 24-MAY-1968
///
/// 2440000.5 JD after the [`JULIAN_EPOCH`]
pub const TRUNCATED_JULIAN_EPOCH: Epoch = Epoch(Date {
    year: 1968,
    day_of_year: 145,
});

///
/// A Julian Date represents a number of days (86400 seconds) since a particular
/// Epoch.
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
pub struct JulianDayNumber<T> {
    epoch: Epoch,
    day_number: f64,

    _phantom: PhantomData<T>,
}

impl<T> JulianDayNumber<T> {
    pub(crate) fn new(epoch: Epoch, day_number: f64) -> Self {
        JulianDayNumber {
            epoch,
            day_number,
            _phantom: Default::default(),
        }
    }
    pub fn get_day_number(&self) -> f64 {
        self.day_number
    }
    pub fn get_epoch(&self) -> Epoch {
        self.epoch
    }
}

/// No functionality, used as a static compile-time type check
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
pub struct JulianEpoch;

/// No functionality, used as a static compile-time type check
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
pub struct ReducedJulianEpoch;

/// No functionality, used as a static compile-time type check
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
pub struct ModifiedJulianEpoch;

/// No functionality, used as a static compile-time type check
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
pub struct TruncatedJulianEpoch;

/// No functionality, used as a static compile-time type check
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
pub struct LilianEpoch;

/// No functionality, used as a static compile-time type check
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
pub struct RataDieEpoch;

///
/// The Julian Date is the number of days since the [`JULIAN_EPOCH`]
///
/// Noon 12:00 on 01-JAN, 4713 BC
pub type JulianDate = JulianDayNumber<JulianEpoch>;

pub const JULIAN_JD_OFFSET: f64 = 0.0_f64;

///
/// The Reduced Julian Date is the number of days since the [`REDUCED_JULIAN_EPOCH`]
/// or 2400000 days after the [`JULIAN_EPOCH`]
///
/// Noon 12:00 on 16-NOV-1858
pub type ReducedJulianDate = JulianDayNumber<ReducedJulianEpoch>;

/// The offset from the [`JULIAN_EPOCH`] for the [`ReducedJulianDate`]
pub const REDUCED_JD_OFFSET: f64 = 2400000_f64;

///
/// The Modified Julian Date shifts the Reduced Julian Date by 12 hours forward,
/// or 2400000.5 days after the [`JULIAN_EPOCH`]
///
/// Midnight on 17-NOV-1858
pub type ModifiedJulianDate = JulianDayNumber<ModifiedJulianEpoch>;

/// The offset from the [`JULIAN_EPOCH`] for the [`ModifiedJulianDate`]
pub const MODIFIED_JD_OFFSET: f64 = 2400000.5_f64;

///
/// The Truncated Julian Date uses the [`TRUNCATED_JULIAN_EPOCH`] as a round
/// offset of 2440000.5 after the [`JULIAN_EPOCH`]
///
/// Midnight on 24-MAY-1968
pub type TruncatedJulianDate = JulianDayNumber<TruncatedJulianEpoch>;

/// The offset from the [`JULIAN_EPOCH`] for the [`TruncatedJulianDate`]
pub const TRUNCATED_JD_OFFSET: f64 = 2440000.5_f64;

///
/// The Lilian Date is the day number offset from the [`GREGORIAN_EPOCH`],
/// 2299159.5 JD after the [`JULIAN_EPOCH`]
///
/// Midnight on 15-OCT-1582
pub type LilianDate = JulianDayNumber<LilianEpoch>;

/// The offset from the [`JULIAN_EPOCH`] for the [`LilianDate`]
pub const LILIAN_JD_OFFSET: f64 = 2299159.5_f64;

///
/// The Rata Die (Latin: "Fixed Date") is the fixed number of days in the Common
/// Era, since Midnight 01-01-0001 AD, 1721424.5 after [`JULIAN_EPOCH`]
pub type RataDieDate = JulianDayNumber<RataDieEpoch>;

/// The offset from the [`JULIAN_EPOCH`] for the [`RataDieDate`]
pub const RATA_DIE_JD_OFFSET: f64 = 1721424.5_f64;

/// The offset from the [`JULIAN_EPOCH`] for the [`UnixTimestamp`]
pub const UNIX_TS_JD_OFFSET: f64 = 2240587.5_f64;

///
/// The Prime Date is the fixed number of days since 01-JAN-1900.
pub type PrimeDate = JulianDayNumber<PrimeEpoch>;
pub const PRIME_JD_OFFSET: f64 = 2415020.5_f64;

macro_rules! impl_julian {
    ($date:ident,$epoch:ident,$offset:ident) => {
        impl From<JulianDate> for $date {
            fn from(value: JulianDate) -> Self {
                $date::new($epoch, value.day_number - $offset)
            }
        }
        impl From<$date> for JulianDate {
            fn from(value: $date) -> Self {
                JulianDate::new(JULIAN_EPOCH, value.day_number + $offset)
            }
        }
        impl From<&JulianDate> for $date {
            fn from(value: &JulianDate) -> Self {
                $date::new($epoch, value.day_number - $offset)
            }
        }
        impl From<&$date> for JulianDate {
            fn from(value: &$date) -> Self {
                JulianDate::new(JULIAN_EPOCH, value.day_number + $offset)
            }
        }
    };
}

impl<T> Add<Duration> for JulianDayNumber<T> {
    type Output = JulianDayNumber<T>;

    fn add(self, rhs: Duration) -> Self::Output {
        let day_number = self.day_number + rhs.as_seconds_f64() / SECONDS_IN_DAY as f64;
        Self::new(self.epoch, day_number)
    }
}

impl<T> Add<&Duration> for JulianDayNumber<T> {
    type Output = JulianDayNumber<T>;

    fn add(self, rhs: &Duration) -> Self::Output {
        let day_number = self.day_number + rhs.as_seconds_f64() / SECONDS_IN_DAY as f64;
        Self::new(self.epoch, day_number)
    }
}

impl<T> Sub<Duration> for JulianDayNumber<T> {
    type Output = JulianDayNumber<T>;

    fn sub(self, rhs: Duration) -> Self::Output {
        let day_number = self.day_number - rhs.as_seconds_f64() / SECONDS_IN_DAY as f64;
        Self::new(self.epoch, day_number)
    }
}

impl<T> Sub<&Duration> for JulianDayNumber<T> {
    type Output = JulianDayNumber<T>;

    fn sub(self, rhs: &Duration) -> Self::Output {
        let day_number = self.day_number - rhs.as_seconds_f64() / SECONDS_IN_DAY as f64;
        Self::new(self.epoch, day_number)
    }
}

impl<T> AddAssign<Duration> for JulianDayNumber<T> {
    fn add_assign(&mut self, rhs: Duration) {
        self.day_number += rhs.as_seconds_f64() / SECONDS_IN_DAY as f64;
    }
}

impl<T> AddAssign<&Duration> for JulianDayNumber<T> {
    fn add_assign(&mut self, rhs: &Duration) {
        self.day_number += rhs.as_seconds_f64() / SECONDS_IN_DAY as f64;
    }
}

impl<T> SubAssign<Duration> for JulianDayNumber<T> {
    fn sub_assign(&mut self, rhs: Duration) {
        self.day_number -= rhs.as_seconds_f64() / SECONDS_IN_DAY as f64;
    }
}

impl<T> SubAssign<&Duration> for JulianDayNumber<T> {
    fn sub_assign(&mut self, rhs: &Duration) {
        self.day_number -= rhs.as_seconds_f64() / SECONDS_IN_DAY as f64;
    }
}

impl<T> Sub<JulianDayNumber<T>> for JulianDayNumber<T> {
    type Output = Duration;

    fn sub(self, rhs: JulianDayNumber<T>) -> Self::Output {
        let dx = self.day_number - rhs.day_number;
        Duration::new(dx, DurationUnit::Day)
    }
}
impl<T> Sub<&JulianDayNumber<T>> for JulianDayNumber<T> {
    type Output = Duration;

    fn sub(self, rhs: &JulianDayNumber<T>) -> Self::Output {
        let dx = self.day_number - rhs.day_number;
        Duration::new(dx, DurationUnit::Day)
    }
}

impl From<UnixTimestamp> for JulianDate {
    fn from(value: UnixTimestamp) -> Self {
        let jd = value.get_offset().as_seconds_f64() / SECONDS_IN_DAY as f64 + UNIX_TS_JD_OFFSET;
        JulianDate::new(JULIAN_EPOCH, jd)
    }
}

impl From<JulianDate> for UnixTimestamp {
    fn from(value: JulianDate) -> Self {
        let ts = (value.day_number - UNIX_TS_JD_OFFSET) * SECONDS_IN_DAY as f64;
        UnixTimestamp::from_seconds_f64(ts)
    }
}

impl_julian!(ReducedJulianDate, REDUCED_JULIAN_EPOCH, REDUCED_JD_OFFSET);
impl_julian!(ModifiedJulianDate, REDUCED_JULIAN_EPOCH, MODIFIED_JD_OFFSET);
impl_julian!(
    TruncatedJulianDate,
    TRUNCATED_JULIAN_EPOCH,
    TRUNCATED_JD_OFFSET
);
impl_julian!(LilianDate, GREGORIAN_EPOCH, LILIAN_JD_OFFSET);
impl_julian!(RataDieDate, COMMON_ERA_EPOCH, RATA_DIE_JD_OFFSET);
impl_julian!(PrimeDate, PRIME_EPOCH, PRIME_JD_OFFSET);