126 lines
4.6 KiB
Rust
126 lines
4.6 KiB
Rust
//! Tests for UTC and TAI conversions on the Modified Julian Date scale.
|
||
//!
|
||
//! 约化儒略日尺度上 UTC 与 TAI 换算的测试。
|
||
|
||
use apsis_leap::{
|
||
MJD_AT_UNIX_EPOCH, calendar_mjd, tai_minus_utc_at_mjd, tai_mjd_from_utc_mjd,
|
||
utc_mjd_from_tai_mjd,
|
||
};
|
||
|
||
const SECONDS_PER_DAY: f64 = 86_400.0;
|
||
|
||
#[test]
|
||
fn unix_epoch_constant_matches_the_calendar_conversion() {
|
||
assert_eq!(MJD_AT_UNIX_EPOCH, calendar_mjd(1970, 1, 1));
|
||
}
|
||
|
||
#[test]
|
||
fn offset_is_rejected_before_the_table_begins() {
|
||
let before = calendar_mjd(1971, 12, 31);
|
||
assert_eq!(tai_minus_utc_at_mjd(before), None);
|
||
assert_eq!(tai_mjd_from_utc_mjd(before), None);
|
||
assert_eq!(tai_minus_utc_at_mjd(f64::NAN), None);
|
||
assert_eq!(tai_minus_utc_at_mjd(f64::INFINITY), None);
|
||
}
|
||
|
||
#[test]
|
||
fn first_entry_supplies_the_initial_offset() {
|
||
let start = calendar_mjd(1972, 1, 1);
|
||
assert_eq!(tai_minus_utc_at_mjd(start), Some(10.0));
|
||
// Just before midnight one day earlier the table does not apply.
|
||
// 再早一天、午夜之前,该表不适用。
|
||
assert_eq!(tai_minus_utc_at_mjd(start - 1.0), None);
|
||
}
|
||
|
||
#[test]
|
||
fn offset_changes_exactly_on_the_effective_date() {
|
||
// The 2017-01-01 leap second raised the offset from 36 to 37.
|
||
// 2017-01-01 的闰秒把偏移从 36 升到 37。
|
||
let effective = calendar_mjd(2017, 1, 1);
|
||
assert_eq!(tai_minus_utc_at_mjd(effective - 1.0), Some(36.0));
|
||
assert_eq!(tai_minus_utc_at_mjd(effective), Some(37.0));
|
||
assert_eq!(tai_minus_utc_at_mjd(effective + 0.5), Some(37.0));
|
||
}
|
||
|
||
#[test]
|
||
fn every_entry_is_reachable_at_its_effective_instant() {
|
||
for entry in apsis_leap::LEAP_SECONDS {
|
||
let at_entry = tai_minus_utc_at_mjd(entry.effective_mjd());
|
||
assert_eq!(at_entry, Some(entry.tai_minus_utc), "entry {entry:?}");
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn offset_is_constant_between_entries() {
|
||
// Mid 2016 sits between the 2015-07-01 and 2017-01-01 entries.
|
||
// 2016 年年中位于 2015-07-01 与 2017-01-01 两项之间。
|
||
let instant = calendar_mjd(2016, 6, 1);
|
||
assert_eq!(tai_minus_utc_at_mjd(instant), Some(36.0));
|
||
assert_eq!(tai_minus_utc_at_mjd(instant + 0.25), Some(36.0));
|
||
}
|
||
|
||
#[test]
|
||
fn future_instants_keep_the_last_offset() {
|
||
let far_future = calendar_mjd(2100, 1, 1);
|
||
assert_eq!(tai_minus_utc_at_mjd(far_future), Some(37.0));
|
||
}
|
||
|
||
#[test]
|
||
fn utc_to_tai_adds_the_offset() {
|
||
let utc = calendar_mjd(2017, 6, 1) + 0.5;
|
||
let tai = tai_mjd_from_utc_mjd(utc).expect("instant is inside the table");
|
||
// An MJD near 58000 has a resolution of about 7e-12 days, so the sum
|
||
// can only be compared to well below a microsecond.
|
||
// 接近 58000 的 MJD 分辨率约为 7e-12 天,故该和只能比较到远低于一微秒。
|
||
assert!((tai - utc - 37.0 / SECONDS_PER_DAY).abs() < 1e-11);
|
||
}
|
||
|
||
#[test]
|
||
fn utc_to_tai_round_trips() {
|
||
for (year, month, day) in [
|
||
(1972, 1, 1),
|
||
(1980, 1, 6),
|
||
(1999, 1, 1),
|
||
(2015, 7, 1),
|
||
(2017, 1, 1),
|
||
(2024, 6, 15),
|
||
] {
|
||
let utc = calendar_mjd(year, month, day) + 0.375;
|
||
let tai = tai_mjd_from_utc_mjd(utc).expect("instant is inside the table");
|
||
let back = utc_mjd_from_tai_mjd(tai).expect("instant is inside the table");
|
||
assert!(
|
||
(back - utc).abs() < 1e-10,
|
||
"round trip failed at {year}-{month}-{day}: {utc} vs {back}"
|
||
);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn tai_to_utc_recovers_the_offset_on_each_side_of_a_step() {
|
||
let effective = calendar_mjd(2017, 1, 1);
|
||
// One hour before the step the offset is 36 s, one hour after it is 37 s.
|
||
// 阶跃前一小时偏移为 36 s,其后一小时为 37 s。
|
||
let before_utc = effective - 1.0 / 24.0;
|
||
let after_utc = effective + 1.0 / 24.0;
|
||
let before_tai = tai_mjd_from_utc_mjd(before_utc).expect("inside the table");
|
||
let after_tai = tai_mjd_from_utc_mjd(after_utc).expect("inside the table");
|
||
assert!((utc_mjd_from_tai_mjd(before_tai).expect("inside") - before_utc).abs() < 1e-10);
|
||
assert!((utc_mjd_from_tai_mjd(after_tai).expect("inside") - after_utc).abs() < 1e-10);
|
||
}
|
||
|
||
#[test]
|
||
fn tai_to_utc_rejects_non_finite_input() {
|
||
assert_eq!(utc_mjd_from_tai_mjd(f64::NAN), None);
|
||
assert_eq!(utc_mjd_from_tai_mjd(f64::NEG_INFINITY), None);
|
||
}
|
||
|
||
#[test]
|
||
fn tai_to_utc_at_the_boundary_stays_inside_the_table() {
|
||
// The first TAI instant that the table covers is 1972-01-01 00:00:10
|
||
// TAI, because the offset there is ten seconds.
|
||
// 该表覆盖的第一个 TAI 瞬时是 1972-01-01 00:00:10 TAI,因为那里的偏移是十秒。
|
||
let boundary = calendar_mjd(1972, 1, 1) + 10.0 / SECONDS_PER_DAY;
|
||
let utc = utc_mjd_from_tai_mjd(boundary).expect("boundary is covered");
|
||
assert!((utc - calendar_mjd(1972, 1, 1)).abs() < 1e-10);
|
||
}
|