render_video/src/time.rs

245 lines
4.6 KiB
Rust
Raw Normal View History

2023-10-22 22:44:59 +02:00
use anyhow::bail;
use std::{
fmt::{self, Display, Write as _},
2023-10-30 21:26:17 +01:00
ops::{Add, Sub},
2023-10-22 22:44:59 +02:00
str::FromStr
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Date {
2023-10-28 23:38:17 +02:00
pub year: u16,
pub month: u8,
pub day: u8
2023-10-22 22:44:59 +02:00
}
impl FromStr for Date {
type Err = anyhow::Error;
fn from_str(s: &str) -> anyhow::Result<Self> {
parse_date(s)
}
}
impl Display for Date {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&format_date(*self))
}
}
/// Parse a date in YYMMDD format.
pub fn parse_date(s: &str) -> anyhow::Result<Date> {
let int: u32 = s.parse()?;
let year = int / 10000 + 2000;
if year / 10 != 202 {
bail!("Expected a date in 202X");
}
let month = int / 100 % 100;
if month < 1 || month > 12 {
bail!("Invalid month: {month}");
}
let day = int % 100;
if day < 1 || day > 31 {
bail!("Invalid day: {day}");
}
Ok(Date {
year: year as _,
month: month as _,
day: day as _
})
}
/// Format a date in YYMMDD format.
pub fn format_date(d: Date) -> String {
format!("{:02}{:02}{:02}", d.year % 100, d.month, d.day)
}
/// Format a date in DD. MMMM YYYY format.
pub fn format_date_long(d: Date) -> String {
let month = match d.month {
1 => "Januar",
2 => "Februar",
3 => "März",
4 => "April",
5 => "Mai",
6 => "Juni",
7 => "Juli",
8 => "August",
9 => "September",
10 => "Oktober",
11 => "November",
12 => "Dezember",
_ => unreachable!()
};
format!("{:02}. {month} {:04}", d.day, d.year)
}
2023-10-30 21:38:16 +01:00
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
2023-10-22 22:44:59 +02:00
pub struct Time {
2023-10-28 23:38:17 +02:00
pub seconds: u32,
pub micros: u32
2023-10-22 22:44:59 +02:00
}
2023-10-30 21:26:17 +01:00
impl Add for Time {
type Output = Self;
fn add(self, rhs: Self) -> Self {
let mut seconds = self.seconds + rhs.seconds;
let mut micros = self.micros + rhs.micros;
if micros >= 1_000_000 {
seconds += 1;
micros -= 1_000_000;
}
Self { seconds, micros }
}
}
impl Sub for Time {
type Output = Self;
fn sub(mut self, rhs: Self) -> Self {
if rhs.micros > self.micros {
self.seconds -= 1;
self.micros += 1_000_000;
}
Self {
seconds: self.seconds - rhs.seconds,
micros: self.micros - rhs.micros
}
}
}
2023-10-22 22:44:59 +02:00
impl FromStr for Time {
type Err = anyhow::Error;
fn from_str(s: &str) -> anyhow::Result<Self> {
parse_time(s)
}
}
impl Display for Time {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.seconds)?;
if self.micros != 0 {
write!(f, ".{}", self.micros)?;
}
Ok(())
2023-10-22 22:44:59 +02:00
}
}
/// Parse a time in hh:mm:ss:sub or hh:mm:ss.micros format.
pub fn parse_time(s: &str) -> anyhow::Result<Time> {
let split = s.split(':');
let mut seconds = 0;
let mut micros = 0;
for (i, digits) in split.enumerate() {
// we cannot have more than 4 splits
if i > 3 {
bail!("Expected end of input, found ':'")
}
// we cannot process data after we have parsed micros
if micros != 0 {
bail!("Unexpected microsecond notation");
}
// the 4th split is subseconds, converting to micros
if i == 3 {
micros = digits.parse::<u32>()? * 1000000 / 60;
continue;
}
// add to seconds and potentially micros
seconds *= 60;
if let Some(idx) = digits.find('.') {
seconds += digits[0 .. idx].parse::<u32>()?;
let micros_digits = &digits[idx + 1 ..];
micros = micros_digits.parse::<u32>()?
* 10_u32.pow((6 - micros_digits.len()) as _);
} else {
seconds += digits.parse::<u32>()?;
}
}
Ok(Time { seconds, micros })
}
/// Format a time in hh:mm:ss.micros format.
pub fn format_time(t: Time) -> String {
let h = t.seconds / 3600;
let m = (t.seconds / 60) % 60;
let s = t.seconds % 60;
let mut out = String::new();
if h != 0 {
write!(out, "{h}:").unwrap();
}
if h != 0 || m != 0 {
write!(out, "{m:02}:{s:02}").unwrap();
} else {
write!(out, "{s}").unwrap();
}
if t.micros != 0 {
write!(out, ".{:06}", t.micros).unwrap();
out.truncate(out.trim_end_matches('0').len());
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn test_time_parse_only(s: &str, t: Time) {
let time = parse_time(s).unwrap();
assert_eq!(time, t);
}
fn test_time(s: &str, t: Time) {
test_time_parse_only(s, t);
assert_eq!(format_time(t), s);
}
#[test]
fn test_time_0() {
test_time("0", Time {
seconds: 0,
micros: 0
});
}
#[test]
fn test_time_2_3() {
test_time("02:03", Time {
seconds: 123,
micros: 0
});
}
#[test]
fn test_time_1_2_3() {
test_time("1:02:03", Time {
seconds: 3723,
micros: 0
});
}
#[test]
fn test_time_subsecs() {
test_time_parse_only("1:02:03:30", Time {
seconds: 3723,
micros: 500000
});
}
#[test]
fn test_time_micros() {
test_time("1:02:03.5", Time {
seconds: 3723,
micros: 500000
});
}
}