alpaca-time 0.24.9

New York time and US trading-calendar semantics for the alpaca-rust workspace
Documentation
use crate::calendar::{is_trading_date, market_hours_for_date};
use crate::clock::{now, parse_naive_date, parse_naive_timestamp, parse_timestamp};
use crate::error::{TimeError, TimeResult};
use crate::types::DayCountBasis;

pub fn close(expiration_date: &str) -> TimeResult<String> {
    if !is_trading_date(expiration_date) {
        return Err(TimeError::new(
            "invalid_expiration_date",
            format!("expiration date must be a trading date: {expiration_date}"),
        ));
    }
    let hours = market_hours_for_date(expiration_date)?;
    Ok(format!(
        "{} {}:00",
        expiration_date,
        hours
            .regular_close
            .ok_or_else(|| TimeError::new("invalid_market_hours", "missing regular close"))?
    ))
}

pub fn calendar_days(expiration_date: &str, at: Option<&str>) -> TimeResult<i64> {
    let timestamp = parse_naive_timestamp(&parse_timestamp(at.unwrap_or(&now()))?)?;
    let start_date = timestamp.date();
    let expiration_date = parse_naive_date(expiration_date)?;
    let day_diff = (expiration_date - start_date).num_days();
    if day_diff != 0 {
        return Ok(day_diff);
    }
    let expiration =
        parse_naive_timestamp(&close(&expiration_date.format("%Y-%m-%d").to_string())?)?;
    if timestamp <= expiration {
        Ok(0)
    } else {
        Ok(-1)
    }
}

pub fn days(expiration_date: &str, at: Option<&str>) -> TimeResult<f64> {
    let start = parse_naive_timestamp(&parse_timestamp(at.unwrap_or(&now()))?)?;
    let end = parse_naive_timestamp(&close(expiration_date)?)?;
    let seconds = end.signed_duration_since(start).num_seconds();
    Ok(seconds as f64 / 86_400.0)
}

pub fn years(expiration_date: &str, at: Option<&str>, basis: Option<&str>) -> f64 {
    let basis = DayCountBasis::from_option_str(basis);
    days(expiration_date, at)
        .map(|value| (value / basis.denominator()).max(0.0))
        .unwrap_or(0.0)
}

pub fn years_between_dates(
    start_date: &str,
    end_date: &str,
    basis: Option<&str>,
) -> TimeResult<f64> {
    let start_date = parse_naive_date(start_date)?;
    let end_date = parse_naive_date(end_date)?;
    let basis = match basis {
        Some(value) => DayCountBasis::from_option_str(Some(value)),
        None => DayCountBasis::Act365,
    };
    Ok((end_date - start_date).num_days() as f64 / basis.denominator())
}