Skip to content
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

optimise gregorian #160

Merged
merged 2 commits into from
Oct 16, 2022
Merged
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: 0 additions & 1 deletion benches/bench_epoch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ pub fn criterion_benchmark(c: &mut Criterion) {

c.bench_function("TT", |b| {
b.iter(|| {
// TT is too slow now! Used to be 184ns, is now 241
let e = Epoch::from_gregorian_utc_hms(2015, 2, 7, 11, 22, 33);
e.as_tt_seconds();

Expand Down
61 changes: 41 additions & 20 deletions src/epoch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,17 @@ const fn usual_days_per_month(month: u8) -> u8 {
}
}

/// Calculates the prefix-sum of days counted up to the month start
const CUMULATIVE_DAYS_FOR_MONTH: [u16; 12] = {
let mut days = [0; 12];
let mut month = 1;
while month < 12 {
days[month] = days[month - 1] + usual_days_per_month(month as u8 - 1) as u16;
month += 1;
}
days
};

/// Defines a nanosecond-precision Epoch.
///
/// Refer to the appropriate functions for initializing this Epoch from different time systems or representations.
Expand Down Expand Up @@ -555,28 +566,30 @@ impl Epoch {
return Err(Errors::Carry);
}

let mut duration_wrt_1900 = Unit::Day * i64::from(365 * (year - 1900));

// Now add the leap days for all the years prior to the current year
if year >= 1900 {
for year in 1900..year {
if is_leap_year(year) {
duration_wrt_1900 += Unit::Day;
}
}
let years_since_1900 = year - 1900;
let mut duration_wrt_1900 = Unit::Day * i64::from(365 * years_since_1900);

// count leap years
if years_since_1900 > 0 {
// we don't count the leap year in 1904, since jan 1904 hasn't had the leap yet,
// so we push it back to 1905, same for all other leap years
let years_after_1900 = years_since_1900 - 1;
duration_wrt_1900 += Unit::Day * i64::from(years_after_1900 / 4);
duration_wrt_1900 -= Unit::Day * i64::from(years_after_1900 / 100);
// every 400 years we correct our correction. The first one after 1900 is 2000 (years_since_1900 = 100)
// so we add 300 to correct the offset
duration_wrt_1900 += Unit::Day * i64::from((years_after_1900 + 300) / 400);
} else {
// Remove days
for year in year..1900 {
if is_leap_year(year) {
duration_wrt_1900 -= Unit::Day;
}
}
}
// we don't need to fix the offset, since jan 1896 has had the leap, when counting back from 1900
duration_wrt_1900 += Unit::Day * i64::from(years_since_1900 / 4);
duration_wrt_1900 -= Unit::Day * i64::from(years_since_1900 / 100);
// every 400 years we correct our correction. The first one before 1900 is 1600 (years_since_1900 = -300)
// so we subtract 100 to correct the offset
duration_wrt_1900 += Unit::Day * i64::from((years_since_1900 - 100) / 400);
};

// Add the seconds for the months prior to the current month
for month in 0..month - 1 {
duration_wrt_1900 += Unit::Day * i64::from(usual_days_per_month(month));
}
duration_wrt_1900 += Unit::Day * i64::from(CUMULATIVE_DAYS_FOR_MONTH[(month - 1) as usize]);
if is_leap_year(year) && month > 2 {
// NOTE: If on 29th of February, then the day is not finished yet, and therefore
// the extra seconds are added below as per a normal day.
Expand Down Expand Up @@ -2297,7 +2310,7 @@ impl fmt::Octal for Epoch {

#[must_use]
/// Returns true if the provided Gregorian date is valid. Leap second days may have 60 seconds.
pub fn is_gregorian_valid(
pub const fn is_gregorian_valid(
year: i32,
month: u8,
day: u8,
Expand Down Expand Up @@ -2418,3 +2431,11 @@ fn deser_test() {

println!("{}", (1 * Unit::Century + 12 * Unit::Hour).to_seconds());
}

#[test]
fn cumulative_days_for_month() {
assert_eq!(
CUMULATIVE_DAYS_FOR_MONTH,
[0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
)
}