Documentation
/*
==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--

Dia-Time

Copyright (C) 2018-2022, 2024  Anonymous

There are several releases over multiple years,
they are listed as ranges, such as: "2018-2022".

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.

::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--
*/

//! # A small time kit
//!
//! ## Project
//!
//! - License: GNU Lesser General Public License, either version 3, or (at your option) any later version.
//! - _This project follows [Semantic Versioning 2.0.0]_
//!
//! ## Features
//!
//! - Constants as seconds: [`MINUTE`][const:MINUTE], [`HOUR`][const:HOUR], [`DAY`][const:DAY], [`WEEK`][const:WEEK].
//! - Constants as milliseconds: [`millis::SECOND`][const:millis/SECOND], [`millis::MINUTE`][const:millis/MINUTE],
//!   [`millis::HOUR`][const:millis/HOUR], [`millis::DAY`][const:millis/DAY], [`millis::WEEK`][const:millis/WEEK].
//! - Other modules: [`micros`][mod:micros], [`nanos`][mod:nanos], [`decis`][mod:decis], [`centis`][mod:centis], [`picos`][mod:picos],
//!   [`femtos`][mod:femtos], [`attos`][mod:attos], [`zeptos`][mod:zeptos], [`yoctos`][mod:yoctos].
//! - And some helper functions for formatting time...
//!
//! Some components require `lib-c` feature. If you can't access them, you should enable that feature. For example:
//!
//! ```toml
//! [dependencies]
//! dia-time = { version='...', features=['lib-c'] }
//! ```
//!
//! ## Notes
//!
//! Documentation is built with all features. Some of them are optional. If you see components from other crates, you can view source to see
//! what features are required.
//!
//! ## References
//!
//! - Wikipedia: <https://en.wikipedia.org/wiki/Second#SI_multiples>
//!
//! [Semantic Versioning 2.0.0]: https://semver.org/spec/v2.0.0.html
//! [const:MINUTE]: constant.MINUTE.html
//! [const:HOUR]: constant.HOUR.html
//! [const:DAY]: constant.DAY.html
//! [const:WEEK]: constant.WEEK.html
//! [mod:micros]: micros/index.html
//! [mod:nanos]: nanos/index.html
//! [mod:decis]: decis/index.html
//! [mod:centis]: centis/index.html
//! [mod:picos]: picos/index.html
//! [mod:femtos]: femtos/index.html
//! [mod:attos]: attos/index.html
//! [mod:zeptos]: zeptos/index.html
//! [mod:yoctos]: yoctos/index.html
//! [const:millis/SECOND]: millis/constant.SECOND.html
//! [const:millis/MINUTE]: millis/constant.MINUTE.html
//! [const:millis/HOUR]: millis/constant.HOUR.html
//! [const:millis/DAY]: millis/constant.DAY.html
//! [const:millis/WEEK]: millis/constant.WEEK.html

#![warn(missing_docs)]
#![no_std]
#![feature(doc_cfg)]

// ╔═════════════════╗
// ║   IDENTIFIERS   ║
// ╚═════════════════╝

macro_rules! crate_code_name    { () => { "dia-time" }}
macro_rules! crate_version      { () => { "9.0.0" }}

/// # Crate name
pub const NAME: &str = "Dia-Time";

/// # Crate code name
pub const CODE_NAME: &str = crate_code_name!();

/// # ID of this crate
pub const ID: &str = concat!(
    "88754f65-f8ad90df-c6f5d48f-3b7ec1e3-43dd5631-337efed4-cda6b228-5fb13c39-",
    "c88959ab-59b4eb2d-b6ea09d6-c3fbc084-c3b4f246-ce05808f-91bdb0dc-a06f9b1e",
);

/// # Crate version
pub const VERSION: &str = crate_version!();

/// # Crate release date (year/month/day)
pub const RELEASE_DATE: (u16, u8, u8) = (2024, 4, 18);

/// # Tag, which can be used for logging...
pub const TAG: &str = concat!(crate_code_name!(), "::88754f65::", crate_version!());

// ╔════════════════════╗
// ║   IMPLEMENTATION   ║
// ╚════════════════════╝

#[macro_use]
extern crate alloc;

#[cfg(feature="std")]
extern crate std;

use alloc::string::String;

