#[cfg(feature = "python")]
use pyo3::prelude::*;
#[cfg(feature = "serde")]
use serde_derive::{Deserialize, Serialize};
#[cfg(kani)]
use kani::Arbitrary;
use core::fmt;
use core::str::FromStr;
use crate::{
Duration, Epoch, Errors, ParsingErrors, J2000_REF_EPOCH_ET, J2000_REF_EPOCH_TDB,
J2000_TO_J1900_DURATION, SECONDS_PER_DAY,
};
pub const J1900_REF_EPOCH: Epoch = Epoch::from_tai_duration(Duration::ZERO);
pub const J2000_REF_EPOCH: Epoch = Epoch::from_tai_duration(J2000_TO_J1900_DURATION);
pub const GPST_REF_EPOCH: Epoch = Epoch::from_tai_duration(Duration {
centuries: 0,
nanoseconds: 2_524_953_619_000_000_000, });
pub const SECONDS_GPS_TAI_OFFSET: f64 = 2_524_953_619.0;
pub const SECONDS_GPS_TAI_OFFSET_I64: i64 = 2_524_953_619;
pub const DAYS_GPS_TAI_OFFSET: f64 = SECONDS_GPS_TAI_OFFSET / SECONDS_PER_DAY;
pub const GST_REF_EPOCH: Epoch = Epoch::from_tai_duration(Duration {
centuries: 0,
nanoseconds: 3_144_268_819_000_000_000,
});
pub const SECONDS_GST_TAI_OFFSET: f64 = 3_144_268_819.0;
pub const SECONDS_GST_TAI_OFFSET_I64: i64 = 3_144_268_819;
pub const BDT_REF_EPOCH: Epoch = Epoch::from_tai_duration(Duration {
centuries: 1,
nanoseconds: 189_302_433_000_000_000,
});
pub const SECONDS_BDT_TAI_OFFSET: f64 = 3_345_062_433.0;
pub const SECONDS_BDT_TAI_OFFSET_I64: i64 = 3_345_062_433;
pub const UNIX_REF_EPOCH: Epoch = Epoch::from_tai_duration(Duration {
centuries: 0,
nanoseconds: 2_208_988_800_000_000_000,
});
#[non_exhaustive]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "python", pyclass)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum TimeScale {
TAI,
TT,
ET,
TDB,
UTC,
GPST,
GST,
BDT,
QZSST,
}
#[cfg(kani)]
impl Arbitrary for TimeScale {
#[inline(always)]
fn any() -> Self {
let ts_u8: u8 = kani::any();
Self::from(ts_u8)
}
}
impl Default for TimeScale {
fn default() -> Self {
Self::TAI
}
}
impl TimeScale {
pub(crate) const fn formatted_len(&self) -> usize {
match &self {
Self::QZSST => 5,
Self::GPST => 4,
Self::TAI | Self::TDB | Self::UTC | Self::GST | Self::BDT => 3,
Self::ET | Self::TT => 2,
}
}
pub const fn is_gnss(&self) -> bool {
matches!(self, Self::GPST | Self::GST | Self::BDT | Self::QZSST)
}
pub const fn ref_epoch(&self) -> Epoch {
match self {
Self::GPST => GPST_REF_EPOCH,
Self::GST => GST_REF_EPOCH,
Self::BDT => BDT_REF_EPOCH,
Self::ET => J2000_REF_EPOCH_ET,
Self::TDB => J2000_REF_EPOCH_TDB,
Self::TT | Self::TAI | Self::UTC => J1900_REF_EPOCH,
Self::QZSST => GPST_REF_EPOCH,
}
}
}
impl fmt::Display for TimeScale {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::TAI => write!(f, "TAI"),
Self::TT => write!(f, "TT"),
Self::ET => write!(f, "ET"),
Self::TDB => write!(f, "TDB"),
Self::UTC => write!(f, "UTC"),
Self::GPST => write!(f, "GPST"),
Self::GST => write!(f, "GST"),
Self::BDT => write!(f, "BDT"),
Self::QZSST => write!(f, "QZSST"),
}
}
}
impl fmt::LowerHex for TimeScale {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::GPST => write!(f, "GPS"),
Self::GST => write!(f, "GAL"),
Self::BDT => write!(f, "BDS"),
Self::QZSST => write!(f, "QZSS"),
_ => write!(f, "{self}"),
}
}
}
#[cfg_attr(feature = "python", pymethods)]
impl TimeScale {
pub const fn uses_leap_seconds(&self) -> bool {
matches!(self, Self::UTC)
}
}
impl From<TimeScale> for u8 {
fn from(ts: TimeScale) -> Self {
match ts {
TimeScale::TAI => 0,
TimeScale::TT => 1,
TimeScale::ET => 2,
TimeScale::TDB => 3,
TimeScale::UTC => 4,
TimeScale::GPST => 5,
TimeScale::GST => 6,
TimeScale::BDT => 7,
TimeScale::QZSST => 8,
}
}
}
impl From<u8> for TimeScale {
fn from(val: u8) -> Self {
match val {
1 => Self::TT,
2 => Self::ET,
3 => Self::TDB,
4 => Self::UTC,
5 => Self::GPST,
6 => Self::GST,
7 => Self::BDT,
8 => Self::QZSST,
_ => Self::TAI,
}
}
}
impl FromStr for TimeScale {
type Err = Errors;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let val = s.trim();
if val == "UTC" {
Ok(Self::UTC)
} else if val == "TT" {
Ok(Self::TT)
} else if val == "TAI" {
Ok(Self::TAI)
} else if val == "TDB" {
Ok(Self::TDB)
} else if val == "ET" {
Ok(Self::ET)
} else if val == "GPST" || val == "GPS" {
Ok(Self::GPST)
} else if val == "GST" || val == "GAL" {
Ok(Self::GST)
} else if val == "BDT" || val == "BDS" {
Ok(Self::BDT)
} else if val == "QZSST" || val == "QZSS" {
Ok(Self::QZSST)
} else {
Err(Errors::ParseError(ParsingErrors::TimeSystem))
}
}
}
#[test]
#[cfg(feature = "serde")]
fn test_serdes() {
let ts = TimeScale::UTC;
let content = "\"UTC\"";
assert_eq!(content, serde_json::to_string(&ts).unwrap());
let parsed: TimeScale = serde_json::from_str(content).unwrap();
assert_eq!(ts, parsed);
}
#[test]
fn test_ts() {
for ts_u8 in 0..u8::MAX {
let ts = TimeScale::from(ts_u8);
let ts_u8_back: u8 = ts.into();
if ts_u8 < 9 {
assert_eq!(ts_u8_back, ts_u8, "got {ts_u8_back} want {ts_u8}");
} else {
assert_eq!(ts, TimeScale::TAI);
}
}
}
#[cfg(kani)]
#[kani::proof]
fn formal_time_scale() {
let _time_scale: TimeScale = kani::any();
}