rstime 0.1.0

A zero-dependency Rust time library providing date, time, datetime types with formatting, parsing, Unix timestamps, and clock functionality.
Documentation
//! DateTime module
//!
//! Provides `DateTime` combining [`Date`] and [`Time`] with Unix
//! timestamp conversion and arithmetic.

use crate::date::{date_from_days, days_from_epoch, Date};
use crate::duration::TimeDelta;
use crate::time::Time;
use std::fmt;

/// A combined date and time value
///
/// Supports creation from components, Unix timestamp conversion,
/// and arithmetic with [`TimeDelta`].
///
/// # Examples
///
/// ```rust
/// use rstime::{DateTime, Date, Time};
///
/// let dt = DateTime::from_ymd_hms(2026, 5, 10, 14, 5, 9);
/// assert_eq!(dt.date(), &Date::new(2026, 5, 10));
/// assert_eq!(dt.time(), &Time::from_hms(14, 5, 9));
/// ```
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub struct DateTime {
    /// Date component
    pub date: Date,
    /// Time component
    pub time: Time,
}

impl DateTime {
    /// Create from a [`Date`] and [`Time`]
    pub fn new(date: Date, time: Time) -> Self {
        DateTime { date, time }
    }

    /// Create from year, month, day, hour, minute, second
    pub fn from_ymd_hms(
        year: i32,
        month: u8,
        day: u8,
        hour: u8,
        minute: u8,
        second: u8,
    ) -> Self {
        DateTime {
            date: Date::new(year, month, day),
            time: Time::from_hms(hour, minute, second),
        }
    }

    /// Create from year, month, day, hour, minute, second, millisecond
    pub fn from_ymd_hms_milli(
        year: i32,
        month: u8,
        day: u8,
        hour: u8,
        minute: u8,
        second: u8,
        millisecond: u16,
    ) -> Self {
        DateTime {
            date: Date::new(year, month, day),
            time: Time::new(hour, minute, second, millisecond),
        }
    }

    /// Returns a reference to the date component
    pub fn date(&self) -> &Date {
        &self.date
    }

    /// Returns a reference to the time component
    pub fn time(&self) -> &Time {
        &self.time
    }

    /// Unix timestamp in seconds since 1970-01-01 00:00:00 UTC
    ///
    /// ```rust
    /// use rstime::DateTime;
    ///
    /// let dt = DateTime::from_unix(0);
    /// assert_eq!(dt.date.year, 1970);
    /// assert_eq!(dt.date.month, 1);
    /// assert_eq!(dt.date.day, 1);
    /// ```
    pub fn unix_timestamp(&self) -> i64 {
        unix_timestamp(*self)
    }

    /// Create from a Unix timestamp (seconds since epoch)
    pub fn from_unix(secs: i64) -> Self {
        from_unix_timestamp(secs)
    }

    /// Unix timestamp in milliseconds
    pub fn unix_timestamp_millis(&self) -> i64 {
        unix_timestamp(*self) * 1000 + self.time.millisecond as i64
    }

    /// Create from a Unix timestamp in milliseconds
    pub fn from_unix_millis(ms: i64) -> Self {
        let secs = ms / 1000;
        let millis = (ms % 1000) as u16;
        let mut dt = from_unix_timestamp(secs);
        dt.time.millisecond = millis;
        dt
    }
}

fn unix_epoch_days() -> i64 {
    days_from_epoch(Date::new(1970, 1, 1))
}

fn unix_timestamp(dt: DateTime) -> i64 {
    let days = days_from_epoch(dt.date) - unix_epoch_days();
    days * 86400 + dt.time.total_seconds() as i64
}

fn from_unix_timestamp(secs: i64) -> DateTime {
    let days = unix_epoch_days() + secs / 86400;
    let remaining_secs = secs % 86400;
    let remaining_secs = if remaining_secs < 0 {
        86400 + remaining_secs
    } else {
        remaining_secs
    };
    let date = date_from_days(days);
    let time = Time::new(
        (remaining_secs / 3600) as u8 % 24,
        ((remaining_secs % 3600) / 60) as u8,
        (remaining_secs % 60) as u8,
        0,
    );
    DateTime { date, time }
}

impl fmt::Display for DateTime {
    /// Formats as `YYYY-MM-DD HH:MM:SS`
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{:04}-{:02}-{:02} {:02}:{:02}:{:02}",
            self.date.year,
            self.date.month,
            self.date.day,
            self.time.hour,
            self.time.minute,
            self.time.second
        )
    }
}

impl std::ops::Add<TimeDelta> for DateTime {
    type Output = DateTime;
    /// Add a time delta to the datetime
    fn add(self, delta: TimeDelta) -> DateTime {
        let total_secs = unix_timestamp(self) + delta.total_seconds() as i64;
        from_unix_timestamp(total_secs)
    }
}

impl std::ops::Sub<TimeDelta> for DateTime {
    type Output = DateTime;
    /// Subtract a time delta from the datetime
    fn sub(self, delta: TimeDelta) -> DateTime {
        let total_secs = unix_timestamp(self) - delta.total_seconds() as i64;
        from_unix_timestamp(total_secs)
    }
}

impl std::ops::Sub<DateTime> for DateTime {
    type Output = TimeDelta;
    /// Calculate the time difference between two datetimes
    fn sub(self, rhs: DateTime) -> TimeDelta {
        let diff = unix_timestamp(self) - unix_timestamp(rhs);
        TimeDelta::new(diff, 0)
    }
}