#[test]
fn test_crate_version() {
    assert_eq!(VERSION, env!("CARGO_PKG_VERSION"));
}

use core::time::Duration;

/// # Makes new Error with formatted string, or without one
macro_rules! err {
    () => {
        $crate::Error::new(line!(), module_path!(), None)
    };
    ($s: literal) => {
        $crate::Error::new(line!(), module_path!(), Some(alloc::borrow::Cow::Borrowed($s)))
    };
    ($s: literal, $($arg: tt)+) => {
        $crate::Error::new(line!(), module_path!(), Some(alloc::borrow::Cow::Owned(alloc::format!($s, $($arg)+))))
    };
}

#[test]
fn test_macro_err() {
    use alloc::borrow::Cow;

    macro_rules! s_test { () => { "test" }}

    fn eq(first: Error, second: Error) -> bool {
        first.line() == second.line() && first.module_path() == second.module_path() && first.msg() == second.msg()
    }

    assert!(eq(err!(), Error::new(line!(), module_path!(), None)));
    assert!(eq(err!("test"), Error::new(line!(), module_path!(), Some(Cow::Borrowed(s_test!())))));
    assert!(eq(err!("{s:?}", s=s_test!()), Error::new(line!(), module_path!(), Some(Cow::Owned(alloc::format!("{:?}", s_test!()))))));
}

mod error;
mod month;
mod time;
mod weekday;

pub use self::{
    error::*,
    month::*,
    time::*,
    weekday::*,
};

pub mod debts;
pub mod decis;
pub mod centis;
pub mod millis;
pub mod micros;
pub mod nanos;
pub mod picos;
pub mod femtos;
pub mod attos;
pub mod zeptos;
pub mod yoctos;
pub mod symbols;

pub mod version_info;

/// # Result type used in this crate
pub type Result<T> = core::result::Result<T, Error>;

/// # 1 second
pub const SECOND: u64 = 1;

/// # 1 minute in seconds
pub const MINUTE: u64 = 60;

/// # 1 hour in seconds
pub const HOUR: u64 = 3_600;

/// # 1 day in seconds
pub const DAY: u64 = 86_400;

/// # 1 week in seconds
pub const WEEK: u64 = 604_800;

/// # 1 decasecond
pub const DECASECOND: u64 = 10;

/// # 1 hectosecond
pub const HECTOSECOND: u64 = 100;

/// # 1 kilosecond
pub const KILOSECOND: u64 = 1_000;

/// # 1 megasecond
pub const MEGASECOND: u64 = 1_000_000;

/// # 1 gigasecond
pub const GIGASECOND: u64 = 1_000_000_000;

/// # 1 terasecond
pub const TERASECOND: u64 = 1_000_000_000_000;

/// # 1 petasecond
pub const PETASECOND: u64 = 1_000_000_000_000_000;

/// # 1 exasecond
pub const EXASECOND: u64 = 1_000_000_000_000_000_000;

/// # 1 zettasecond
pub const ZETTASECOND: u128 = 1_000_000_000_000_000_000_000;

/// # 1 yottasecond
pub const YOTTASECOND: u128 = 1_000_000_000_000_000_000_000_000;

/// # Converts duration to days, hours, minutes, seconds.
///
/// # Examples
///
/// ```
/// use std::time::Duration;
/// use dia_time::{self, HOUR, MINUTE};
///
/// assert_eq!(
///     dia_time::duration_to_dhms(&Duration::from_secs(HOUR + MINUTE)),
///     (0, 1, 1, 0)
/// );
/// ```
pub fn duration_to_dhms(duration: &Duration) -> (u64, u64, u64, u64) {
    let seconds = duration.as_secs();
    let (minutes, seconds) = {
        let minutes = seconds / 60;
        (minutes, seconds - minutes * 60)
    };
    let (hours, minutes) = {
        let hours = minutes / 60;
        (hours, minutes - hours * 60)
    };
    let (days, hours) = {
        let days = hours / 24;
        (days, hours - days * 24)
    };

    (days as u64, hours as u64, minutes as u64, seconds as u64)
}

