use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum CalendarKind {
Earnings,
Ipo,
Dividend,
Split,
Economic,
MarketHoliday,
MarketStatus,
}
impl CalendarKind {
pub(crate) fn operation(self) -> crate::providers::Operation {
use crate::providers::Operation;
match self {
Self::Earnings => Operation::EarningsCalendar,
Self::Ipo => Operation::IpoCalendar,
Self::Dividend => Operation::DividendCalendar,
Self::Split => Operation::SplitCalendar,
Self::Economic => Operation::EconomicCalendar,
Self::MarketHoliday => Operation::HolidayCalendar,
Self::MarketStatus => Operation::MarketStatus,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct MarketCalendarEntry {
pub symbol: Option<String>,
pub date: Option<String>,
pub detail: CalendarDetail,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum CalendarDetail {
#[non_exhaustive]
Earnings {
eps: Option<f64>,
eps_estimated: Option<f64>,
revenue: Option<f64>,
revenue_estimated: Option<f64>,
fiscal_date_ending: Option<String>,
time: Option<String>,
company: Option<String>,
estimate_count: Option<u32>,
surprise_percent: Option<f64>,
previous_eps: Option<f64>,
previous_report_date: Option<String>,
market_cap: Option<f64>,
},
Ipo {
company: Option<String>,
exchange: Option<String>,
actions: Option<String>,
shares: Option<f64>,
price_range: Option<String>,
market_cap: Option<f64>,
},
Dividend {
dividend: Option<f64>,
adj_dividend: Option<f64>,
record_date: Option<String>,
payment_date: Option<String>,
declaration_date: Option<String>,
},
Split {
numerator: Option<f64>,
denominator: Option<f64>,
},
MarketHoliday {
name: Option<String>,
exchange: Option<String>,
status: Option<String>,
open: Option<String>,
close: Option<String>,
},
Economic {
event: Option<String>,
country: Option<String>,
actual: Option<f64>,
previous: Option<f64>,
estimate: Option<f64>,
change: Option<f64>,
change_percentage: Option<f64>,
impact: Option<String>,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn earnings_json_from_before_the_added_fields_still_deserializes() {
let entry: MarketCalendarEntry = serde_json::from_value(serde_json::json!({
"symbol": "AAPL",
"date": "2026-07-30",
"detail": {
"type": "earnings",
"eps": 1.62,
"eps_estimated": 1.5,
"revenue": null,
"revenue_estimated": null,
"fiscal_date_ending": "2026-06-30",
"time": "amc"
}
}))
.unwrap();
let CalendarDetail::Earnings {
eps,
time,
company,
market_cap,
..
} = entry.detail
else {
panic!("expected Earnings detail");
};
assert_eq!((eps, time.as_deref()), (Some(1.62), Some("amc")));
assert_eq!((company, market_cap), (None, None));
}
}