use crate::event::MediaTime;
use alloc::{format, string::String};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TimeAnchor {
pub pts_90k: u64,
pub utc_epoch_ms: i64,
}
impl TimeAnchor {
pub fn media_to_epoch_ms(&self, t: MediaTime) -> i64 {
let delta_ticks = t.0 as i64 - self.pts_90k as i64;
self.utc_epoch_ms + (delta_ticks as i128 * 1000 / crate::PTS_HZ as i128) as i64
}
pub fn rfc3339(&self, t: MediaTime) -> String {
format_rfc3339_ms(self.media_to_epoch_ms(t))
}
}
pub fn format_rfc3339_ms(epoch_ms: i64) -> String {
let (secs, ms) = (epoch_ms.div_euclid(1000), epoch_ms.rem_euclid(1000));
let days = secs.div_euclid(86_400);
let tod = secs.rem_euclid(86_400);
let (h, m, s) = (tod / 3600, (tod % 3600) / 60, tod % 60);
let (y, mo, d) = civil_from_days(days);
format!(
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z",
y, mo, d, h, m, s, ms
)
}
fn civil_from_days(z: i64) -> (i64, u32, u32) {
let z = z + 719_468;
let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
let doe = z - era * 146_097; let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365; let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); let mp = (5 * doy + 2) / 153; let d = (doy - (153 * mp + 2) / 5 + 1) as u32; let m = if mp < 10 { mp + 3 } else { mp - 9 } as u32; (if m <= 2 { y + 1 } else { y }, m, d)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::event::MediaTime;
#[test]
fn epoch_zero_formats_unix_epoch() {
assert_eq!(format_rfc3339_ms(0), "1970-01-01T00:00:00.000Z");
assert_eq!(format_rfc3339_ms(86_400_000), "1970-01-02T00:00:00.000Z");
assert_eq!(format_rfc3339_ms(1_000), "1970-01-01T00:00:01.000Z");
}
#[test]
fn anchor_maps_media_to_wallclock() {
let a = TimeAnchor {
pts_90k: 0,
utc_epoch_ms: 1_000,
};
assert_eq!(a.media_to_epoch_ms(MediaTime(90_000)), 2_000);
assert_eq!(a.rfc3339(MediaTime(0)), "1970-01-01T00:00:01.000Z");
}
}