1use crate::event::MediaTime;
3use alloc::{format, string::String};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8pub struct TimeAnchor {
9 pub pts_90k: u64,
11 pub utc_epoch_ms: i64,
13}
14
15impl TimeAnchor {
16 pub fn media_to_epoch_ms(&self, t: MediaTime) -> i64 {
18 let delta_ticks = t.0 as i64 - self.pts_90k as i64;
19 self.utc_epoch_ms + (delta_ticks as i128 * 1000 / crate::PTS_HZ as i128) as i64
21 }
22
23 pub fn rfc3339(&self, t: MediaTime) -> String {
25 format_rfc3339_ms(self.media_to_epoch_ms(t))
26 }
27}
28
29pub fn format_rfc3339_ms(epoch_ms: i64) -> String {
31 let (secs, ms) = (epoch_ms.div_euclid(1000), epoch_ms.rem_euclid(1000));
32 let days = secs.div_euclid(86_400);
33 let tod = secs.rem_euclid(86_400);
34 let (h, m, s) = (tod / 3600, (tod % 3600) / 60, tod % 60);
35 let (y, mo, d) = civil_from_days(days);
36 format!(
37 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z",
38 y, mo, d, h, m, s, ms
39 )
40}
41
42fn civil_from_days(z: i64) -> (i64, u32, u32) {
44 let z = z + 719_468;
45 let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
46 let doe = z - era * 146_097; let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365; let y = yoe + era * 400;
49 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)
54}
55
56#[cfg(test)]
59mod tests {
60 use super::*;
61 use crate::event::MediaTime;
62
63 #[test]
64 fn epoch_zero_formats_unix_epoch() {
65 assert_eq!(format_rfc3339_ms(0), "1970-01-01T00:00:00.000Z");
66 assert_eq!(format_rfc3339_ms(86_400_000), "1970-01-02T00:00:00.000Z");
67 assert_eq!(format_rfc3339_ms(1_000), "1970-01-01T00:00:01.000Z");
68 }
69
70 #[test]
71 fn anchor_maps_media_to_wallclock() {
72 let a = TimeAnchor {
74 pts_90k: 0,
75 utc_epoch_ms: 1_000,
76 };
77 assert_eq!(a.media_to_epoch_ms(MediaTime(90_000)), 2_000);
78 assert_eq!(a.rfc3339(MediaTime(0)), "1970-01-01T00:00:01.000Z");
79 }
80}