Skip to main content

finance_query_core/models/
calendar.rs

1use chrono::{DateTime, TimeZone, Utc};
2use serde::{Deserialize, Serialize};
3
4/// Calendar events for a stock (earnings, dividends, etc.)
5#[derive(Debug, Clone, Serialize, Deserialize)]
6#[serde(rename_all = "camelCase")]
7pub struct Calendar {
8    pub symbol: String,
9    /// Earnings date (single date or start of range)
10    pub earnings_date: Option<DateTime<Utc>>,
11    /// Earnings date range start
12    pub earnings_date_start: Option<DateTime<Utc>>,
13    /// Earnings date range end
14    pub earnings_date_end: Option<DateTime<Utc>>,
15    /// Dividend date
16    pub dividend_date: Option<DateTime<Utc>>,
17    /// Ex-dividend date
18    pub ex_dividend_date: Option<DateTime<Utc>>,
19    /// Dividend rate
20    pub dividend_rate: Option<f64>,
21    /// Dividend yield
22    pub dividend_yield: Option<f64>,
23}
24
25impl Calendar {
26    pub(crate) fn from_yahoo_response(
27        symbol: String,
28        response: YahooCalendarResponse,
29    ) -> Result<Self, crate::client::YahooError> {
30        let result = response.quote_summary.result.first().ok_or_else(|| {
31            crate::client::YahooError::ParseError("No calendar data in response".to_string())
32        })?;
33
34        let events = &result.calendar_events;
35
36        // Parse earnings dates (can be raw timestamps or objects)
37        let earnings_dates: Vec<DateTime<Utc>> = events
38            .earnings
39            .as_ref()
40            .and_then(|e| e.earnings_date.as_ref())
41            .map(|dates| {
42                dates
43                    .iter()
44                    .filter_map(|ts| Utc.timestamp_opt(*ts, 0).single())
45                    .collect()
46            })
47            .unwrap_or_default();
48
49        let earnings_date = earnings_dates.first().copied();
50        let earnings_date_start = earnings_dates.first().copied();
51        let earnings_date_end = earnings_dates.get(1).copied().or(earnings_date_start);
52
53        // Parse dividend date (raw timestamp)
54        let dividend_date = events
55            .dividend_date
56            .and_then(|ts| Utc.timestamp_opt(ts, 0).single());
57
58        // Parse ex-dividend date (raw timestamp)
59        let ex_dividend_date = events
60            .ex_dividend_date
61            .and_then(|ts| Utc.timestamp_opt(ts, 0).single());
62
63        Ok(Self {
64            symbol,
65            earnings_date,
66            earnings_date_start,
67            earnings_date_end,
68            dividend_date,
69            ex_dividend_date,
70            dividend_rate: None,
71            dividend_yield: None,
72        })
73    }
74}
75
76// Internal Yahoo response structures
77#[derive(Debug, Deserialize)]
78pub(crate) struct YahooCalendarResponse {
79    #[serde(rename = "quoteSummary")]
80    pub quote_summary: QuoteSummaryData,
81}
82
83#[derive(Debug, Deserialize)]
84pub(crate) struct QuoteSummaryData {
85    pub result: Vec<QuoteSummaryResult>,
86}
87
88#[derive(Debug, Deserialize)]
89pub(crate) struct QuoteSummaryResult {
90    #[serde(rename = "calendarEvents")]
91    pub calendar_events: CalendarEventsData,
92}
93
94#[derive(Debug, Deserialize)]
95pub(crate) struct CalendarEventsData {
96    pub earnings: Option<EarningsData>,
97    #[serde(rename = "dividendDate")]
98    pub dividend_date: Option<i64>,
99    #[serde(rename = "exDividendDate")]
100    pub ex_dividend_date: Option<i64>,
101}
102
103#[derive(Debug, Deserialize)]
104pub(crate) struct EarningsData {
105    #[serde(rename = "earningsDate")]
106    pub earnings_date: Option<Vec<i64>>,
107}