use core::time::Duration;
use crate::error::{ErrorKind, ResultExt};
use crate::{AsTime, Error};
const SECONDS_PER_DAY: i64 = 86_400;
const HOURS_PER_DAY: i64 = 24;
const SECONDS_PER_HOUR: i64 = 3_600;
const MINUTES_PER_HOUR: i64 = 60;
const SECONDS_PER_MINUTE: i64 = 60;
const MILLISECONDS_PER_SECOND: i64 = 1_000;
const MICROSECONDS_PER_SECOND: i64 = 1_000_000;
const NANOSECONDS_PER_SECOND: i64 = 1_000_000_000;
const NANOSECONDS_PER_MICROSECOND: i128 = 1_000;
const NANOSECONDS_PER_MILLISECOND: i128 = 1_000_000;
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub struct TimeInterval {
secs: i64,
nsec: i32,
}
impl TimeInterval {
pub const MAX: Self = Self {
secs: i64::MAX,
nsec: (NANOSECONDS_PER_SECOND - 1) as i32,
};
pub const MIN: Self = Self {
secs: i64::MIN,
nsec: (1 - NANOSECONDS_PER_SECOND) as i32,
};
pub const ZERO: Self = Self::new(0, 0);
}
impl TimeInterval {
#[must_use]
pub const fn new(mut seconds: i64, mut nanoseconds: i32) -> Self {
seconds = opt_expect!(
seconds.checked_add(nanoseconds as i64 / NANOSECONDS_PER_SECOND),
"overflow in TimeInterval::new"
);
nanoseconds %= NANOSECONDS_PER_SECOND as i32;
if seconds.is_positive() && nanoseconds.is_negative() {
seconds -= 1;
nanoseconds += NANOSECONDS_PER_SECOND as i32;
} else if seconds.is_negative() && nanoseconds.is_positive() {
seconds += 1;
nanoseconds -= NANOSECONDS_PER_SECOND as i32;
}
Self {
secs: seconds,
nsec: nanoseconds,
}
}
#[must_use]
pub fn between<T: AsTime>(start: T, end: T) -> Self {
let (start_secs, start_nsec) = start.as_timestamp();
let (end_secs, end_nsec) = end.as_timestamp();
let secs = end_secs
.checked_sub(start_secs)
.expect("overflow when subtracting timestamps");
let nsec = end_nsec as i32 - start_nsec as i32;
Self::new(secs, nsec)
}
}
impl TimeInterval {
#[must_use]
pub const fn from_hours(hours: i32) -> Self {
let secs = opt_expect!(
(hours as i64).checked_mul(SECONDS_PER_HOUR),
"overflow in TimeInterval::from_hours"
);
Self::from_seconds(secs)
}
#[must_use]
pub const fn from_minutes(minutes: i64) -> Self {
let secs = opt_expect!(
minutes.checked_mul(SECONDS_PER_MINUTE),
"overflow in TimeInterval::from_minutes"
);
Self::from_seconds(secs)
}
#[must_use]
pub const fn from_seconds(seconds: i64) -> Self {
Self {
secs: seconds,
nsec: 0,
}
}
#[must_use]
pub const fn from_milliseconds(milliseconds: i128) -> Self {
let secs = (milliseconds / MILLISECONDS_PER_SECOND as i128) as i64;
let nsec =
((milliseconds % MILLISECONDS_PER_SECOND as i128) * NANOSECONDS_PER_MILLISECOND) as i32;
Self { secs, nsec }
}
#[must_use]
pub const fn from_microseconds(microseconds: i128) -> Self {
let secs = (microseconds / MICROSECONDS_PER_SECOND as i128) as i64;
let nsec =
((microseconds % MICROSECONDS_PER_SECOND as i128) * NANOSECONDS_PER_MICROSECOND) as i32;
Self { secs, nsec }
}
#[must_use]
pub const fn from_nanoseconds(nanoseconds: i128) -> Self {
let secs = (nanoseconds / NANOSECONDS_PER_SECOND as i128) as i64;
let nsec = (nanoseconds % NANOSECONDS_PER_SECOND as i128) as i32;
Self { secs, nsec }
}
}
impl TimeInterval {
#[must_use]
pub const fn days(self) -> i64 {
self.secs / SECONDS_PER_DAY
}
#[must_use]
pub const fn hours(self) -> i8 {
((self.secs / SECONDS_PER_HOUR) % HOURS_PER_DAY) as i8
}
#[must_use]
pub const fn minutes(self) -> i8 {
((self.secs / SECONDS_PER_MINUTE) % MINUTES_PER_HOUR) as i8
}
#[must_use]
pub const fn seconds(self) -> i8 {
(self.secs % SECONDS_PER_MINUTE) as i8
}
#[must_use]
pub const fn milliseconds(self) -> i16 {
(self.nanoseconds() / 1_000_000) as i16
}
#[must_use]
pub const fn microseconds(self) -> i16 {
(self.nanoseconds() / 1_000) as i16
}
#[must_use]
pub const fn nanoseconds(self) -> i32 {
self.nsec
}
}
impl TimeInterval {
#[must_use]
pub const fn is_zero(self) -> bool {
self.secs == 0 && self.nsec == 0
}
#[must_use]
pub const fn is_positive(self) -> bool {
self.secs.is_positive() || self.nsec.is_positive()
}
#[must_use]
pub const fn is_negative(self) -> bool {
self.secs.is_negative() || self.nsec.is_negative()
}
#[must_use]
pub const fn abs(self) -> Self {
match self.secs.checked_abs() {
Some(secs) => Self::new(secs, self.nsec.abs()),
None => Self::MAX,
}
}
}
impl TimeInterval {
#[must_use]
pub fn as_total_days_f64(self) -> f64 {
self.as_total_seconds_f64() / SECONDS_PER_DAY as f64
}
#[must_use]
pub const fn as_total_hours(self) -> i32 {
(self.secs / SECONDS_PER_HOUR) as i32
}
#[must_use]
pub const fn as_total_minutes(self) -> i64 {
self.secs / SECONDS_PER_MINUTE
}
#[must_use]
pub const fn as_total_seconds(self) -> i64 {
self.secs
}
#[must_use]
pub fn as_total_seconds_f64(self) -> f64 {
self.secs as f64 + self.nsec as f64 / NANOSECONDS_PER_SECOND as f64
}
#[must_use]
pub const fn as_total_milliseconds(self) -> i128 {
self.as_total_nanoseconds() / 1_000_000
}
#[must_use]
pub const fn as_total_microseconds(self) -> i128 {
self.as_total_nanoseconds() / 1_000
}
#[must_use]
pub const fn as_total_nanoseconds(self) -> i128 {
(self.secs as i128 * NANOSECONDS_PER_SECOND as i128) + self.nsec as i128
}
}
impl TryFrom<Duration> for TimeInterval {
type Error = Error;
fn try_from(value: Duration) -> Result<Self, Self::Error> {
let secs: i64 = value.as_secs().try_into().context(ErrorKind::OutOfRange)?;
let nsec = value.subsec_nanos() as i32;
Ok(Self { secs, nsec })
}
}
impl TryFrom<TimeInterval> for Duration {
type Error = Error;
fn try_from(value: TimeInterval) -> Result<Self, Self::Error> {
let secs: u64 = value.secs.try_into().context(ErrorKind::OutOfRange)?;
let nsec: u32 = value.nsec.try_into().context(ErrorKind::OutOfRange)?;
Ok(Self::new(secs, nsec))
}
}
#[cfg(test)]
mod tests {
use float_cmp::assert_approx_eq;
use test_case::test_case;
use crate::interval::TimeInterval;
#[test_case(0, 0, 0)]
#[test_case(23_567_894, 23_567, 23)]
#[test_case(2_335_325_235_567_894, 2_335_325_235_567, 2_335_325_235)]
fn nanoseconds_to_totals(nanos: i128, micros: i128, millis: i128) {
let interval = TimeInterval::from_nanoseconds(nanos);
assert_eq!(interval.as_total_nanoseconds(), nanos);
assert_eq!(interval.as_total_microseconds(), micros);
assert_eq!(interval.as_total_milliseconds(), millis);
}
#[test_case(-23_567_894, -23_567, -23)]
#[test_case(-2_335_325_235_567_894, -2_335_325_235_567, -2_335_325_235)]
fn negative_nanoseconds_to_totals(nanos: i128, micros: i128, millis: i128) {
let interval = TimeInterval::from_nanoseconds(nanos);
assert_eq!(interval.as_total_nanoseconds(), nanos);
assert_eq!(interval.as_total_microseconds(), micros);
assert_eq!(interval.as_total_milliseconds(), millis);
}
#[test_case(0, 0.0)]
#[test_case(23567894, 0.023567894)]
#[test_case(2335325235567894, 2335325.235567894)]
fn nanoseconds_to_total_seconds_f64(nanoseconds: i128, seconds: f64) {
let interval = TimeInterval::from_nanoseconds(nanoseconds);
assert_approx_eq!(f64, interval.as_total_seconds_f64(), seconds);
}
#[test_case(-23567894, -0.023567894000000034)]
#[test_case(-2335325235567894, -2335325.235567894)]
fn negative_nanoseconds_to_total_seconds_f64(nanoseconds: i128, seconds: f64) {
let interval = TimeInterval::from_nanoseconds(nanoseconds);
assert_approx_eq!(f64, interval.as_total_seconds_f64(), seconds);
}
#[test_case(0, (0, 0, 0))]
#[test_case(50, (0, 0, 50))]
#[test_case(1229, (0, 20, 29))]
#[test_case(4189, (1, 9, 49))]
#[test_case(23567894, (6546, 38, 14))]
fn seconds_to_components(seconds: i64, components: (i32, i8, i8)) {
let interval = TimeInterval::from_seconds(seconds);
assert_eq!(
(
interval.as_total_hours(),
interval.minutes(),
interval.seconds()
),
components
);
}
#[test_case(-50, (0, 0, -50))]
#[test_case(-1229, (0, -20, -29))]
#[test_case(-4189, (-1, -9, -49))]
#[test_case(-23567894, (-6546, -38, -14))]
fn negative_seconds_to_components(seconds: i64, components: (i32, i8, i8)) {
let interval = TimeInterval::from_seconds(seconds);
assert_eq!(
(
interval.as_total_hours(),
interval.minutes(),
interval.seconds()
),
components
);
}
#[test_case(0, (0, 0, 0, 0))]
#[test_case(23_567_894, (0, 0, 0, 23_567_894))]
#[test_case(2_335_325_235_567_894, (648, 42, 5, 235_567_894))]
fn nanoseconds_to_components(nanos: i128, components: (i32, i8, i8, i32)) {
let interval = TimeInterval::from_nanoseconds(nanos);
assert_eq!(
(
interval.as_total_hours(),
interval.minutes(),
interval.seconds(),
interval.nanoseconds()
),
components
);
}
#[test_case(-23_567_894, (0, 0, 0, -23_567_894))]
#[test_case(-2_335_325_235_567_894, (-648, -42, -5, -235_567_894))]
fn negative_nanoseconds_to_components(nanos: i128, components: (i32, i8, i8, i32)) {
let interval = TimeInterval::from_nanoseconds(nanos);
assert_eq!(
(
interval.as_total_hours(),
interval.minutes(),
interval.seconds(),
interval.nanoseconds()
),
components
);
}
}