#[cfg(feature = "std")]
extern crate core;
use hifitime::{
is_gregorian_valid, Duration, Epoch, Errors, ParsingErrors, TimeScale, TimeUnits, Unit,
Weekday, BDT_REF_EPOCH, DAYS_GPS_TAI_OFFSET, GPST_REF_EPOCH, GST_REF_EPOCH, J1900_OFFSET,
J1900_REF_EPOCH, J2000_OFFSET, MJD_OFFSET, SECONDS_BDT_TAI_OFFSET, SECONDS_GPS_TAI_OFFSET,
SECONDS_GST_TAI_OFFSET, SECONDS_PER_DAY,
};
use hifitime::efmt::{Format, Formatter};
#[cfg(feature = "std")]
use core::f64::EPSILON;
#[cfg(not(feature = "std"))]
use std::f64::EPSILON;
#[test]
fn test_const_ops() {
let mjd_offset = MJD_OFFSET * Unit::Day;
assert!((mjd_offset.to_unit(Unit::Day) - MJD_OFFSET).abs() < f64::EPSILON);
let j2000_offset = J2000_OFFSET * Unit::Day;
assert!((j2000_offset.to_unit(Unit::Day) - J2000_OFFSET).abs() < f64::EPSILON);
}
#[allow(clippy::float_equality_without_abs)]
#[test]
fn utc_epochs() {
assert!(Epoch::from_mjd_tai(J1900_OFFSET).to_tai_seconds() < EPSILON);
assert!((Epoch::from_mjd_tai(J1900_OFFSET).to_mjd_tai_days() - J1900_OFFSET).abs() < EPSILON);
let this_epoch = Epoch::from_tai_seconds(1_199_333_568.0);
let epoch_utc = Epoch::maybe_from_gregorian_utc(1938, 1, 3, 4, 12, 48, 0).expect("init epoch");
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
let this_epoch = Epoch::from_tai_seconds(1_204_156_800.0);
let epoch_utc =
Epoch::maybe_from_gregorian_utc(1938, 2, 28, 00, 00, 00, 0).expect("init epoch");
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
let this_epoch = Epoch::from_tai_seconds(1_204_243_199.0);
let epoch_utc =
Epoch::maybe_from_gregorian_utc(1938, 2, 28, 23, 59, 59, 0).expect("init epoch");
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
let this_epoch = Epoch::from_tai_seconds(1_204_243_200.0);
let epoch_utc = Epoch::maybe_from_gregorian_utc(1938, 3, 1, 00, 00, 00, 0).expect("init epoch");
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
let this_epoch = Epoch::from_tai_seconds(1_206_850_368.0);
let epoch_utc = Epoch::maybe_from_gregorian_utc(1938, 3, 31, 4, 12, 48, 0).expect("init epoch");
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
let this_epoch = Epoch::from_tai_seconds(1_214_194_368.0);
let epoch_utc = Epoch::maybe_from_gregorian_utc(1938, 6, 24, 4, 12, 48, 0).expect("init epoch");
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
let this_epoch = Epoch::from_tai_seconds(1_220_069_568.0);
let epoch_utc = Epoch::maybe_from_gregorian_utc(1938, 8, 31, 4, 12, 48, 0).expect("init epoch");
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
let this_epoch = Epoch::from_tai_seconds(1_230_610_368.0);
let epoch_utc =
Epoch::maybe_from_gregorian_utc(1938, 12, 31, 4, 12, 48, 0).expect("init epoch");
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
let this_epoch = Epoch::from_tai_seconds(1_230_696_768.0);
let epoch_utc = Epoch::maybe_from_gregorian_utc(1939, 1, 1, 4, 12, 48, 0).expect("init epoch");
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
let this_epoch = Epoch::from_tai_seconds(1_235_794_368.0);
let epoch_utc = Epoch::maybe_from_gregorian_utc(1939, 3, 1, 4, 12, 48, 0).expect("init epoch");
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
let this_epoch = Epoch::from_tai_seconds(1_267_416_768.0);
let epoch_utc = Epoch::maybe_from_gregorian_utc(1940, 3, 1, 4, 12, 48, 0).expect("init epoch");
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
let this_epoch = Epoch::from_tai_seconds(1_233_375_168.0);
let epoch_utc = Epoch::maybe_from_gregorian_utc(1939, 2, 1, 4, 12, 48, 0).expect("init epoch");
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
let this_epoch = Epoch::from_tai_seconds(1_264_911_168.0);
let epoch_utc = Epoch::maybe_from_gregorian_utc(1940, 2, 1, 4, 12, 48, 0).expect("init epoch");
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
let this_epoch = Epoch::from_tai_seconds(1_267_243_968.0);
let epoch_utc = Epoch::maybe_from_gregorian_utc(1940, 2, 28, 4, 12, 48, 0).expect("init epoch");
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
let this_epoch = Epoch::from_tai_seconds(1_267_330_368.0);
let epoch_utc = Epoch::maybe_from_gregorian_utc(1940, 2, 29, 4, 12, 48, 0).expect("init epoch");
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
let epoch_from_tai_secs = Epoch::from_gregorian_tai_at_midnight(1972, 1, 1);
assert!(epoch_from_tai_secs.to_tai_seconds() - 2_272_060_800.0 < EPSILON);
let epoch_from_tai_greg = Epoch::from_tai_seconds(2_272_060_800.0);
assert_eq!(epoch_from_tai_greg, epoch_from_tai_secs, "Incorrect epoch");
let epoch_from_utc_greg = Epoch::from_gregorian_utc_hms(1972, 6, 30, 23, 59, 59);
let epoch_from_utc_greg1 = Epoch::from_gregorian_utc_hms(1972, 7, 1, 0, 0, 0);
assert!(
(epoch_from_utc_greg1.to_tai_seconds() - epoch_from_utc_greg.to_tai_seconds() - 2.0).abs()
< EPSILON
);
let this_epoch = Epoch::from_tai_seconds(3_692_217_599.0);
let epoch_utc = Epoch::from_gregorian_utc_hms(2016, 12, 31, 23, 59, 23);
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
assert!(this_epoch.to_tai_seconds() - epoch_utc.to_utc_seconds() - 36.0 < EPSILON);
let this_epoch = Epoch::from_tai_seconds(3_692_217_600.0);
let epoch_utc = Epoch::from_gregorian_utc_hms(2016, 12, 31, 23, 59, 24);
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
assert!(this_epoch.to_tai_seconds() - epoch_utc.to_utc_seconds() - 37.0 < EPSILON);
let mut this_epoch = Epoch::from_tai_seconds(3_692_217_600.0);
let epoch_utc = Epoch::from_gregorian_utc_hms(2016, 12, 31, 23, 59, 24);
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch");
this_epoch += Unit::Second * 3600.0;
assert_eq!(
this_epoch,
Epoch::from_gregorian_utc_hms(2017, 1, 1, 0, 59, 23),
"Incorrect epoch when adding an hour across leap second"
);
this_epoch -= Unit::Hour;
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch after sub");
this_epoch += Unit::Hour;
this_epoch -= 1 * Unit::Hour;
assert_eq!(epoch_utc, this_epoch, "Incorrect epoch after sub");
let this_epoch = Epoch::from_gregorian_tai_at_midnight(2020, 1, 1);
assert!((this_epoch.to_jde_tai_days() - 2_458_849.5).abs() < EPSILON)
}
#[allow(clippy::float_equality_without_abs)]
#[test]
fn utc_tai() {
let flp_from_secs_tai = Epoch::from_tai_seconds(2_272_060_800.0);
let flp_from_greg_tai = Epoch::from_gregorian_tai_at_midnight(1972, 1, 1);
assert_eq!(flp_from_secs_tai, flp_from_greg_tai);
assert_eq!(
Epoch::from_gregorian_tai_hms(1972, 1, 1, 0, 0, 10),
Epoch::from_gregorian_utc_at_midnight(1972, 1, 1),
"UTC discontinuity failed"
);
assert!(
Epoch::from_gregorian_utc_at_noon(1972, 1, 1)
> Epoch::from_gregorian_tai_at_noon(1972, 1, 1),
"TAI is not ahead of UTC (via PartialEq) at noon after first leap second"
);
assert_eq!(
Epoch::from_gregorian_utc_at_noon(1972, 1, 1)
.min(Epoch::from_gregorian_tai_at_noon(1972, 1, 1)),
Epoch::from_gregorian_tai_at_noon(1972, 1, 1),
"TAI is not ahead of UTC (via PartialEq) at noon after first leap second"
);
assert_eq!(
Epoch::from_gregorian_utc_at_noon(1972, 1, 1)
.max(Epoch::from_gregorian_tai_at_noon(1972, 1, 1)),
Epoch::from_gregorian_utc_at_noon(1972, 1, 1),
"TAI is not ahead of UTC (via PartialEq) at noon after first leap second"
);
assert!(
flp_from_secs_tai.to_tai_seconds() > flp_from_secs_tai.to_utc_seconds(),
"TAI is not ahead of UTC (via function call)"
);
assert!(
(flp_from_secs_tai.to_tai_seconds() - flp_from_secs_tai.to_utc_seconds() - 10.0) < EPSILON,
"TAI is not ahead of UTC"
);
let epoch_utc = Epoch::from_gregorian_utc_hms(2019, 8, 1, 20, 10, 23);
let epoch_tai = Epoch::from_gregorian_tai_hms(2019, 8, 1, 20, 10, 23);
assert!(epoch_tai < epoch_utc, "TAI is not ahead of UTC");
let delta: Duration = epoch_utc - epoch_tai - Unit::Second * 37.0;
assert!(delta < Unit::Nanosecond, "TAI is not ahead of UTC");
assert!(
(epoch_utc.to_tai_seconds() - epoch_tai.to_tai_seconds() - 37.0).abs() < EPSILON,
"TAI is not ahead of UTC"
);
assert!(
(epoch_utc.to_utc_seconds() - epoch_tai.to_utc_seconds() - 37.0).abs() < EPSILON,
"TAI is not ahead of UTC"
);
assert_eq!(
Epoch::from_gregorian_utc_at_midnight(1972, 1, 1),
Epoch::from_utc_seconds(2_272_060_800.0)
);
assert_eq!(
Epoch::from_gregorian_utc_hms(1972, 1, 1, 0, 0, 10),
Epoch::from_utc_seconds(2_272_060_810.0)
);
assert_eq!(
Epoch::from_gregorian_utc_at_midnight(1972, 1, 1),
Epoch::from_utc_days(26297.0)
);
let now = Epoch::from_gregorian_tai_hms(2019, 8, 24, 3, 49, 9);
assert!(
now.to_tai_seconds() > now.to_utc_seconds(),
"TAI is not ahead of UTC"
);
assert!((now.to_tai_seconds() - now.to_utc_seconds() - 37.0).abs() < EPSILON);
assert!(
now.to_tai_seconds() > now.to_gpst_seconds(),
"TAI is not ahead of GPS Time"
);
}
#[test]
fn julian_epoch() {
let nist_j1900 = Epoch::from_tai_days(0.0);
assert!((nist_j1900.to_mjd_tai_days() - 15_020.0).abs() < EPSILON);
assert!((nist_j1900.to_jde_tai_days() - 2_415_020.5).abs() < EPSILON);
let mjd = Epoch::from_gregorian_utc_at_midnight(1900, 1, 1);
assert!((mjd.to_mjd_tai_days() - 15_020.0).abs() < EPSILON);
let j1900 = Epoch::from_tai_days(0.5);
assert!((j1900.to_mjd_tai_days() - 15_020.5).abs() < EPSILON);
assert!((j1900.to_jde_tai_days() - 2_415_021.0).abs() < EPSILON);
let mjd = Epoch::from_gregorian_utc_at_noon(1900, 1, 1);
assert!((mjd.to_mjd_tai_days() - 15_020.5).abs() < EPSILON);
let mjd = Epoch::from_gregorian_utc_at_midnight(1900, 1, 8);
assert!((mjd.to_mjd_tai_days() - 15_027.0).abs() < EPSILON);
assert!((mjd.to_jde_tai_days() - 2_415_027.5).abs() < EPSILON);
let gps_std_epoch = Epoch::from_gregorian_tai_at_midnight(1980, 1, 6);
assert!((gps_std_epoch.to_mjd_tai_days() - 44_244.0).abs() < EPSILON);
assert!((gps_std_epoch.to_jde_tai_days() - 2_444_244.5).abs() < EPSILON);
let j2000 = Epoch::from_gregorian_tai_at_midnight(2000, 1, 1);
assert!((j2000.to_mjd_tai_days() - 51_544.0).abs() < EPSILON);
assert!((j2000.to_jde_tai_days() - 2_451_544.5).abs() < EPSILON);
assert!(
Epoch::from_gregorian_tai_at_midnight(2000, 1, 1)
< Epoch::from_gregorian_utc_at_midnight(2000, 1, 1),
"TAI not ahead of UTC on J2k"
);
assert_eq!(
(Epoch::from_gregorian_utc_at_midnight(2000, 1, 1)
- Epoch::from_gregorian_tai_at_midnight(2000, 1, 1)),
Unit::Second * 32.0
);
let j2000 = Epoch::from_gregorian_utc_at_midnight(2000, 1, 1);
assert!((j2000.to_mjd_utc_days() - 51_544.0).abs() < EPSILON);
assert!((j2000.to_jde_utc_days() - 2_451_544.5).abs() < EPSILON);
let jd020207 = Epoch::from_gregorian_tai_at_midnight(2002, 2, 7);
assert!((jd020207.to_mjd_tai_days() - 52_312.0).abs() < EPSILON);
assert!((jd020207.to_jde_tai_days() - 2_452_312.5).abs() < EPSILON);
assert!(
(Epoch::from_gregorian_tai_hms(2015, 6, 30, 23, 59, 59).to_mjd_tai_days()
- 57_203.999_988_425_92)
.abs()
< EPSILON,
"Incorrect July 2015 leap second MJD computed"
);
assert!(
(Epoch::from_gregorian_tai_hms(2015, 6, 30, 23, 59, 60).to_mjd_tai_days()
- 57_203.999_988_425_92)
.abs()
< EPSILON,
"Incorrect July 2015 leap second MJD computed"
);
assert!(
(Epoch::from_gregorian_tai_at_midnight(2015, 7, 1).to_mjd_tai_days() - 57_204.0).abs()
< EPSILON,
"Incorrect Post July 2015 leap second MJD computed"
);
}
#[test]
fn datetime_invalid_dates() {
assert!(!is_gregorian_valid(2001, 2, 29, 22, 8, 47, 0));
assert!(!is_gregorian_valid(2016, 12, 31, 23, 59, 61, 0));
assert!(!is_gregorian_valid(2015, 6, 30, 23, 59, 61, 0));
}
#[test]
fn gpst() {
let ref_gps = Epoch::from_gregorian_utc_at_midnight(1980, 01, 06);
let gnss = Epoch::from_gpst_seconds(1.0);
assert_eq!(gnss, ref_gps + 1.0 * Unit::Second);
let gnss = Epoch::from_gpst_days(1.5);
assert_eq!(gnss, ref_gps + 1.5 * Unit::Day);
let now = Epoch::from_gregorian_tai_hms(2019, 8, 24, 3, 49, 9);
assert_eq!(
Epoch::from_gpst_nanoseconds(now.to_gpst_nanoseconds().unwrap()),
now,
"To/from (recip.) GPST nanoseconds failed"
);
assert!(
(now.to_tai_seconds() - SECONDS_GPS_TAI_OFFSET - now.to_gpst_seconds()).abs() < EPSILON
);
assert!(
now.to_gpst_seconds() + SECONDS_GPS_TAI_OFFSET > now.to_utc_seconds(),
"GPS Time is not ahead of UTC"
);
let gps_epoch = Epoch::from_tai_seconds(SECONDS_GPS_TAI_OFFSET);
assert_eq!(format!("{}", GPST_REF_EPOCH), "1980-01-06T00:00:00 UTC");
assert_eq!(format!("{:x}", GPST_REF_EPOCH), "1980-01-06T00:00:19 TAI");
assert_eq!(format!("{:o}", gps_epoch), "0");
assert_eq!(
Epoch::from_gpst_days(0.0).to_duration_since_j1900(),
gps_epoch.duration_since_j1900_tai
);
assert_eq!(
gps_epoch.to_tai_seconds(),
Epoch::from_gregorian_utc_at_midnight(1980, 1, 6).to_tai_seconds()
);
assert!(
gps_epoch.to_gpst_seconds().abs() < EPSILON,
"The number of seconds from the GPS epoch was not 0: {}",
gps_epoch.to_gpst_seconds()
);
assert!(
gps_epoch.to_gpst_days().abs() < EPSILON,
"The number of days from the GPS epoch was not 0: {}",
gps_epoch.to_gpst_days()
);
let epoch = Epoch::from_gregorian_utc_at_midnight(1972, 1, 1);
assert!(
(epoch.to_tai_seconds() - SECONDS_GPS_TAI_OFFSET - epoch.to_gpst_seconds()).abs() < EPSILON
);
assert!((epoch.to_tai_days() - DAYS_GPS_TAI_OFFSET - epoch.to_gpst_days()).abs() < 1e-11);
let epoch = Epoch::from_gregorian_utc_at_midnight(1980, 1, 1);
assert!((epoch.to_gpst_seconds() + 5.0 * SECONDS_PER_DAY).abs() < EPSILON);
assert!((epoch.to_gpst_days() + 5.0).abs() < EPSILON);
}
#[test]
fn galileo_time_scale() {
let now = Epoch::from_gregorian_tai_hms(2019, 8, 24, 3, 49, 9);
let gst_nanos = now.to_gst_nanoseconds().unwrap();
assert_eq!(
Epoch::from_gst_nanoseconds(gst_nanos),
now,
"To/from (recip.) GPST nanoseconds failed"
);
assert!((now.to_tai_seconds() - SECONDS_GST_TAI_OFFSET - now.to_gst_seconds()).abs() < EPSILON);
assert!(
now.to_gst_seconds() + SECONDS_GST_TAI_OFFSET > now.to_utc_seconds(),
"GST Time is not ahead of UTC"
);
let ref_gst = Epoch::from_gst_nanoseconds(0);
assert_eq!(format!("{}", ref_gst), "1999-08-21T23:59:47 UTC");
let gst_epoch = Epoch::from_tai_seconds(SECONDS_GST_TAI_OFFSET);
assert_eq!(gst_epoch, Epoch::from_gst_days(0.0));
assert_eq!(gst_epoch, Epoch::from_gst_seconds(0.0));
assert_eq!(gst_epoch, Epoch::from_gst_nanoseconds(0));
assert_eq!(gst_epoch, GST_REF_EPOCH);
assert_eq!(format!("{}", GST_REF_EPOCH), "1999-08-21T23:59:47 UTC");
assert_eq!(format!("{:x}", GST_REF_EPOCH), "1999-08-22T00:00:19 TAI");
assert_eq!(
Epoch::from_gst_days(0.0).to_duration_since_j1900(),
gst_epoch.duration_since_j1900_tai
);
assert_eq!(
gst_epoch.to_tai_seconds(),
Epoch::from_gregorian_utc_at_midnight(1999, 08, 22).to_tai_seconds() - 13.0
);
assert!(
gst_epoch.to_gst_seconds().abs() < EPSILON,
"The number of seconds from the GST epoch was not 0: {}",
gst_epoch.to_gst_seconds()
);
assert!(
gst_epoch.to_gst_days().abs() < EPSILON,
"The number of days from the GST epoch was not 0: {}",
gst_epoch.to_gst_days()
);
}
#[test]
fn beidou_time_scale() {
let now = Epoch::from_gregorian_tai_hms(2019, 8, 24, 3, 49, 9);
let nanos = now.to_bdt_nanoseconds().unwrap();
assert_eq!(
Epoch::from_bdt_nanoseconds(nanos),
now,
"To/from (recip.) BDT nanoseconds failed"
);
assert!((now.to_tai_seconds() - SECONDS_BDT_TAI_OFFSET - now.to_bdt_seconds()).abs() < EPSILON);
assert!(
now.to_bdt_seconds() + SECONDS_BDT_TAI_OFFSET > now.to_utc_seconds(),
"BDT Time is not ahead of UTC"
);
let bdt_epoch = Epoch::from_tai_seconds(SECONDS_BDT_TAI_OFFSET);
assert_eq!(bdt_epoch, Epoch::from_bdt_days(0.0));
assert_eq!(bdt_epoch, Epoch::from_bdt_seconds(0.0));
assert_eq!(bdt_epoch, Epoch::from_bdt_nanoseconds(0));
assert_eq!(bdt_epoch, BDT_REF_EPOCH);
assert_eq!(format!("{bdt_epoch}"), "2006-01-01T00:00:00 UTC");
assert_eq!(format!("{bdt_epoch:x}"), "2006-01-01T00:00:33 TAI");
assert_eq!(
Epoch::from_bdt_days(0.0).to_duration_since_j1900(),
bdt_epoch.duration_since_j1900_tai
);
assert_eq!(
bdt_epoch.to_tai_seconds(),
Epoch::from_gregorian_utc_at_midnight(2006, 01, 01).to_tai_seconds()
);
assert!(
bdt_epoch.to_bdt_seconds().abs() < EPSILON,
"The number of seconds from the BDT epoch was not 0: {}",
bdt_epoch.to_bdt_seconds()
);
assert!(
bdt_epoch.to_bdt_days().abs() < EPSILON,
"The number of days from the BDT epoch was not 0: {}",
bdt_epoch.to_bdt_days()
);
}
#[test]
fn unix() {
#[cfg(feature = "std")]
{
use std::time::SystemTime;
let std_unix_time_s = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs_f64();
let epoch_unix_time_s = Epoch::now().unwrap().to_unix_seconds();
assert!(
(std_unix_time_s - epoch_unix_time_s).abs() < 1e-5,
"hifitime and std differ in UNIX by more than 10 microseconds: hifitime = {}\tstd = {}",
epoch_unix_time_s,
std_unix_time_s
);
}
let now = Epoch::from_gregorian_utc_hms(2022, 5, 2, 10, 39, 15);
assert!((now.to_unix_seconds() - 1651487955.0_f64).abs() < EPSILON);
assert!((now.to_unix_milliseconds() - 1651487955000.0_f64).abs() < EPSILON);
assert_eq!(
Epoch::from_unix_seconds(now.to_unix_seconds()),
now,
"To/from UNIX seconds failed"
);
assert_eq!(
Epoch::from_unix_milliseconds(now.to_unix_milliseconds()),
now,
"To/from UNIX milliseconds failed"
);
let unix_epoch = Epoch::from_gregorian_utc_at_midnight(1970, 1, 1);
assert_eq!(
format!("{}", unix_epoch.in_time_scale(TimeScale::UTC)),
"1970-01-01T00:00:00 UTC"
);
assert_eq!(
format!("{:x}", unix_epoch.in_time_scale(TimeScale::TAI)),
"1970-01-01T00:00:00 TAI"
);
assert_eq!(format!("{:p}", unix_epoch), "0");
assert_eq!(
unix_epoch.to_tai_seconds(),
Epoch::from_gregorian_utc_at_midnight(1970, 1, 1).to_tai_seconds()
);
assert!(
unix_epoch.to_unix_seconds().abs() < EPSILON,
"The number of seconds from the UNIX epoch was not 0: {}",
unix_epoch.to_unix_seconds()
);
assert!(
unix_epoch.to_unix_milliseconds().abs() < EPSILON,
"The number of milliseconds from the UNIX epoch was not 0: {}",
unix_epoch.to_unix_seconds()
);
assert!(
unix_epoch.to_unix_days().abs() < EPSILON,
"The number of days from the UNIX epoch was not 0: {}",
unix_epoch.to_unix_days()
);
}
#[test]
fn naif_spice_et_tdb_verification() {
let max_tdb_et_err = 32 * Unit::Microsecond;
let spice_utc_tai_ls_err = 9.0;
let spice_jde_precision = 1e-7;
let recip_err_s = 2e-6;
let spice_verif_func = |epoch: Epoch, et_s: f64, utc_jde_d: f64| {
assert!(
(Epoch::from_et_seconds(et_s).to_et_seconds() - et_s).abs() < recip_err_s,
"{} failed ET reciprocity test:\nwant: {}\tgot: {}\nerror: {} ns",
epoch,
et_s,
Epoch::from_et_seconds(et_s).to_et_seconds(),
(et_s - Epoch::from_et_seconds(et_s).to_et_seconds()).abs() * 1e9
);
assert!(
(Epoch::from_tdb_seconds(et_s).to_tdb_seconds() - et_s).abs() < recip_err_s,
"{} failed TDB reciprocity test:\nwant: {}\tgot: {}\nerror: {} ns",
epoch,
et_s,
Epoch::from_tdb_seconds(et_s).to_tdb_seconds(),
(et_s - Epoch::from_tdb_seconds(et_s).to_et_seconds()).abs() * 1e9
);
let extra_seconds = if epoch.leap_seconds_iers() == 0 {
spice_utc_tai_ls_err
} else {
0.0
};
assert!(
(epoch.to_et_seconds() - et_s + extra_seconds).abs() < EPSILON,
"{} failed ET test",
epoch
);
assert!(
(epoch.to_tdb_duration() - et_s * Unit::Second + extra_seconds * Unit::Second).abs()
<= max_tdb_et_err,
"{} failed TDB test",
epoch
);
assert!(
(epoch.to_jde_utc_days() - utc_jde_d).abs() < spice_jde_precision,
"{} failed JDE UTC days test:\nwant: {}\tgot: {}\nerror = {} days",
epoch,
utc_jde_d,
epoch.to_jde_utc_days(),
(epoch.to_jde_utc_days() - utc_jde_d).abs()
);
};
spice_verif_func(
Epoch::from_gregorian_utc(1900, 1, 9, 0, 17, 15, 0),
-3155024523.8157988,
2415028.5119792,
);
spice_verif_func(
Epoch::from_gregorian_utc(1920, 7, 23, 14, 39, 29, 0),
-2506972789.816543,
2422529.1107523,
);
spice_verif_func(
Epoch::from_gregorian_utc(1954, 12, 24, 6, 6, 31, 0),
-1420782767.8162904,
2435100.7545255,
);
spice_verif_func(
Epoch::from_gregorian_utc(1960, 2, 14, 6, 6, 31, 0),
-1258523567.8148985,
2436978.7545255,
);
spice_verif_func(
Epoch::from_gregorian_utc(1983, 4, 13, 12, 9, 14, 274_000_000),
-527644192.54036534,
2445438.0064152,
);
spice_verif_func(
Epoch::from_gregorian_utc(2000, 2, 29, 14, 57, 29, 0),
5108313.185383182,
2451604.1232523,
);
spice_verif_func(
Epoch::from_gregorian_utc(2022, 11, 29, 7, 58, 49, 782_000_000),
722980798.9650334,
2459912.8325206,
);
spice_verif_func(
Epoch::from_gregorian_utc(2044, 6, 6, 12, 18, 54, 0),
1402100403.1847699,
2467773.0131250,
);
spice_verif_func(
Epoch::from_gregorian_utc(2075, 4, 30, 23, 59, 54, 0),
2377166463.185493,
2479058.4999306,
);
}
#[test]
fn spice_et_tdb() {
let max_tdb_et_err = 30 * Unit::Microsecond;
let max_prec = 10 * Unit::Nanosecond;
let sp_ex = Epoch::from_gregorian_utc_hms(2012, 2, 7, 11, 22, 33);
let expected_et_s = 381_885_819.184_935_87;
let from_et_s = Epoch::from_tdb_seconds(expected_et_s);
assert!((from_et_s.to_tdb_seconds() - expected_et_s).abs() < EPSILON);
assert!(dbg!(sp_ex.to_et_seconds() - expected_et_s).abs() < max_prec.to_seconds());
assert!(dbg!(sp_ex.to_tdb_seconds() - expected_et_s).abs() < max_tdb_et_err.to_seconds());
assert!(dbg!(sp_ex.to_jde_utc_days() - 2455964.9739931).abs() < 1e-7);
assert!(dbg!(sp_ex.to_tai_seconds() - from_et_s.to_tai_seconds()).abs() < 3e-6);
let sp_ex = Epoch::from_gregorian_utc_at_midnight(2002, 2, 7);
let expected_et_s = 66_312_064.184_938_76;
assert!(dbg!(sp_ex.to_et_seconds() - expected_et_s).abs() < max_prec.to_seconds());
assert!(dbg!(sp_ex.to_tdb_seconds() - expected_et_s).abs() < max_tdb_et_err.to_seconds());
assert!(
(sp_ex.to_tai_seconds() - Epoch::from_tdb_seconds(expected_et_s).to_tai_seconds()).abs()
< 1e-5
);
let sp_ex = Epoch::from_gregorian_utc_hms(1996, 2, 7, 11, 22, 33);
let expected_et_s = -123_035_784.815_060_48;
assert!(dbg!(sp_ex.to_et_seconds() - expected_et_s).abs() < max_prec.to_seconds());
assert!(dbg!(sp_ex.to_tdb_seconds() - expected_et_s).abs() < max_tdb_et_err.to_seconds());
assert!(
dbg!(sp_ex.to_tai_seconds() - Epoch::from_tdb_seconds(expected_et_s).to_tai_seconds())
.abs()
< 1e-5
);
let sp_ex = Epoch::from_gregorian_utc_hms(2015, 2, 7, 11, 22, 33);
let expected_et_s = 476580220.1849411;
assert!(dbg!(sp_ex.to_et_seconds() - expected_et_s).abs() < max_prec.to_seconds());
assert!(dbg!(sp_ex.to_tdb_seconds() - expected_et_s).abs() < max_tdb_et_err.to_seconds());
assert!((sp_ex.to_jde_utc_days() - 2457060.9739931).abs() < 1e-7);
let sp_ex = Epoch::from_tdb_seconds(66_312_032.184_939_09);
assert!((2452312.500372511 - sp_ex.to_jde_et_days()).abs() < EPSILON);
assert!((2452312.500372511 - sp_ex.to_jde_tdb_days()).abs() < EPSILON);
assert_ne!(sp_ex.to_jde_et_duration(), sp_ex.to_jde_tdb_duration());
let sp_ex = Epoch::from_tdb_seconds(381_885_753.003_859_5);
assert!((2455964.9739931 - sp_ex.to_jde_et_days()).abs() < EPSILON);
assert!((2455964.9739931 - sp_ex.to_jde_tdb_days()).abs() < max_tdb_et_err.to_seconds());
}
#[test]
fn test_from_str() {
use core::str::FromStr;
let dt = Epoch::from_gregorian_utc(2017, 1, 14, 0, 31, 55, 0);
assert_eq!(dt, Epoch::from_str("2017-01-14T00:31:55 UTC").unwrap());
assert_eq!(dt, Epoch::from_str("2017-01-14T00:31:55").unwrap());
assert_eq!(dt, Epoch::from_str("2017-01-14 00:31:55").unwrap());
assert!(Epoch::from_str("2017-01-14 00:31:55 TAI").is_ok());
assert!(Epoch::from_str("2017-01-14 00:31:55 TT").is_ok());
assert!(Epoch::from_str("2017-01-14 00:31:55 ET").is_ok());
assert!(Epoch::from_str("2017-01-14 00:31:55 TDB").is_ok());
let jde = 2_452_312.500_372_511;
let to_tdb = Epoch::from_str("JD 2452312.500372511 TDB").unwrap();
let to_et = Epoch::from_str("JD 2452312.500372511 ET").unwrap();
let to_tai = Epoch::from_str("JD 2452312.500372511 TAI").unwrap();
const SPICE_EPSILON: f64 = 1e-9;
assert!((to_tdb.to_jde_tdb_days() - jde).abs() < SPICE_EPSILON);
assert!((to_et.to_jde_et_days() - jde).abs() < SPICE_EPSILON);
assert!((to_tai.to_jde_tai_days() - jde).abs() < SPICE_EPSILON);
assert!(
(Epoch::from_str("MJD 51544.5 TAI")
.unwrap()
.to_mjd_tai_days()
- 51544.5)
.abs()
< EPSILON
);
assert!((Epoch::from_str("SEC 0.5 TAI").unwrap().to_tai_seconds() - 0.5).abs() < EPSILON);
assert!(
(Epoch::from_str("SEC 66312032.18493909 TDB")
.unwrap()
.to_tdb_seconds()
- 66312032.18493909)
.abs()
< 1e-4
);
let greg = "2020-01-31T00:00:00 UTC";
assert_eq!(greg, format!("{}", Epoch::from_str(greg).unwrap()));
let greg = "2020-01-31T00:00:00 TAI";
assert_eq!(greg, format!("{:x}", Epoch::from_str(greg).unwrap()));
let greg = "2020-01-31T00:00:00 TDB";
assert_eq!(greg, format!("{:e}", Epoch::from_str(greg).unwrap()));
let greg = "2020-01-31T00:00:00 ET";
assert_eq!(
"2020-01-31T00:00:00.000000011 ET",
format!("{:E}", Epoch::from_str(greg).unwrap())
);
assert_eq!(
Epoch::from_gregorian_utc(2017, 1, 14, 0, 31, 55, 811000000),
Epoch::from_gregorian_str("2017-01-14 00:31:55.811 UTC").unwrap()
);
assert_eq!(
Epoch::from_gregorian_utc(2017, 1, 14, 0, 31, 55, 811000000),
Epoch::from_gregorian(2017, 1, 14, 0, 31, 55, 811000000, TimeScale::UTC),
);
assert!(Epoch::from_str("2022-12-15 13:32:16.421857-07:00").is_ok());
assert_eq!(
Epoch::from_str("blah"),
Err(Errors::ParseError(ParsingErrors::UnknownFormat))
);
}
#[test]
fn test_from_str_tdb() {
use core::str::FromStr;
let greg = "2020-01-31T00:00:00 TDB";
assert_eq!(greg, format!("{:e}", Epoch::from_str(greg).unwrap()));
}
#[test]
fn test_rfc3339() {
use core::str::FromStr;
assert_eq!(
Epoch::from_gregorian_utc_hms(1994, 11, 5, 13, 15, 30),
Epoch::from_str("1994-11-05T08:15:30-05:00").unwrap()
);
assert_eq!(
Epoch::from_gregorian_utc_hms(1994, 11, 5, 13, 15, 30),
Epoch::from_str("1994-11-05T13:15:30Z").unwrap()
);
assert_eq!(
Epoch::from_gregorian_tai_hms(1994, 11, 5, 13, 15, 30),
Epoch::from_str("1994-11-05T08:15:30-05:00 TAI").unwrap()
);
assert_eq!(
Epoch::from_gregorian_tai_hms(1994, 11, 5, 13, 15, 30),
Epoch::from_str("1994-11-05T13:15:30Z TAI").unwrap()
);
assert_eq!(
Epoch::from_gregorian_hms(1994, 11, 5, 13, 15, 30, TimeScale::TDB),
Epoch::from_str("1994-11-05T08:15:30-05:00 TDB").unwrap()
);
assert_eq!(
Epoch::from_gregorian_hms(1994, 11, 5, 13, 15, 30, TimeScale::TDB),
Epoch::from_str("1994-11-05T13:15:30Z TDB").unwrap()
);
}
#[test]
fn test_format() {
use core::str::FromStr;
use hifitime::Epoch;
let epoch = Epoch::from_gregorian_utc_hms(2022, 9, 6, 23, 24, 29);
assert!((epoch.to_et_seconds() - 715778738.1825389).abs() < EPSILON);
assert_eq!(format!("{epoch:?}"), "2022-09-06T23:24:29 UTC");
assert_eq!(format!("{epoch}"), "2022-09-06T23:24:29 UTC");
assert_eq!(format!("{epoch:x}"), "2022-09-06T23:25:06 TAI");
assert_eq!(format!("{epoch:X}"), "2022-09-06T23:25:38.184000000 TT");
assert_eq!(format!("{epoch:E}"), "2022-09-06T23:25:38.182538909 ET");
assert_eq!(format!("{epoch:e}"), "2022-09-06T23:25:38.182541259 TDB");
assert_eq!(format!("{epoch:p}"), "1662506669"); assert_eq!(format!("{epoch:o}"), "1346541887000000000");
for ts_u8 in 0..=7 {
let ts: TimeScale = ts_u8.into();
let recent = Epoch::from_gregorian(2020, 9, 6, 23, 24, 29, 2, ts);
let post_ref = Epoch::from_gregorian_hms(1900, 1, 1, 0, 0, 1, ts);
let pre_ref = Epoch::from_gregorian_hms(1899, 12, 31, 23, 59, 59, ts);
let way_old = Epoch::from_gregorian(1820, 9, 6, 23, 24, 29, 2, ts);
assert!(
((post_ref - pre_ref) - 2 * Unit::Second).abs() < 2 * Unit::Nanosecond,
"delta time should be 2 s in {ts:?} but is {}",
post_ref - pre_ref
);
for (i, epoch) in [recent, post_ref, pre_ref, way_old].iter().enumerate() {
if ts == TimeScale::TAI {
match i {
0 => assert_eq!(format!("{epoch:x}"), "2020-09-06T23:24:29.000000002 TAI"),
1 => {
assert_eq!(epoch.duration_since_j1900_tai, 1 * Unit::Second);
assert_eq!(format!("{epoch:x}"), "1900-01-01T00:00:01 TAI")
}
2 => {
assert_eq!(epoch.duration_since_j1900_tai, -1 * Unit::Second);
assert_eq!(format!("{epoch:x}"), "1899-12-31T23:59:59 TAI")
}
3 => assert_eq!(format!("{epoch:x}"), "1820-09-06T23:24:29.000000002 TAI"),
_ => {}
}
}
assert_eq!(
format!("{epoch:?}"),
match ts {
TimeScale::TAI => format!("{epoch:x}"),
TimeScale::ET => format!("{epoch:E}"),
TimeScale::TDB => format!("{epoch:e}"),
TimeScale::TT => format!("{epoch:X}"),
TimeScale::UTC => format!("{epoch}"),
TimeScale::GPST => format!("{epoch:x}").replace("TAI", "GPST"),
TimeScale::GST => format!("{epoch:x}").replace("TAI", "GST"),
TimeScale::BDT => format!("{epoch:x}").replace("TAI", "BDT"),
}
);
match Epoch::from_str(&format!("{epoch:?}")) {
Ok(rebuilt) => {
if ts == TimeScale::ET {
assert!(
(rebuilt - *epoch) < 30.0 * Unit::Microsecond,
"#{i} error = {}\ngot = {}\nwant: {}",
rebuilt - *epoch,
rebuilt.duration_since_j1900_tai,
epoch.duration_since_j1900_tai
)
} else {
assert_eq!(
&rebuilt,
epoch,
"#{i} error = {}\ngot = {}\nwant: {}",
rebuilt - *epoch,
rebuilt.duration_since_j1900_tai,
epoch.duration_since_j1900_tai
)
}
}
Err(e) => {
panic!(
"#{i} {e:?} with {epoch:?} (duration since j1900 = {})",
epoch.duration_since_j1900_tai
)
}
};
}
}
let epoch = Epoch::from_gregorian_utc_hms(2020, 1, 2, 23, 24, 29);
assert_eq!(format!("{epoch:?}"), "2020-01-02T23:24:29 UTC");
let epoch_post = Epoch::from_gregorian_tai_hms(1900, 1, 1, 0, 0, 1);
let epoch_pre = Epoch::from_gregorian_tai_hms(1899, 12, 31, 23, 59, 59);
assert_eq!(epoch_post.duration_since_j1900_tai.decompose().0, 0);
assert_eq!(epoch_pre.duration_since_j1900_tai.decompose().0, -1);
}
#[test]
fn ops() {
let sp_ex: Epoch = Epoch::from_gregorian_utc_hms(2012, 2, 7, 11, 22, 33) + Unit::Second * 1.0;
let expected_et_s = 381_885_819.184_935_87;
assert!(dbg!(sp_ex.to_tdb_seconds() - expected_et_s - 1.0).abs() < 2.6e-6);
let sp_ex: Epoch = sp_ex - Unit::Second * 1.0;
assert!((sp_ex.to_tdb_seconds() - expected_et_s).abs() < 2.6e-6);
}
#[test]
fn test_range() {
let start = Epoch::from_gregorian_utc_hms(2012, 2, 7, 11, 22, 33);
let middle = Epoch::from_gregorian_utc_hms(2012, 2, 30, 0, 11, 22);
let end = Epoch::from_gregorian_utc_hms(2012, 3, 7, 11, 22, 33);
let rng = start..end;
assert_eq!(rng, core::ops::Range { start, end });
assert!(rng.contains(&middle));
}
#[test]
fn regression_test_gh_85() {
let earlier_epoch =
Epoch::maybe_from_gregorian(2020, 1, 8, 16, 1, 17, 100, TimeScale::TAI).unwrap();
let later_epoch =
Epoch::maybe_from_gregorian(2020, 1, 8, 16, 1, 17, 200, TimeScale::TAI).unwrap();
assert!(
later_epoch > earlier_epoch,
"later_epoch should be 100ns after earlier_epoch"
);
}
#[test]
fn test_leap_seconds_iers() {
let epoch_from_utc_greg = Epoch::from_gregorian_tai_hms(1971, 12, 31, 23, 59, 59);
let epoch_from_utc_greg1 = Epoch::from_gregorian_tai_hms(1972, 1, 1, 0, 0, 0);
assert_eq!(epoch_from_utc_greg1.day_of_year(), 0.0);
assert_eq!(epoch_from_utc_greg.leap_seconds_iers(), 0);
assert_eq!(epoch_from_utc_greg1.leap_seconds_iers(), 10);
let epoch_from_utc_greg = Epoch::from_gregorian_tai_hms(1972, 6, 30, 23, 59, 59);
assert_eq!(
epoch_from_utc_greg.duration_in_year(),
(31 + 29 + 31 + 30 + 31 + 30) * Unit::Day - Unit::Second
);
let epoch_from_utc_greg1 = Epoch::from_gregorian_tai_hms(1972, 7, 1, 0, 0, 0);
assert_eq!(epoch_from_utc_greg.leap_seconds_iers(), 10);
assert_eq!(epoch_from_utc_greg1.leap_seconds_iers(), 11);
}
#[cfg(feature = "std")]
#[test]
fn test_utc_str() {
let dt_str = "2017-01-14T00:31:55 UTC";
let dt = Epoch::from_gregorian_str(dt_str).unwrap();
let (centuries, nanos) = dt.to_tai_duration().to_parts();
assert_eq!(centuries, 1);
assert_eq!(nanos, 537582752000000000);
}
#[test]
fn test_floor_ceil_round() {
use hifitime::TimeUnits;
let e = Epoch::from_gregorian_tai_hms(2022, 5, 20, 17, 57, 43);
assert_eq!(
e.ceil(1.hours()),
Epoch::from_gregorian_tai_hms(2022, 5, 20, 18, 0, 0)
);
assert_eq!(
e.floor(1.hours()),
Epoch::from_gregorian_tai_hms(2022, 5, 20, 17, 0, 0)
);
assert_eq!(
e.round(1.hours()),
Epoch::from_gregorian_tai_hms(2022, 5, 20, 18, 0, 0)
);
}
#[test]
fn test_ord() {
let epoch1 = Epoch::maybe_from_gregorian(2020, 1, 8, 16, 1, 17, 100, TimeScale::TAI).unwrap();
let epoch2 = Epoch::maybe_from_gregorian(2020, 1, 8, 16, 1, 17, 200, TimeScale::TAI).unwrap();
assert_eq!(epoch1.max(epoch2), epoch2);
assert_eq!(epoch2.min(epoch1), epoch1);
assert_eq!(epoch1.cmp(&epoch1), core::cmp::Ordering::Equal);
}
#[test]
fn regression_test_gh_145() {
let e = Epoch::from_gregorian_tai(2022, 10, 3, 17, 44, 29, 898032665);
assert_eq!(
e.floor(3.minutes()),
Epoch::from_gregorian_tai_hms(2022, 10, 3, 17, 42, 0)
);
assert_eq!(
e.ceil(3.minutes()),
Epoch::from_gregorian_tai_hms(2022, 10, 3, 17, 45, 0)
);
assert_eq!(
e.round(3.minutes()),
Epoch::from_gregorian_tai_hms(2022, 10, 3, 17, 45, 0)
);
assert_eq!(
e.round(3.minutes()),
Epoch::from_gregorian_tai_hms(2022, 10, 3, 17, 45, 0)
);
let e = Epoch::from_gregorian_utc(2022, 10, 3, 17, 44, 29, 898032665);
assert_eq!(
e.floor(3.minutes()),
Epoch::from_gregorian_utc_hms(2022, 10, 3, 17, 42, 0)
);
assert_eq!(
e.ceil(3.minutes()),
Epoch::from_gregorian_utc_hms(2022, 10, 3, 17, 45, 0)
);
assert_eq!(
e.round(3.minutes()),
Epoch::from_gregorian_utc_hms(2022, 10, 3, 17, 45, 0)
);
let e = Epoch::from_gregorian(2022, 10, 3, 17, 44, 29, 898032665, TimeScale::TT);
assert_eq!(
e.floor(3.minutes()),
Epoch::from_gregorian_hms(2022, 10, 3, 17, 42, 0, TimeScale::TT)
);
assert_eq!(
e.ceil(3.minutes()),
Epoch::from_gregorian_hms(2022, 10, 3, 17, 45, 0, TimeScale::TT)
);
assert_eq!(
e.round(3.minutes()),
Epoch::from_gregorian_hms(2022, 10, 3, 17, 45, 0, TimeScale::TT)
);
let e = Epoch::from_gregorian(2022, 10, 3, 17, 44, 29, 898032665, TimeScale::TDB);
assert_eq!(
e.floor(3.minutes()),
Epoch::from_gregorian_hms(2022, 10, 3, 17, 42, 0, TimeScale::TDB)
);
assert_eq!(
e.ceil(3.minutes()),
Epoch::from_gregorian_hms(2022, 10, 3, 17, 45, 0, TimeScale::TDB)
);
assert_eq!(
e.round(3.minutes()),
Epoch::from_gregorian_hms(2022, 10, 3, 17, 45, 0, TimeScale::TDB)
);
let e = Epoch::from_gregorian(2022, 10, 3, 17, 44, 29, 898032665, TimeScale::ET);
assert_eq!(
e.floor(3.minutes()),
Epoch::from_gregorian_hms(2022, 10, 3, 17, 42, 0, TimeScale::ET)
);
assert_eq!(
e.ceil(3.minutes()),
Epoch::from_gregorian_hms(2022, 10, 3, 17, 45, 0, TimeScale::ET)
);
assert_eq!(
e.round(3.minutes()),
Epoch::from_gregorian_hms(2022, 10, 3, 17, 45, 0, TimeScale::ET)
);
}
#[test]
fn test_timescale_recip() {
let recip_func = |utc_epoch: Epoch| {
assert_eq!(utc_epoch, utc_epoch.set(utc_epoch.to_duration()));
for ts in &[
TimeScale::ET,
TimeScale::TDB,
TimeScale::TT,
TimeScale::UTC,
] {
let converted = utc_epoch.to_duration_in_time_scale(*ts);
let from_dur = Epoch::from_duration(converted, *ts);
if *ts == TimeScale::ET {
assert!(
(utc_epoch - from_dur).abs() < 150 * Unit::Nanosecond,
"ET recip error = {} for {}",
utc_epoch - from_dur,
utc_epoch
);
} else {
assert_eq!(utc_epoch, from_dur);
}
#[cfg(feature = "std")]
{
use core::str::FromStr;
let to_rfc = utc_epoch.to_rfc3339();
let from_rfc = Epoch::from_str(&to_rfc).unwrap();
println!(
"{} for {utc_epoch}\tto = {to_rfc}\tfrom={from_rfc}",
from_rfc - utc_epoch
);
assert_eq!(from_rfc, utc_epoch);
}
}
};
recip_func(Epoch::from_gregorian_utc(1900, 1, 9, 0, 17, 15, 0));
recip_func(Epoch::from_gregorian_utc(1920, 7, 23, 14, 39, 29, 0));
recip_func(Epoch::from_gregorian_utc(1954, 12, 24, 6, 6, 31, 0));
recip_func(Epoch::from_gregorian_utc(1960, 2, 14, 6, 6, 31, 0));
recip_func(Epoch::from_gregorian_utc(
1983,
4,
13,
12,
9,
14,
274_000_000,
));
recip_func(Epoch::from_gregorian_utc(2000, 2, 29, 14, 57, 29, 0));
recip_func(Epoch::from_gregorian_utc(
2022,
11,
29,
7,
58,
49,
782_000_000,
));
recip_func(Epoch::from_gregorian_utc(2044, 6, 6, 12, 18, 54, 0));
recip_func(Epoch::from_gregorian_utc(2075, 4, 30, 23, 59, 54, 0));
}
#[test]
fn test_add_durations_over_leap_seconds() {
let pre_ls_utc = Epoch::from_gregorian_utc_at_noon(1971, 12, 31);
let pre_ls_tai = pre_ls_utc.in_time_scale(TimeScale::TAI);
assert_eq!(pre_ls_utc - pre_ls_tai, Duration::ZERO);
assert_eq!(
(pre_ls_utc + 1 * Unit::Day) - (pre_ls_tai + 1 * Unit::Day),
10 * Unit::Second
);
let post_ls_utc = pre_ls_utc + Unit::Day;
let post_ls_tai = pre_ls_tai + Unit::Day;
assert_eq!(
(post_ls_utc - Unit::Day) - (post_ls_tai - Unit::Day),
Duration::ZERO
);
}
#[test]
fn test_add_f64_seconds() {
let e = Epoch::from_gregorian_tai(2044, 6, 6, 12, 18, 54, 0);
assert_eq!(e + 159 * Unit::Second, e + 159.0);
}
#[test]
#[should_panic]
fn from_infinite_tai_seconds() {
let _ = Epoch::from_tai_seconds(f64::NAN);
}
#[test]
#[should_panic]
fn from_infinite_tai_days() {
let _ = Epoch::from_tai_days(f64::NAN);
}
#[test]
#[should_panic]
fn from_infinite_mjd_tai_days() {
let _ = Epoch::from_mjd_tai(f64::NAN);
}
#[test]
#[should_panic]
fn from_infinite_mjd_utc_days() {
let _ = Epoch::from_mjd_utc(f64::NAN);
}
#[test]
#[should_panic]
fn from_infinite_jde_tai_days() {
let _ = Epoch::from_jde_tai(f64::NAN);
}
#[test]
#[should_panic]
fn from_infinite_jde_et_days() {
let _ = Epoch::from_jde_et(f64::NAN);
}
#[test]
#[should_panic]
fn from_infinite_jde_tdb_days() {
let _ = Epoch::from_jde_tdb(f64::NAN);
}
#[test]
#[should_panic]
fn from_infinite_tdb_seconds() {
let _ = Epoch::from_tdb_seconds(f64::NAN);
}
#[test]
fn test_minmax() {
use hifitime::Epoch;
let e0 = Epoch::from_gregorian_utc_at_midnight(2022, 10, 20);
let e1 = Epoch::from_gregorian_utc_at_midnight(2022, 10, 21);
assert_eq!(e0, e1.min(e0));
assert_eq!(e0, e0.min(e1));
assert_eq!(e1, e1.max(e0));
assert_eq!(e1, e0.max(e1));
}
#[test]
fn test_weekday() {
let permutate_time_scale = |e: Epoch, expect: Weekday| {
for new_time_scale in [
TimeScale::BDT,
TimeScale::ET,
TimeScale::GPST,
TimeScale::GST,
TimeScale::TAI,
TimeScale::TDB,
TimeScale::TT,
TimeScale::UTC,
] {
let e_ts = e.in_time_scale(new_time_scale);
assert_eq!(e_ts.weekday(), expect, "error with {new_time_scale}");
}
};
let j1900 = J1900_REF_EPOCH;
assert_eq!(j1900.weekday(), Weekday::Monday);
permutate_time_scale(j1900, Weekday::Monday);
let j1900_1ns = Epoch::from_gregorian_tai(1900, 01, 01, 0, 0, 0, 1);
assert_eq!(j1900_1ns.weekday(), Weekday::Monday);
permutate_time_scale(j1900_1ns, Weekday::Monday);
let j1900_10h_123_ns = Epoch::from_gregorian_tai(1900, 01, 01, 10, 00, 00, 123);
assert_eq!(j1900_10h_123_ns.weekday(), Weekday::Monday);
permutate_time_scale(j1900_10h_123_ns, Weekday::Monday);
let j1901 = j1900 + Duration::from_days(1.0);
assert_eq!(j1901.weekday(), Weekday::Tuesday);
permutate_time_scale(j1901, Weekday::Tuesday);
let j1901 = j1901 + Duration::from_nanoseconds(1.0);
assert_eq!(j1901.weekday(), Weekday::Tuesday);
permutate_time_scale(j1901, Weekday::Tuesday);
let e = j1900 + Duration::from_days(6.0);
assert_eq!(e.weekday(), Weekday::Sunday);
permutate_time_scale(e, Weekday::Sunday);
let e = e + Duration::from_nanoseconds(10000.0);
assert_eq!(e.weekday(), Weekday::Sunday);
permutate_time_scale(e, Weekday::Sunday);
let e = j1900 + Duration::from_days(7.0);
assert_eq!(e.weekday(), Weekday::Monday);
permutate_time_scale(e, Weekday::Monday);
let epoch = Epoch::from_gregorian_utc_at_midnight(2022, 12, 01);
assert_eq!(epoch.weekday_utc(), Weekday::Thursday);
permutate_time_scale(epoch, Weekday::Thursday);
let epoch = Epoch::from_gregorian_utc_at_midnight(2022, 11, 28);
assert_eq!(epoch.weekday_utc(), Weekday::Monday);
permutate_time_scale(epoch, Weekday::Monday);
let epoch = Epoch::from_gregorian_utc_at_midnight(1988, 1, 2);
assert_eq!(epoch.weekday_utc(), Weekday::Saturday);
permutate_time_scale(epoch, Weekday::Saturday);
let epoch_tai = BDT_REF_EPOCH;
assert_eq!(epoch_tai.weekday(), Weekday::Sunday);
let epoch_tai = GST_REF_EPOCH;
assert_eq!(epoch_tai.weekday(), Weekday::Sunday);
}
#[test]
fn test_get_time() {
let epoch = Epoch::from_gregorian_utc(2022, 12, 01, 10, 11, 12, 13);
assert_eq!(epoch.hours(), 10);
assert_eq!(epoch.minutes(), 11);
assert_eq!(epoch.seconds(), 12);
assert_eq!(epoch.milliseconds(), 0);
assert_eq!(epoch.microseconds(), 0);
assert_eq!(epoch.nanoseconds(), 13);
let epoch_midnight = epoch.with_hms(0, 0, 0);
assert_eq!(
epoch_midnight,
Epoch::from_gregorian_utc_at_midnight(2022, 12, 01) + 13 * Unit::Nanosecond
);
let epoch_midnight = epoch.with_hms_strict(0, 0, 0);
assert_eq!(
epoch_midnight,
Epoch::from_gregorian_utc_at_midnight(2022, 12, 01)
);
let epoch = Epoch::from_gregorian_utc(2022, 12, 01, 10, 11, 12, 13);
let other_utc = Epoch::from_gregorian_utc(2024, 12, 01, 20, 21, 22, 23);
let other = other_utc.in_time_scale(TimeScale::TDB);
assert_eq!(
epoch.with_hms_from(other),
Epoch::from_gregorian_utc(2022, 12, 01, 20, 21, 22, 13)
);
assert_eq!(
epoch.with_hms_strict_from(other),
Epoch::from_gregorian_utc(2022, 12, 01, 20, 21, 22, 0)
);
assert_eq!(
epoch.with_time_from(other),
Epoch::from_gregorian_utc(2022, 12, 01, 20, 21, 22, 23)
);
}
#[test]
fn test_start_of_week() {
let epoch = Epoch::from_gregorian_utc(2022, 12, 01, 10, 11, 12, 13);
assert_eq!(epoch.weekday_utc(), Weekday::Thursday);
assert_eq!(
epoch.previous_weekday_at_midnight(Weekday::Sunday),
Epoch::from_gregorian_utc_at_midnight(2022, 11, 27)
);
assert_eq!(
epoch
.previous_weekday_at_midnight(Weekday::Sunday)
.weekday_utc(),
Weekday::Sunday
);
let epoch = Epoch::from_gregorian_utc(2022, 09, 15, 01, 01, 01, 01);
assert_eq!(epoch.weekday_utc(), Weekday::Thursday);
assert_eq!(
epoch.previous_weekday_at_midnight(Weekday::Sunday),
Epoch::from_gregorian_utc_at_midnight(2022, 09, 11)
);
assert_eq!(
epoch
.previous_weekday_at_midnight(Weekday::Sunday)
.weekday_utc(),
Weekday::Sunday
);
}
#[test]
fn test_time_of_week() {
let epoch = Epoch::from_time_of_week(0, 10 * 1_000_000_000 + 10, TimeScale::TAI);
assert_eq!(epoch.to_gregorian_utc(), (1900, 01, 01, 00, 00, 10, 10));
assert_eq!(epoch.to_time_of_week(), (0, 10 * 1_000_000_000 + 10));
let epoch_utc = epoch.in_time_scale(TimeScale::UTC);
let (week, tow) = epoch_utc.to_time_of_week();
assert_eq!(
Epoch::from_time_of_week(week, tow, TimeScale::UTC),
epoch_utc
);
let epoch = Epoch::from_time_of_week(1, 10 * 1_000_000_000 + 10, TimeScale::TAI);
assert_eq!(epoch.to_gregorian_utc(), (1900, 01, 08, 00, 00, 10, 10));
let epoch = Epoch::from_time_of_week(2238, 345_618_000_000_000, TimeScale::GPST);
assert_eq!(epoch.to_gregorian_utc(), (2022, 12, 01, 00, 00, 00, 00));
assert_eq!(epoch.to_time_of_week(), (2238, 345_618_000_000_000));
let epoch_utc = epoch.in_time_scale(TimeScale::UTC);
let (utc_wk, utc_tow) = epoch_utc.to_time_of_week();
assert_eq!(
Epoch::from_time_of_week(utc_wk, utc_tow, TimeScale::UTC),
epoch_utc
);
let epoch = Epoch::from_time_of_week(0, 3_618_000_000_000, TimeScale::GPST);
assert_eq!(epoch.to_gregorian_utc(), (1980, 01, 06, 01, 00, 0 + 18, 00));
assert_eq!(epoch.to_time_of_week(), (0, 3_618_000_000_000));
let epoch_utc = epoch.in_time_scale(TimeScale::UTC);
let (utc_wk, utc_tow) = epoch_utc.to_time_of_week();
assert_eq!(
Epoch::from_time_of_week(utc_wk, utc_tow, TimeScale::UTC),
epoch_utc
);
let epoch = Epoch::from_time_of_week(51, 349_218_000_000_000, TimeScale::GPST);
assert_eq!(epoch.to_gregorian_utc(), (1981, 01, 01, 01, 00, 18, 00));
assert_eq!(epoch.to_time_of_week(), (51, 349_218_000_000_000));
let epoch = Epoch::from_time_of_week(24, 306_457_000_000_000, TimeScale::GPST);
assert_eq!(
epoch.to_gregorian_utc(),
(1980, 06, 25, 13, 07, 18 + 19, 00)
);
assert_eq!(epoch.to_time_of_week(), (24, 306_457_000_000_000));
let epoch_utc = epoch.in_time_scale(TimeScale::UTC);
let (week, tow) = epoch_utc.to_time_of_week();
assert_eq!(
Epoch::from_time_of_week(week, tow, TimeScale::UTC),
epoch_utc
);
let epoch = Epoch::from_time_of_week(2238, 345_618_000_000_001, TimeScale::GPST);
assert_eq!(epoch.to_gregorian_utc(), (2022, 12, 01, 00, 00, 00, 01));
let epoch_utc = epoch.in_time_scale(TimeScale::UTC);
let (week, tow) = epoch_utc.to_time_of_week();
assert_eq!(
Epoch::from_time_of_week(week, tow, TimeScale::UTC),
epoch_utc
);
let epoch = Epoch::from_time_of_week(2238, 475_218_000_000_000, TimeScale::GPST);
assert_eq!(epoch.to_gregorian_utc(), (2022, 12, 02, 12, 00, 00, 00));
let epoch_utc = epoch.in_time_scale(TimeScale::UTC);
let (week, tow) = epoch_utc.to_time_of_week();
assert_eq!(
Epoch::from_time_of_week(week, tow, TimeScale::UTC),
epoch_utc
);
let epoch = Epoch::from_time_of_week(2238, 487_657_000_000_010, TimeScale::GPST);
assert_eq!(epoch.to_gregorian_utc(), (2022, 12, 02, 15, 27, 19, 10));
let epoch_utc = epoch.in_time_scale(TimeScale::UTC);
let (week, tow) = epoch_utc.to_time_of_week();
assert_eq!(
Epoch::from_time_of_week(week, tow, TimeScale::UTC),
epoch_utc
);
let epoch = Epoch::from_time_of_week(0, 3_600_000_000_000, TimeScale::GST);
let expected_tai = TimeScale::GST.ref_epoch() + Duration::from_hours(1.0);
assert_eq!(epoch.to_gregorian_utc(), expected_tai.to_gregorian_utc());
assert_eq!(epoch.to_time_of_week(), (0, 3_600_000_000_000));
let epoch_utc = epoch.in_time_scale(TimeScale::UTC);
let (week, tow) = epoch_utc.to_time_of_week();
assert_eq!(
Epoch::from_time_of_week(week, tow, TimeScale::UTC),
epoch_utc
);
let epoch = Epoch::from_time_of_week(1, 128 * 3600 * 1_000_000_000, TimeScale::GST);
let expected_tai =
TimeScale::GST.ref_epoch() + Duration::from_days(7.0) + Duration::from_hours(128.0);
assert_eq!(epoch.to_gregorian_utc(), expected_tai.to_gregorian_utc());
assert_eq!(epoch.to_time_of_week(), (1, 128 * 3600 * 1_000_000_000));
let epoch_utc = epoch.in_time_scale(TimeScale::UTC);
let (week, tow) = epoch_utc.to_time_of_week();
assert_eq!(
Epoch::from_time_of_week(week, tow, TimeScale::UTC),
epoch_utc
);
let epoch = Epoch::from_time_of_week(
0,
13 * 3600 * 1_000_000_000 + 1800 * 1_000_000_000,
TimeScale::BDT,
);
let expected_tai = TimeScale::BDT.ref_epoch() + Duration::from_hours(13.5);
assert_eq!(epoch.to_gregorian_utc(), expected_tai.to_gregorian_utc());
assert_eq!(
epoch.to_time_of_week(),
(0, 13 * 3600 * 1_000_000_000 + 1800 * 1_000_000_000)
);
let epoch_utc = epoch.in_time_scale(TimeScale::UTC);
let (week, tow) = epoch_utc.to_time_of_week();
assert_eq!(
Epoch::from_time_of_week(week, tow, TimeScale::UTC),
epoch_utc
);
let epoch = Epoch::from_time_of_week(
10,
36 * 3600 * 1_000_000_000 + 900 * 1_000_000_000,
TimeScale::BDT,
);
let expected_tai =
TimeScale::BDT.ref_epoch() + Duration::from_days(70.0) + Duration::from_hours(36.25);
assert_eq!(epoch.to_gregorian_utc(), expected_tai.to_gregorian_utc());
assert_eq!(
epoch.to_time_of_week(),
(10, 36 * 3600 * 1_000_000_000 + 900 * 1_000_000_000)
);
let epoch_utc = epoch.in_time_scale(TimeScale::UTC);
let (week, tow) = epoch_utc.to_time_of_week();
assert_eq!(
Epoch::from_time_of_week(week, tow, TimeScale::UTC),
epoch_utc
);
}
#[test]
fn test_day_of_year() {
let recip_func = |utc_epoch: Epoch| {
for ts in &[
TimeScale::UTC,
TimeScale::TAI,
TimeScale::TT,
TimeScale::ET,
TimeScale::TDB,
] {
let epoch = utc_epoch.in_time_scale(*ts);
let (year, days) = epoch.year_days_of_year();
let rebuilt = Epoch::from_day_of_year(year, days, *ts);
if *ts == TimeScale::ET || *ts == TimeScale::TDB {
assert!(
(epoch - rebuilt).abs() < 750 * Unit::Nanosecond,
"{} recip error = {} for {}",
ts,
epoch - rebuilt,
epoch
);
} else {
assert!(
(epoch - rebuilt).abs() < 50 * Unit::Nanosecond,
"{} recip error = {} for {}",
ts,
epoch - rebuilt,
epoch
);
}
}
};
recip_func(Epoch::from_gregorian_utc(1900, 1, 9, 0, 17, 15, 0));
recip_func(Epoch::from_gregorian_utc(1920, 7, 23, 14, 39, 29, 0));
recip_func(Epoch::from_gregorian_utc(1954, 12, 24, 6, 6, 31, 0));
recip_func(Epoch::from_gregorian_utc(1960, 2, 14, 6, 6, 31, 0));
recip_func(Epoch::from_gregorian_utc(
1983,
4,
13,
12,
9,
14,
274_000_000,
));
recip_func(Epoch::from_gregorian_utc(2000, 2, 29, 14, 57, 29, 0));
recip_func(Epoch::from_gregorian_utc(
2022,
11,
29,
7,
58,
49,
782_000_000,
));
recip_func(Epoch::from_gregorian_utc(2044, 6, 6, 12, 18, 54, 0));
recip_func(Epoch::from_gregorian_utc(2075, 4, 30, 23, 59, 54, 0));
}
#[test]
fn test_epoch_formatter() {
use core::str::FromStr;
use hifitime::efmt::consts::*;
let bday = Epoch::from_gregorian_utc(2000, 2, 29, 14, 57, 29, 37);
let fmt_iso_ord = Formatter::new(bday, ISO8601_ORDINAL);
assert_eq!(format!("{fmt_iso_ord}"), "2000-059");
let fmt_iso_ord = Formatter::new(bday, Format::from_str("%j").unwrap());
assert_eq!(format!("{fmt_iso_ord}"), "059");
let fmt_iso = Formatter::new(bday, ISO8601);
assert_eq!(format!("{fmt_iso}"), format!("{bday}"));
let fmt = Formatter::new(bday, ISO8601_DATE);
assert_eq!(format!("{fmt}"), format!("2000-02-29"));
let fmt = Formatter::new(bday, RFC2822);
assert_eq!(format!("{fmt}"), format!("Tue, 29 Feb 2000 14:57:29"));
let fmt = Formatter::new(bday, RFC2822_LONG);
assert_eq!(
format!("{fmt}"),
format!("Tuesday, 29 February 2000 14:57:29")
);
let fmt = Formatter::new(bday, Format::from_str("%w").unwrap());
assert_eq!(format!("{fmt}"), format!("2"));
let init_str = "1994-11-05T08:15:30-05:00";
let e = Epoch::from_str(init_str).unwrap();
let fmt = Format::from_str("%Y-%m-%dT%H:%M:%S.%f%z").unwrap();
assert_eq!(fmt, RFC3339);
let fmtd = Formatter::with_timezone(e, Duration::from_str("-05:00").unwrap(), RFC3339_FLEX);
assert_eq!(init_str, format!("{fmtd}"));
assert_eq!(
format!("{:?}", Format::from_str("%A, ").unwrap()),
"EpochFormat:`Weekday, `"
);
assert_eq!(
format!("{:?}", Format::from_str("%A,?").unwrap()),
"EpochFormat:`Weekday,?`"
);
assert_eq!(
Format::from_str("%p"),
Err(hifitime::ParsingErrors::UnknownFormattingToken('p'))
);
}
#[cfg(feature = "std")]
#[test]
fn test_leap_seconds_file() {
use hifitime::leap_seconds::{LatestLeapSeconds, LeapSecondsFile};
let provider = LeapSecondsFile::from_path("data/leap-seconds.list").unwrap();
let default = LatestLeapSeconds::default();
let mut pos = 0;
for expected in default {
if expected.announced_by_iers {
assert_eq!(expected, provider[pos]);
pos += 1;
}
}
}