teaql-tool-std 0.1.2

Zero-dependency standard utilities for the TeaQL Tool ecosystem.
Documentation
use chrono::{DateTime, Datelike, Duration, Months, NaiveDate, TimeZone, Utc};
use chrono_tz::Tz;
use std::str::FromStr;
use teaql_tool_core::{MustPurpose, Result, TeaQLToolError};

pub struct TimeTool;

impl TimeTool {
    pub fn new() -> Self {
        Self
    }

    pub fn now(&self) -> MustPurpose<DateTime<Utc>> {
        MustPurpose::new(Utc::now())
    }

    pub fn today(&self) -> MustPurpose<NaiveDate> {
        MustPurpose::new(Utc::now().date_naive())
    }

    pub fn parse_date(&self, s: &str) -> Result<MustPurpose<NaiveDate>> {
        NaiveDate::from_str(s)
            .map(MustPurpose::new)
            .map_err(|e| TeaQLToolError::ParseError(e.to_string()))
    }

    pub fn parse_datetime(&self, s: &str) -> Result<MustPurpose<DateTime<Utc>>> {
        s.parse::<DateTime<Utc>>()
            .map(MustPurpose::new)
            .map_err(|e| TeaQLToolError::ParseError(e.to_string()))
    }

    pub fn add_days(&self, dt: DateTime<Utc>, days: i64) -> MustPurpose<DateTime<Utc>> {
        MustPurpose::new(dt + Duration::days(days))
    }

    pub fn add_months(&self, dt: DateTime<Utc>, months: u32) -> MustPurpose<DateTime<Utc>> {
        MustPurpose::new(dt.checked_add_months(Months::new(months)).unwrap_or(dt))
    }

    pub fn start_of_day(&self, dt: DateTime<Utc>) -> MustPurpose<DateTime<Utc>> {
        MustPurpose::new(
            Utc.with_ymd_and_hms(dt.year(), dt.month(), dt.day(), 0, 0, 0)
                .unwrap(),
        )
    }

    pub fn end_of_day(&self, dt: DateTime<Utc>) -> MustPurpose<DateTime<Utc>> {
        MustPurpose::new(self.start_of_day(dt).purpose("internal") + Duration::days(1) - Duration::nanoseconds(1))
    }

    pub fn days_between(&self, dt1: DateTime<Utc>, dt2: DateTime<Utc>) -> MustPurpose<i64> {
        let dur = dt2.signed_duration_since(dt1);
        MustPurpose::new(dur.num_days())
    }

    pub fn to_timezone(&self, dt: DateTime<Utc>, tz_str: &str) -> Result<MustPurpose<DateTime<Tz>>> {
        let tz: Tz = tz_str
            .parse()
            .map_err(|e| TeaQLToolError::InvalidArgument(format!("{}", e)))?;
        Ok(MustPurpose::new(dt.with_timezone(&tz)))
    }
}

impl Default for TimeTool {
    fn default() -> Self {
        Self::new()
    }
}