dtime 1.0.2

A professional, cross-platform datetime display utility with colored output and millisecond precision
Documentation
//! File: src\lib.rs
//! Author: Hadi Cahyadi <cumulus13@gmail.com>
//! Date: 2026-05-09
//! Description:
//! License: MIT

//! # dtime
//!
//! A professional, cross-platform datetime display utility with colored output
//! and millisecond precision.
//!
//! ## Features
//!
//! - Display local and UTC time with millisecond precision
//! - Unix timestamps in seconds and milliseconds
//! - Timezone information with offset
//! - Colored output using true color (24-bit)
//! - Cross-platform support (Windows, Linux, macOS)
//! - JSON output mode for scripting
//!
//! ## Usage
//!
//! ```rust
//! use dtime::{DateTimeInfo, Config};
//!
//! let config = Config::default();
//! let dt_info = DateTimeInfo::new(&config);
//! println!("{}", dt_info.format_colored());
//! ```

pub mod config;
pub mod display;
pub mod error;

// Re-export key types for convenience
pub use config::Config;

use chrono::{Datelike, Local, Timelike, Utc};
use std::time::{SystemTime, UNIX_EPOCH};

/// Represents comprehensive datetime information
#[derive(Debug, Clone)]
pub struct DateTimeInfo {
    /// Local datetime formatted string
    pub local_time: String,
    /// UTC datetime formatted string
    pub utc_time: String,
    /// Unix timestamp in seconds
    pub unix_seconds: u64,
    /// Unix timestamp in milliseconds
    pub unix_milliseconds: u128,
    /// Timezone abbreviation
    pub timezone: String,
    /// UTC offset
    pub offset: String,
    /// Day of week
    pub weekday: String,
    /// ISO 8601 formatted local time
    pub iso_local: String,
    /// ISO 8601 formatted UTC time
    pub iso_utc: String,
    /// Config
    pub config: Config,
}

impl DateTimeInfo {
    /// Create new DateTimeInfo with current time
    pub fn new(config: &Config) -> Self {
        let now = SystemTime::now();

        // Calculate duration since UNIX epoch
        let duration = now.duration_since(UNIX_EPOCH).unwrap_or_default();

        let local = Local::now();
        let utc = Utc::now();

        // Format local time
        let local_time = format!(
            "{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:03} {}",
            local.year(),
            local.month(),
            local.day(),
            local.hour(),
            local.minute(),
            local.second(),
            local.timestamp_subsec_millis(),
            local.format("%Z")
        );

        // Format UTC time
        let utc_time = format!(
            "{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:03} UTC",
            utc.year(),
            utc.month(),
            utc.day(),
            utc.hour(),
            utc.minute(),
            utc.second(),
            utc.timestamp_subsec_millis(),
        );

        // Weekday
        let weekday = local.format("%A").to_string();

        // ISO 8601 formats
        let iso_local = local.format("%Y-%m-%dT%H:%M:%S%.3f%:z").to_string();
        let iso_utc = utc.format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string();

        DateTimeInfo {
            local_time,
            utc_time,
            unix_seconds: duration.as_secs(),
            unix_milliseconds: duration.as_millis(),
            timezone: local.format("%Z").to_string(),
            offset: local.format("%:z").to_string(),
            weekday,
            iso_local,
            iso_utc,
            config: config.clone(),
        }
    }

    /// Format as colored string
    pub fn format_colored(&self) -> String {
        display::format_colored(self)
    }

    /// Format as plain text
    pub fn format_plain(&self) -> String {
        display::format_plain(self)
    }

    /// Format as JSON
    pub fn format_json(&self) -> String {
        serde_json::to_string_pretty(&serde_json::json!({
            "local_time": self.local_time,
            "utc_time": self.utc_time,
            "unix_seconds": self.unix_seconds,
            "unix_milliseconds": self.unix_milliseconds,
            "timezone": self.timezone,
            "offset": self.offset,
            "weekday": self.weekday,
            "iso_local": self.iso_local,
            "iso_utc": self.iso_utc,
        }))
        .unwrap_or_default()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_datetime_info_creation() {
        let config = Config::default();
        let dt = DateTimeInfo::new(&config);

        assert!(!dt.local_time.is_empty());
        assert!(!dt.utc_time.is_empty());
        assert!(dt.unix_seconds > 0);
        assert!(dt.unix_milliseconds > 0);
        assert!(!dt.timezone.is_empty());
        assert!(dt.offset.starts_with('+') || dt.offset.starts_with('-'));
    }

    #[test]
    fn test_format_plain() {
        let config = Config::default();
        let dt = DateTimeInfo::new(&config);
        let formatted = dt.format_plain();

        assert!(formatted.contains("System Time"));
        assert!(formatted.contains("Local Time"));
        assert!(formatted.contains("UTC Time"));
    }

    #[test]
    fn test_format_json() {
        let config = Config::default();
        let dt = DateTimeInfo::new(&config);
        let json = dt.format_json();

        assert!(json.contains("local_time"));
        assert!(json.contains("utc_time"));
        assert!(json.contains("unix_seconds"));

        // Verify it's valid JSON
        let _: serde_json::Value = serde_json::from_str(&json).unwrap();
    }
}