#[allow(
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::cast_possible_truncation
)]
#[must_use]
pub fn eastern_offset_ms(epoch_ms: u64) -> i64 {
let epoch_secs = epoch_ms as i64 / 1_000;
let days_since_epoch = epoch_secs / 86_400;
let z = days_since_epoch + 719_468;
let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
let doe = (z - era * 146_097) as u32;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146_096) / 365;
let year = yoe as i32 + (era * 400) as i32;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let month = if mp < 10 { mp + 3 } else { mp - 9 };
let year = if month <= 2 { year + 1 } else { year };
let (dst_start_utc, dst_end_utc) = if year >= 2007 {
(
march_second_sunday_utc(year),
november_first_sunday_utc(year),
)
} else {
(april_first_sunday_utc(year), october_last_sunday_utc(year))
};
let epoch_ms_i64 = epoch_ms as i64;
if epoch_ms_i64 >= dst_start_utc && epoch_ms_i64 < dst_end_utc {
-4 * 3_600 * 1_000 } else {
-5 * 3_600 * 1_000 }
}
#[must_use]
pub fn march_second_sunday_utc(year: i32) -> i64 {
let mar1 = civil_to_epoch_days(year, 3, 1);
let dow = ((mar1 + 3) % 7 + 7) % 7;
let days_to_first_sunday = (6 - dow + 7) % 7; let second_sunday = mar1 + days_to_first_sunday + 7; second_sunday * 86_400_000 + 7 * 3_600 * 1_000 }
#[must_use]
pub fn november_first_sunday_utc(year: i32) -> i64 {
let nov1 = civil_to_epoch_days(year, 11, 1);
let dow = ((nov1 + 3) % 7 + 7) % 7;
let days_to_first_sunday = (6 - dow + 7) % 7;
let first_sunday = nov1 + days_to_first_sunday;
first_sunday * 86_400_000 + 6 * 3_600 * 1_000 }
#[must_use]
pub fn april_first_sunday_utc(year: i32) -> i64 {
let apr1 = civil_to_epoch_days(year, 4, 1);
let dow = ((apr1 + 3) % 7 + 7) % 7;
let days_to_first_sunday = (6 - dow + 7) % 7;
let first_sunday = apr1 + days_to_first_sunday;
first_sunday * 86_400_000 + 7 * 3_600 * 1_000 }
#[must_use]
pub fn october_last_sunday_utc(year: i32) -> i64 {
let oct31 = civil_to_epoch_days(year, 10, 31);
let dow = ((oct31 + 3) % 7 + 7) % 7; let days_back = (dow + 1) % 7; let last_sunday = oct31 - days_back;
last_sunday * 86_400_000 + 6 * 3_600 * 1_000 }
#[allow(clippy::cast_sign_loss, clippy::cast_possible_wrap)]
#[must_use]
pub fn civil_to_epoch_days(year: i32, month: u32, day: u32) -> i64 {
let y = if month <= 2 {
i64::from(year) - 1
} else {
i64::from(year)
};
let m = if month <= 2 {
i64::from(month) + 9
} else {
i64::from(month) - 3
};
let era = if y >= 0 { y } else { y - 399 } / 400;
let yoe = (y - era * 400) as u64;
let doy = (153 * m as u64 + 2) / 5 + u64::from(day) - 1;
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
era * 146_097 + doe as i64 - 719_468
}
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
#[must_use]
pub fn timestamp_to_ms_of_day(epoch_ms: u64) -> i32 {
let offset = eastern_offset_ms(epoch_ms);
let local_ms = epoch_ms as i64 + offset;
(local_ms.rem_euclid(86_400_000)) as i32
}
#[allow(
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::cast_possible_truncation
)]
#[must_use]
pub fn timestamp_to_date(epoch_ms: u64) -> i32 {
let offset = eastern_offset_ms(epoch_ms);
let local_secs = (epoch_ms as i64 + offset) / 1_000;
let days = local_secs / 86400 + 719_468;
let era = if days >= 0 { days } else { days - 146_096 } / 146_097;
let doe = (days - era * 146_097) as u32;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146_096) / 365;
let y = i64::from(yoe) + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let m = if mp < 10 { mp + 3 } else { mp - 9 };
let y = if m <= 2 { y + 1 } else { y };
(y as i32) * 10_000 + (m as i32) * 100 + (d as i32)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
fn timestamp_to_ms_of_day_edt() {
let epoch_ms: u64 = 1_775_050_200_000; let ms = timestamp_to_ms_of_day(epoch_ms);
assert_eq!(ms, 34_200_000, "9:30 AM ET in milliseconds");
}
#[test]
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
fn timestamp_to_ms_of_day_est() {
let epoch_ms: u64 = 1_768_487_400_000;
let ms = timestamp_to_ms_of_day(epoch_ms);
assert_eq!(ms, 34_200_000, "9:30 AM ET in milliseconds (winter)");
}
#[test]
fn timestamp_to_date_edt() {
let epoch_ms: u64 = 1_775_050_200_000; let date = timestamp_to_date(epoch_ms);
assert_eq!(date, 20260401);
}
#[test]
fn timestamp_to_date_est() {
let epoch_ms: u64 = 1_768_487_400_000; let date = timestamp_to_date(epoch_ms);
assert_eq!(date, 20260115);
}
#[test]
fn dst_transition_march_2026() {
let before: u64 = 1_772_953_140_000; assert_eq!(eastern_offset_ms(before), -5 * 3_600 * 1_000);
let after: u64 = 1_772_953_260_000; assert_eq!(eastern_offset_ms(after), -4 * 3_600 * 1_000);
}
#[test]
fn pre2007_dst_summer_uses_old_rules() {
let epoch_ms: u64 = 1_153_065_600_000; assert_eq!(
eastern_offset_ms(epoch_ms),
-4 * 3_600 * 1_000,
"mid-July 2006 should be EDT under old DST rules"
);
}
#[test]
fn pre2007_est_before_april_dst_start() {
let epoch_ms: u64 = 1_140_015_600_000; assert_eq!(
eastern_offset_ms(epoch_ms),
-5 * 3_600 * 1_000,
"mid-February 2006 should be EST under old DST rules"
);
}
}