commit c4045c9142cbe596184c8c67d404bc655a8f987a Author: YK Date: Sat Dec 7 06:12:33 2024 +0300 initial commit: basic julian/gregorian tables + julian/gregorian date ⇔ julian day conversion diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..10803a7 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "esodate" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +chrono = "0.4.26" +thiserror = "1.0.47" diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..f32b443 --- /dev/null +++ b/TODO.md @@ -0,0 +1,3 @@ +- [ ] use TryFrom for converting JulianDay into NaiveJulianDate instead of From + - [ ] Sanity checks for NJD are needed (?) + - [ ] Change trait impl diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..85e6e4b --- /dev/null +++ b/src/error.rs @@ -0,0 +1,8 @@ +use thiserror::Error; + + +#[derive(Error, Debug)] +pub enum ConversionError { + #[error("Error during date conversion")] + Generic +} diff --git a/src/julian.rs b/src/julian.rs new file mode 100644 index 0000000..0fc51b6 --- /dev/null +++ b/src/julian.rs @@ -0,0 +1,171 @@ +#![allow(non_snake_case)] + +use chrono::{ NaiveTime, NaiveDate, Datelike }; +use crate::prelude::*; + +#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone, Debug)] +pub struct NaiveJulianDate { + year: i32, + day: u32, + month: u32, +} + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +struct JulianDayNumber (u32); + +impl From for JulianDayNumber { + fn from (value: NaiveDate) -> Self { + use tables::julian_gregorian::*; + + let D = value.day(); + let M = value.month(); + let Y = value.year(); + + + let h = M as i32 - m; + let g = Y as i32 + y - (n - h) / n; + let f = (h - 1 + n) % n; + let e = (p * g + q) / r + D as i32 - 1 - j; + + let J = e + (s * f + t) / u; + + let J = J - (3 * ((g + A) / 100)) / 4 - C; + + Self(J as u32) + } +} + +impl From for JulianDayNumber { + fn from (NaiveJulianDate { day: D, year: Y, month: M }: NaiveJulianDate) -> Self { + use tables::julian_gregorian::*; + + let h = M as i32 - m; + let g = Y as i32 + y - (n - h) / n; + let f = (h - 1 + n) % n; + let e = (p * g + q) / r + D as i32 - 1 - j; + + let J = e + (s * f + t) / u; + + Self(J as u32) + } +} + +impl From for NaiveJulianDate { + fn from (JulianDayNumber(J): JulianDayNumber) -> Self { + use tables::julian_gregorian::*; + + let f = J as i32 + j; + let e = r * f + v; + let g = e % p / r; + let h = u * g + w; + + let day = (h % s / u + 1) as u32; + let month = ((h / s + m) % n + 1) as u32; + let year = e / p - y + (n + m - month as i32) / n; + + Self { year, day, month } + } +} + + +impl TryFrom for NaiveDate { + type Error = ConversionError; + fn try_from (JulianDayNumber(J): JulianDayNumber) -> Result { + use tables::julian_gregorian::*; + + let f = J as i32 + j + (((J as i32 * 4 + B) / 146_097) * 3) / 4 + C; + let e = r * f + v; + let g = e % p / r; + let h = u * g + w; + + let day = (h % s / u + 1) as u32; + let month = ((h / s + m) % n + 1) as u32; + let year = e / p - y + (n + m - month as i32) / n; + + Self::from_ymd_opt(year, month, day).ok_or_else(|| ConversionError::Generic) + } +} + +impl From for NaiveJulianDate { + fn from (value: NaiveDate) -> Self { + Into::::into(value).into() + } +} + +impl TryFrom for NaiveDate { + type Error = ConversionError; + fn try_from (value: NaiveJulianDate) -> Result { + Into::::into(value).try_into() + } +} + +#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] +pub struct NaiveJulianDateTime { + pub date: NaiveJulianDate, + pub time: NaiveTime, +} + + +#[cfg(test)] +#[allow(non_upper_case_globals)] +#[allow(unused_imports)] +mod test { + use super::*; + use crate::prelude::*; + + const _jdt: NaiveJulianDate = NaiveJulianDate { year: 2024, day: 23, month: 11 }; + const _jd: JulianDayNumber = JulianDayNumber(2460651); + + fn gd () -> NaiveDate { + NaiveDate::from_ymd_opt(2024, 12, 6).unwrap() + } + + #[test] + fn gregorian_date_to_julian_day_basic () { + let gd = gd(); + let jd: JulianDayNumber = gd.into(); + + assert_eq!(_jd, jd); + } + + #[test] + fn julian_day_to_julian_date_basic () { + let jdt: NaiveJulianDate = _jd.into(); + + assert_eq!(_jdt, jdt); + } + + #[test] + fn gregorian_date_to_julian_day_to_gregorian_date_basic () { + let gd = gd(); + let jd: JulianDayNumber = gd.into(); + let nd: NaiveDate = jd.try_into().unwrap(); + + assert_eq!(gd, nd); + } + + + #[test] + fn julian_date_to_julian_day_to_julian_date_basic () { + let jd: JulianDayNumber = _jdt.into(); + let nd: NaiveJulianDate = jd.try_into().unwrap(); + + assert_eq!(_jdt, nd); + } + + #[test] + fn gregorian_date_to_julian_date_basic () { + let gd = gd(); + let jd: NaiveJulianDate = gd.into(); + + assert_eq!(_jdt, jd); + } + + #[test] + fn julian_date_to_gregorian_date_basic () { + let _gd = gd(); + let gd: NaiveDate = _jdt.try_into().unwrap(); + + assert_eq!(gd, _gd); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..0086738 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,9 @@ +pub mod tables; +pub mod julian; +pub mod error; + +pub mod prelude { + pub use crate::tables; + pub use crate::error::*; +} + diff --git a/src/tables.rs b/src/tables.rs new file mode 100644 index 0000000..9906bee --- /dev/null +++ b/src/tables.rs @@ -0,0 +1,21 @@ +#![allow(non_upper_case_globals)] +#![allow(non_snake_case)] + +pub mod julian_gregorian { + // Taken from Explanatory Supplement to the Astronomical Almanac, 3rd Edition, p. 617 + pub const y: i32 = 4716; + pub const j: i32 = 1401; + pub const m: i32 = 2; + pub const n: i32 = 12; + pub const r: i32 = 4; + pub const p: i32 = 1461; + pub const q: i32 = 0; + pub const v: i32 = 3; + pub const u: i32 = 5; + pub const s: i32 = 153; + pub const t: i32 = 2; + pub const w: i32 = 2; + pub const A: i32 = 184; + pub const B: i32 = 274244; + pub const C: i32 = -38; +}