pub fn latency_ns(exchange_ms_of_day: i32, event_date: i32, received_at_ns: u64) -> i64 {
let exchange_epoch_ns = exchange_epoch_ns(exchange_ms_of_day, event_date);
received_at_ns as i64 - exchange_epoch_ns
}
fn exchange_epoch_ns(ms_of_day: i32, date_yyyymmdd: i32) -> i64 {
let year = date_yyyymmdd / 10000;
let month = ((date_yyyymmdd % 10000) / 100) as u32;
let day = (date_yyyymmdd % 100) as u32;
let days = civil_to_epoch_days(year, month, day);
let midnight_utc_ms = days * 86_400_000;
let approx_utc_ms = midnight_utc_ms + ms_of_day as i64 + 5 * 3600 * 1000; let offset_ms = eastern_offset_ms(approx_utc_ms as u64);
let exchange_epoch_ms = midnight_utc_ms + ms_of_day as i64 - offset_ms;
exchange_epoch_ms * 1_000_000
}
fn civil_to_epoch_days(year: i32, month: u32, day: u32) -> i64 {
let y = if month <= 2 {
year as i64 - 1
} else {
year as i64
};
let m = if month <= 2 {
month as i64 + 9
} else {
month as i64 - 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 + day as u64 - 1;
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
era * 146097 + doe as i64 - 719468
}
fn eastern_offset_ms(epoch_ms: u64) -> i64 {
let epoch_secs = epoch_ms as i64 / 1000;
let days_since_epoch = epoch_secs / 86400;
let z = days_since_epoch + 719468;
let era = if z >= 0 { z } else { z - 146096 } / 146097;
let doe = (z - era * 146097) as u32;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 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 = march_second_sunday_utc(year);
let dst_end_utc = november_first_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 * 3600 * 1000 } else {
-5 * 3600 * 1000 }
}
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 * 3600 * 1000
}
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 * 3600 * 1000
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn latency_basic_est() {
let days = civil_to_epoch_days(2024, 1, 15);
let midnight_utc_ns = days * 86_400_000 * 1_000_000;
let utc_offset_ns = (14 * 3600 + 30 * 60) as i64 * 1_000_000_000;
let received_at_ns = (midnight_utc_ns + utc_offset_ns) as u64;
let lat = latency_ns(34_200_000, 20240115, received_at_ns);
assert!(
lat.abs() < 1_000_000, "expected ~0 latency, got {lat} ns"
);
}
#[test]
fn latency_basic_edt() {
let days = civil_to_epoch_days(2024, 6, 15);
let midnight_utc_ns = days * 86_400_000 * 1_000_000;
let utc_offset_ns = (13 * 3600 + 30 * 60) as i64 * 1_000_000_000;
let received_at_ns = (midnight_utc_ns + utc_offset_ns) as u64;
let lat = latency_ns(34_200_000, 20240615, received_at_ns);
assert!(lat.abs() < 1_000_000, "expected ~0 latency, got {lat} ns");
}
#[test]
fn latency_positive_when_received_later() {
let days = civil_to_epoch_days(2024, 1, 15);
let midnight_utc_ns = days * 86_400_000 * 1_000_000;
let utc_offset_ns = (14 * 3600 + 30 * 60) as i64 * 1_000_000_000;
let received_at_ns = (midnight_utc_ns + utc_offset_ns) as u64 + 100_000_000;
let lat = latency_ns(34_200_000, 20240115, received_at_ns);
assert!(
(lat - 100_000_000).abs() < 1_000_000,
"expected ~100ms latency, got {lat} ns"
);
}
#[test]
fn latency_negative_when_clock_skewed() {
let days = civil_to_epoch_days(2024, 1, 15);
let midnight_utc_ns = days * 86_400_000 * 1_000_000;
let utc_offset_ns = (14 * 3600 + 30 * 60) as i64 * 1_000_000_000;
let received_at_ns = (midnight_utc_ns + utc_offset_ns) as u64 - 50_000_000;
let lat = latency_ns(34_200_000, 20240115, received_at_ns);
assert!(
(lat + 50_000_000).abs() < 1_000_000,
"expected ~-50ms latency, got {lat} ns"
);
}
}