/// # Examples: `02:53:58.018`
///
/// ```
/// use std::time::Duration;
/// use dia_time::{self, HOUR, MINUTE};
///
/// assert_eq!(
///     dia_time::format_hms_ms(&Duration::from_secs(HOUR * 2 + MINUTE * 53 + 58)),
///     "02:53:58.000"
/// );
/// ```
pub fn format_hms_ms(duration: &Duration) -> String {
    let (_, h, m, s) = duration_to_dhms(duration);
    format!("{h:02}:{m:02}:{s:02}.{ms:03}", h=h, m=m, s=s, ms=duration.subsec_millis())
}

/// # Examples: `3d, 02:53:58.018`
///
/// ```
/// use std::time::Duration;
/// use dia_time::{self, DAY, HOUR, MINUTE};
///
/// assert_eq!(
///     dia_time::format_day_hms_ms(&Duration::from_secs(DAY * 3 + HOUR * 2 + MINUTE * 53 + 58)),
///     "3d, 02:53:58.000"
/// );
/// ```
pub fn format_day_hms_ms(duration: &Duration) -> String {
    format!("{d}d, {t}", d=duration.as_secs() / DAY, t=format_hms_ms(duration))
}

/// # If the duration is within a day, forwards to [`::format_hms_ms()`]; otherwise, forwards to [`::format_day_hms_ms()`].
///
/// # Examples
///
/// ```
/// use std::time::Duration;
/// use dia_time::{self, DAY, HOUR, MINUTE};
///
/// assert_eq!(
///     dia_time::smart_format_day_hms_ms(&Duration::from_secs(HOUR * 2 + MINUTE * 53 + 58)),
///     "02:53:58.000"
/// );
/// assert_eq!(dia_time::smart_format_day_hms_ms(&Duration::from_secs(DAY * 9 + HOUR * 2 + MINUTE * 53 + 58)), "9d, 02:53:58.000");
/// ```
///
/// [`::format_hms_ms()`]: fn.format_hms_ms.html
/// [`::format_day_hms_ms()`]: fn.format_day_hms_ms.html
pub fn smart_format_day_hms_ms(duration: &Duration) -> String {
    match duration.as_secs() / DAY {
        0 => format_hms_ms(duration),
        _ => format_day_hms_ms(duration),
    }
}

/// # Examples: `02:53:58`
///
/// ```
/// use std::time::Duration;
/// use dia_time::{self, HOUR, MINUTE};
///
/// assert_eq!(
///     dia_time::format_hms(&Duration::from_secs(HOUR * 2 + MINUTE * 53 + 58)),
///     "02:53:58"
/// );
/// ```
pub fn format_hms(duration: &Duration) -> String {
    let (_, h, m, s) = duration_to_dhms(duration);
    format!("{h:02}:{m:02}:{s:02}", h=h, m=m, s=s)
}

/// # Examples: `9d, 02:53:58`
///
/// ```
/// use std::time::Duration;
/// use dia_time::{self, DAY, HOUR, MINUTE};
///
/// assert_eq!(
///     dia_time::format_day_hms(&Duration::from_secs(DAY * 9 + HOUR * 2 + MINUTE * 53 + 58)),
///     "9d, 02:53:58"
/// );
/// ```
pub fn format_day_hms(duration: &Duration) -> String {
    format!("{d}d, {t}", d=duration.as_secs() / DAY, t=format_hms(duration))
}

/// # If the duration is within a day, forwards to [`::format_hms()`]; otherwise, forwards to [`::format_day_hms()`].
///
/// # Examples
///
/// ```
/// use std::time::Duration;
/// use dia_time::{self, DAY, HOUR, MINUTE};
///
/// assert_eq!(
///     dia_time::smart_format_day_hms(&Duration::from_secs(HOUR * 2 + MINUTE * 53 + 58)),
///     "02:53:58"
/// );
/// assert_eq!(
///     dia_time::smart_format_day_hms(&Duration::from_secs(DAY * 9 + HOUR * 2 + MINUTE * 53 + 58)),
///     "9d, 02:53:58"
/// );
/// ```
///
/// [`::format_hms()`]: fn.format_hms.html
/// [`::format_day_hms()`]: fn.format_day_hms.html
pub fn smart_format_day_hms(duration: &Duration) -> String {
    match duration.as_secs() / DAY {
        0 => format_hms(duration),
        _ => format_day_hms(duration),
    }
}