use std::time::{Duration, SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone, Copy, Default)]
pub struct RecordingTime {
pub timestamp_ns: i64,
pub tz_offset_min: i16,
pub dst_offset_min: i16,
}
impl RecordingTime {
pub fn new(timestamp_ns: i64, tz_offset_min: i16, dst_offset_min: i16) -> Self {
RecordingTime {
timestamp_ns,
tz_offset_min,
dst_offset_min,
}
}
pub fn as_unix_seconds(&self) -> f64 {
self.timestamp_ns as f64 / 1_000_000_000.0
}
pub fn as_system_time(&self) -> Option<SystemTime> {
if self.timestamp_ns >= 0 {
let duration = Duration::from_nanos(self.timestamp_ns as u64);
Some(UNIX_EPOCH + duration)
} else {
None
}
}
pub fn total_utc_offset_min(&self) -> i16 {
self.tz_offset_min + self.dst_offset_min
}
pub fn relative_to_absolute(&self, relative_seconds: f64) -> f64 {
self.as_unix_seconds() + relative_seconds
}
pub fn to_iso8601(&self) -> String {
let secs = self.timestamp_ns / 1_000_000_000;
let nanos = (self.timestamp_ns % 1_000_000_000) as u32;
let days_since_epoch = secs / 86400;
let time_of_day = secs % 86400;
let hours = time_of_day / 3600;
let minutes = (time_of_day % 3600) / 60;
let seconds = time_of_day % 60;
let millis = nanos / 1_000_000;
let mut year = 1970i64;
let mut remaining_days = days_since_epoch;
loop {
let days_in_year = if is_leap_year(year) { 366 } else { 365 };
if remaining_days < days_in_year {
break;
}
remaining_days -= days_in_year;
year += 1;
}
let (month, day) = day_of_year_to_month_day(remaining_days as u32 + 1, is_leap_year(year));
format!(
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z",
year, month, day, hours, minutes, seconds, millis
)
}
}
fn is_leap_year(year: i64) -> bool {
(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
}
fn day_of_year_to_month_day(day_of_year: u32, leap: bool) -> (u32, u32) {
let days_in_months: [u32; 12] = if leap {
[31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
} else {
[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
};
let mut remaining = day_of_year;
for (month_idx, &days) in days_in_months.iter().enumerate() {
if remaining <= days {
return (month_idx as u32 + 1, remaining);
}
remaining -= days;
}
(12, 31) }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TimeAxisType {
#[default]
Time,
Angle,
Distance,
Index,
}
#[derive(Debug, Clone, Default)]
pub struct TimeAxis {
pub axis_type: TimeAxisType,
pub unit: String,
pub master_channel_name: Option<String>,
}
impl TimeAxis {
pub fn new(axis_type: TimeAxisType, unit: String) -> Self {
TimeAxis {
axis_type,
unit,
master_channel_name: None,
}
}
pub fn seconds() -> Self {
TimeAxis::new(TimeAxisType::Time, "s".to_string())
}
pub fn angle() -> Self {
TimeAxis::new(TimeAxisType::Angle, "rad".to_string())
}
pub fn distance() -> Self {
TimeAxis::new(TimeAxisType::Distance, "m".to_string())
}
pub fn index() -> Self {
TimeAxis::new(TimeAxisType::Index, "".to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_recording_time_basic() {
let time = RecordingTime::new(1_640_000_000_000_000_000, 60, 0);
assert!((time.as_unix_seconds() - 1640000000.0).abs() < 0.001);
assert_eq!(time.total_utc_offset_min(), 60);
}
#[test]
fn test_recording_time_system_time() {
let time = RecordingTime::new(1000000000, 0, 0);
let sys_time = time.as_system_time();
assert!(sys_time.is_some());
}
#[test]
fn test_recording_time_relative_to_absolute() {
let time = RecordingTime::new(1_000_000_000_000, 0, 0); let absolute = time.relative_to_absolute(5.0);
assert!((absolute - 1005.0).abs() < 0.001);
}
#[test]
fn test_time_axis() {
let axis = TimeAxis::seconds();
assert_eq!(axis.axis_type, TimeAxisType::Time);
assert_eq!(axis.unit, "s");
let axis = TimeAxis::angle();
assert_eq!(axis.axis_type, TimeAxisType::Angle);
}
#[test]
fn test_leap_year() {
assert!(is_leap_year(2000));
assert!(is_leap_year(2004));
assert!(!is_leap_year(1900));
assert!(!is_leap_year(2001));
}
}