apsis-rs/crates/physics/apsis-time/tests/epoch.rs

211 lines
8.4 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Tests for the instant type and its scale conversions.
//!
//! 瞬时类型及其时标换算的测试。
use apsis_time::{Calendar, Epoch, SECONDS_PER_DAY, TAI_MINUS_GPS, TT_MINUS_TAI, TimeScale};
/// Tolerance for comparisons of Modified Julian Dates near the present day.
///
/// A double holds a Modified Julian Date near 60 000 with a resolution of
/// about 1e-11 days, roughly one microsecond. Conversions that add or
/// remove a scale offset each introduce one rounding at that resolution, so
/// comparisons are made at ten microseconds to leave room for a few steps.
///
/// 当今附近的约化儒略日比较的容差。
///
/// 双精度把接近 60 000 的约化儒略日保持到约 1e-11 天的分辨率,大致一微秒。每次
/// 加上或减去时标偏移的换算都会在该分辨率上引入一次舍入,故比较取十微秒,为若干
/// 步留出余量。
const MJD_TOL: f64 = 1e-9;
fn assert_mjd_close(actual: f64, expected: f64) {
assert!(
(actual - expected).abs() < MJD_TOL,
"expected {expected}, got {actual}"
);
}
#[test]
fn j2000_constant_lands_on_the_defining_instant() {
// 2000-01-01T12:00:00 TT is Julian Date 2451545.0, or MJD 51544.5.
// 2000-01-01T12:00:00 TT 是儒略日 2451545.0,即 MJD 51544.5。
let tt = Epoch::J2000
.mjd(TimeScale::Tt)
.expect("TT is always available");
assert_mjd_close(tt, 51_544.5);
}
#[test]
fn uniform_scales_differ_by_their_defining_offsets() {
let epoch = Epoch::from_mjd(60_000.0, TimeScale::Tai).expect("TAI is always available");
assert_mjd_close(
epoch.mjd(TimeScale::Tt).expect("TT is available"),
60_000.0 + TT_MINUS_TAI / SECONDS_PER_DAY,
);
assert_mjd_close(
epoch.mjd(TimeScale::Gps).expect("GPS is available"),
60_000.0 - TAI_MINUS_GPS / SECONDS_PER_DAY,
);
}
#[test]
fn construction_from_each_scale_round_trips() {
// 60 000.5 is a mid-day instant, which keeps the fractional part of
// every intermediate value exactly representable.
// 60 000.5 是一个日中的瞬时,这使每个中间值的零头都精确可表示。
let input = 60_000.5;
for scale in [
TimeScale::Tai,
TimeScale::Tt,
TimeScale::Gps,
TimeScale::Utc,
] {
let epoch = Epoch::from_mjd(input, scale).expect("instant is inside the leap table");
// Reading the instant back on the same scale recovers the number
// that was supplied.
// 在同一时标上读回该瞬时,恢复了所提供的那一数。
assert_mjd_close(epoch.mjd(scale).expect("scale is available"), input);
}
}
#[test]
fn utc_construction_applies_the_leap_second_offset() {
// In 2017 the offset TAI - UTC is 37 seconds.
// 2017 年 TAI - UTC 的偏移是 37 秒。
let utc = 60_000.0;
let epoch = Epoch::from_mjd(utc, TimeScale::Utc).expect("covered by the table");
assert_mjd_close(epoch.tai_mjd(), utc + 37.0 / SECONDS_PER_DAY);
}
#[test]
fn non_finite_input_is_rejected() {
assert_eq!(Epoch::from_mjd(f64::NAN, TimeScale::Tai), None);
assert_eq!(Epoch::from_mjd(f64::INFINITY, TimeScale::Tai), None);
}
#[test]
fn utc_conversion_is_rejected_before_the_table_begins() {
let before = 40_000.0;
assert_eq!(Epoch::from_mjd(before, TimeScale::Utc), None);
// The same instant is representable in TAI.
// 同一瞬时在 TAI 中可表示。
let epoch = Epoch::from_mjd(before, TimeScale::Tai).expect("TAI is always available");
assert_eq!(epoch.mjd(TimeScale::Utc), None);
}
#[test]
fn calendar_construction_and_read_back_agree() {
let calendar = Calendar::new(2024, 6, 15, 12, 30, 45.0);
let epoch = Epoch::from_calendar(calendar, TimeScale::Utc).expect("covered by the table");
let read_back = epoch
.calendar(TimeScale::Utc)
.expect("covered by the table");
assert_eq!(read_back.year, 2024);
assert_eq!(read_back.month, 6);
assert_eq!(read_back.day, 15);
assert_eq!(read_back.hour, 12);
assert_eq!(read_back.minute, 30);
assert!((read_back.second - 45.0).abs() < 1e-4);
}
#[test]
fn calendar_construction_rejects_impossible_dates() {
let calendar = Calendar::from_ymd(2023, 2, 29);
assert_eq!(Epoch::from_calendar(calendar, TimeScale::Tai), None);
}
#[test]
fn seconds_until_is_independent_of_the_scale_used() {
let start = Epoch::from_calendar(Calendar::from_ymd(2024, 1, 1), TimeScale::Utc)
.expect("covered by the table");
let end = Epoch::from_calendar(Calendar::from_ymd(2024, 1, 2), TimeScale::Utc)
.expect("covered by the table");
// One calendar day in early 2024 contains no leap second, so the
// elapsed time is exactly 86 400 seconds.
// 2024 年初的一个日历日不含闰秒,故流逝时间恰好是 86 400 秒。
assert!((start.seconds_until(end) - 86_400.0).abs() < 1e-6);
assert!((end.seconds_until(start) + 86_400.0).abs() < 1e-6);
}
#[test]
fn add_seconds_moves_along_the_uniform_scale() {
let epoch = Epoch::from_mjd(60_000.5, TimeScale::Tai).expect("TAI is always available");
let later = epoch.add_seconds(3_600.0);
assert!((epoch.seconds_until(later) - 3_600.0).abs() < 1e-3);
let earlier = epoch.add_seconds(-3_600.0);
assert!((earlier.seconds_until(epoch) - 3_600.0).abs() < 1e-3);
}
#[test]
fn add_seconds_crosses_a_leap_second_as_elapsed_time() {
// 2016-12-31T23:59:59.5 UTC, half a second before the inserted leap
// second. The leap second makes the next UTC midnight 1.5 SI seconds
// away, because the interval labelled 23:59:60 is a whole second long.
// 2016-12-31T23:59:59.5 UTC,即所插入闰秒之前的半秒。该闰秒使下一个 UTC 午夜
// 在 1.5 SI 秒之外,因为标为 23:59:60 的区间整整一秒长。
let before = Epoch::from_calendar(Calendar::new(2016, 12, 31, 23, 59, 59.5), TimeScale::Utc)
.expect("covered by the table");
// One and a half seconds of elapsed time reaches the next midnight.
// 一秒半的流逝时间到达下一个午夜。
let after = before.add_seconds(1.5);
let utc = after
.calendar(TimeScale::Utc)
.expect("covered by the table");
assert_eq!((utc.year, utc.month, utc.day), (2017, 1, 1));
assert!(utc.second.abs() < 1e-4, "got {}", utc.second);
// An instant strictly inside the leap second has no UTC label on the
// continuous Modified Julian Date scale, because it would be written
// 23:59:60 and that value does not exist there.
// 严格位于闰秒内部的瞬时,在连续的约化儒略日尺度上没有 UTC 标签,因为它会被
// 写成 23:59:60,而该值在那里不存在。
assert_eq!(before.add_seconds(1.0).mjd(TimeScale::Utc), None);
// Reading the interval on the UTC clock shows half a second of clock
// advance over one and a half seconds of elapsed time.
// 在 UTC 时钟上读该区间,显示一秒半的流逝时间里时钟推进了半秒。
let utc_before = before.mjd(TimeScale::Utc).expect("covered by the table");
let utc_after = after.mjd(TimeScale::Utc).expect("covered by the table");
let clock_advance = (utc_after - utc_before) * SECONDS_PER_DAY;
assert!((clock_advance - 0.5).abs() < 1e-3, "got {clock_advance}");
}
#[test]
fn ordering_follows_the_instant() {
let earlier = Epoch::from_mjd(60_000.0, TimeScale::Tai).expect("TAI is always available");
let later = earlier.add_seconds(1.0);
assert!(earlier < later);
assert!(later > earlier);
assert!(earlier <= earlier);
}
#[test]
fn equality_is_scale_independent() {
// The same physical instant described in two scales yields equal
// epochs.
// 在两种时标中描述的同一物理瞬时给出相等的历元。
let in_tai = Epoch::from_mjd(60_000.0, TimeScale::Tai).expect("TAI is always available");
let in_tt = Epoch::from_mjd(60_000.0 + TT_MINUS_TAI / SECONDS_PER_DAY, TimeScale::Tt)
.expect("TT is always available");
assert_eq!(in_tai, in_tt);
assert!(in_tai.seconds_until(in_tt).abs() < 1e-6);
}
#[test]
fn finiteness_is_reported() {
assert!(
Epoch::from_mjd(60_000.0, TimeScale::Tai)
.expect("always available")
.is_finite()
);
assert!(!Epoch::from_tai_mjd(f64::NAN).is_finite());
assert!(!Epoch::from_tai_mjd(f64::INFINITY).is_finite());
}
#[test]
fn display_reports_the_stored_scale() {
let epoch = Epoch::from_tai_mjd(60_000.5);
assert_eq!(epoch.to_string(), "60000.5 TAI");
}