tvdata-rs 0.1.2

Async Rust client for TradingView screener queries, search, calendars, quote snapshots, and OHLCV history.
Documentation
use std::convert::Infallible;
use std::fmt;
use std::str::FromStr;

use serde::{Deserialize, Serialize};
use time::OffsetDateTime;

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum FiscalPeriod {
    FiscalYear { year: i32 },
    FiscalHalfYear { year: i32, half: u8 },
    FiscalQuarter { year: i32, quarter: u8 },
    Raw(String),
}

impl FiscalPeriod {
    pub fn parse(value: impl AsRef<str>) -> Self {
        let value = value.as_ref().trim();
        if let Some((year, quarter)) = value.split_once("-Q")
            && let (Ok(year), Ok(quarter)) = (year.parse::<i32>(), quarter.parse::<u8>())
        {
            return Self::FiscalQuarter { year, quarter };
        }

        if let Some((year, half)) = value.split_once("-H")
            && let (Ok(year), Ok(half)) = (year.parse::<i32>(), half.parse::<u8>())
        {
            return Self::FiscalHalfYear { year, half };
        }

        if let Ok(year) = value.parse::<i32>() {
            return Self::FiscalYear { year };
        }

        Self::Raw(value.to_owned())
    }
}

impl FromStr for FiscalPeriod {
    type Err = Infallible;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(Self::parse(s))
    }
}

impl fmt::Display for FiscalPeriod {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::FiscalYear { year } => write!(f, "{year}"),
            Self::FiscalHalfYear { year, half } => write!(f, "{year}-H{half}"),
            Self::FiscalQuarter { year, quarter } => write!(f, "{year}-Q{quarter}"),
            Self::Raw(value) => f.write_str(value),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct HistoricalObservation<T> {
    pub fiscal_period: Option<FiscalPeriod>,
    pub release_at: Option<OffsetDateTime>,
    pub value: T,
}

impl<T> HistoricalObservation<T> {
    pub fn new(
        fiscal_period: Option<FiscalPeriod>,
        release_at: Option<OffsetDateTime>,
        value: T,
    ) -> Self {
        Self {
            fiscal_period,
            release_at,
            value,
        }
    }
}

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

    #[test]
    fn parses_fiscal_period_variants() {
        assert_eq!(
            FiscalPeriod::parse("2025"),
            FiscalPeriod::FiscalYear { year: 2025 }
        );
        assert_eq!(
            FiscalPeriod::parse("2025-H1"),
            FiscalPeriod::FiscalHalfYear {
                year: 2025,
                half: 1
            }
        );
        assert_eq!(
            FiscalPeriod::parse("2025-Q4"),
            FiscalPeriod::FiscalQuarter {
                year: 2025,
                quarter: 4
            }
        );
        assert_eq!(
            FiscalPeriod::parse("custom"),
            FiscalPeriod::Raw("custom".to_owned())
        );
    }
}