Skip to content

start working on datetime conversions; #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions rust/ast_utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub mod angular;
pub mod ast_math;
pub mod distance;
pub mod temperature;
pub mod time;
pub mod traits;
// simple re-usable helpers for unit tests
mod unit_test_helpers;
Expand Down
112 changes: 112 additions & 0 deletions rust/ast_utils/src/time.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
use crate::traits::*;
use std::fmt;

const JULIAN_DAY_LEAP: f64 = 1_720_994.5; // additional 0.5 means that day starts at midnight

pub fn is_leap_year(year: u16) -> bool {
match year.rem_euclid(4) {
0 if year.rem_euclid(100) == 0 && year.rem_euclid(400) != 0 => false,
0 => true,
_ => false,
}
}

pub struct DateTime {
year: i16,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
z: i8, // 1/24th from greenwich, kind of timezone
}

impl DateTime {
pub fn new(year: i16, month: u8, day: u8, hour: u8, minute: u8, second: u8, z: i8) -> Self {
assert!(month > 0 && month <= 12, "month must be in range 1..=12");
assert!(day > 0 && day <= 31, "day must be in range 1..=31");
assert!(hour < 24, "hour must be in range 0..=23");
assert!(minute < 60, "minute must be in range 0..=59");
assert!(second < 60, "second must be in range 0..=59");
assert!(z >= -12 && z <= 12, "zone must be in range -12..=12");

DateTime {
year,
month,
day,
hour,
minute,
second,
z,
}
}

pub fn from_date(year: i16, month: u8, day: u8) -> Self {
DateTime::new(year, month, day, 0, 0, 0, 0)
}

pub fn is_gregorian(&self) -> bool {
self.year > 1583 || (self.year == 1583 && self.month >= 10 && self.day >= 15)
}

pub fn fractional_day(&self) -> f64 {
(self.day as f64)
+ (self.hour as f64) / 24.0
+ (self.minute as f64) / (24.0 * 60.0)
+ (self.second as f64) / (24.0 * 60.0 * 60.0)
}

pub fn to_julian_day(&self) -> f64 {
let (y, m) = if self.month > 2 {
(self.year, self.month)
} else {
(self.year - 1, self.month + 12)
};

let t = if self.year < 0 { 0.75 } else { 0.0 };
let a = if self.is_gregorian() {
((self.year as f64) / 100.0).trunc()
} else {
0.0
};

let b = if self.is_gregorian() {
2.0 - a + (a / 4.0).trunc()
} else {
0.0
};

b + (365.25 * (y as f64) - t).trunc()
+ (30.6001 * (m + 1) as f64).trunc()
+ self.fractional_day()
+ JULIAN_DAY_LEAP
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::unit_test_helpers::*;

#[test]
fn test_leap_year() {
assert_eq!(false, is_leap_year(1906));
assert_eq!(true, is_leap_year(1908));
assert_eq!(false, is_leap_year(1800));
assert_eq!(true, is_leap_year(1600));
}

#[test]
fn test_to_julian_day() {
let dt = DateTime::from_date(2010, 1, 1);

assert_close(2_455_197.50, dt.to_julian_day());
}

#[test]
fn test_to_julian_day_with_fractional_day() {
let dt = DateTime::new(2015, 3, 21, 12, 0, 0, 0);

assert_close(2_457_103.0, dt.to_julian_day());
}
}
10 changes: 10 additions & 0 deletions rust/ast_utils/src/unit_test_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,13 @@ pub const DISTANCE_TOLERANCE: f64 = 0.1e-5;
pub fn is_close(expected_val: f64, real_val: f64) -> bool {
(expected_val - real_val).abs() <= DISTANCE_TOLERANCE
}

pub fn assert_close(expected_val: f64, real_val: f64) {
assert!(
is_close(expected_val, real_val),
"value {} differs from {} more than {}",
real_val,
expected_val,
DISTANCE_TOLERANCE
)
}