finance_query/models/hours/
response.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Deserialize)]
9struct RawHoursResponse {
10 finance: RawFinance,
11}
12
13#[derive(Debug, Clone, Deserialize)]
14#[serde(rename_all = "camelCase")]
15struct RawFinance {
16 #[serde(default)]
17 market_times: Vec<RawMarketTimes>,
18}
19
20#[derive(Debug, Clone, Deserialize)]
21#[serde(rename_all = "camelCase")]
22struct RawMarketTimes {
23 #[serde(default)]
24 market_time: Vec<RawMarketTime>,
25}
26
27#[derive(Debug, Clone, Deserialize)]
28#[serde(rename_all = "camelCase")]
29struct RawMarketTime {
30 id: String,
31 name: String,
32 status: String,
33 #[serde(default)]
34 message: Option<String>,
35 #[serde(default)]
36 open: Option<String>,
37 #[serde(default)]
38 close: Option<String>,
39 #[serde(default)]
40 time: Option<String>,
41 #[serde(default)]
42 timezone: Vec<RawTimezone>,
43}
44
45#[derive(Debug, Clone, Deserialize)]
46struct RawTimezone {
47 #[serde(default)]
48 dst: Option<String>,
49 #[serde(default)]
50 gmtoffset: Option<String>,
51 #[serde(default)]
52 short: Option<String>,
53 #[serde(rename = "$text", default)]
54 text: Option<String>,
55}
56
57#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
63#[cfg_attr(feature = "dataframe", derive(crate::ToDataFrame))]
64#[serde(rename_all = "camelCase")]
65#[non_exhaustive]
66pub struct MarketTime {
67 pub id: String,
69
70 pub name: String,
72
73 pub status: String,
75
76 #[serde(skip_serializing_if = "Option::is_none")]
78 pub message: Option<String>,
79
80 #[serde(skip_serializing_if = "Option::is_none")]
82 pub open: Option<String>,
83
84 #[serde(skip_serializing_if = "Option::is_none")]
86 pub close: Option<String>,
87
88 #[serde(skip_serializing_if = "Option::is_none")]
90 pub time: Option<String>,
91
92 #[serde(skip_serializing_if = "Option::is_none")]
94 pub timezone: Option<String>,
95
96 #[serde(skip_serializing_if = "Option::is_none")]
98 pub timezone_short: Option<String>,
99
100 #[serde(skip_serializing_if = "Option::is_none")]
102 pub gmt_offset: Option<i32>,
103
104 #[serde(skip_serializing_if = "Option::is_none")]
106 pub dst: Option<bool>,
107}
108
109#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
111#[serde(rename_all = "camelCase")]
112#[non_exhaustive]
113pub struct MarketHours {
114 pub markets: Vec<MarketTime>,
116}
117
118impl MarketHours {
119 pub(crate) fn from_response(raw: &serde_json::Value) -> Result<Self, String> {
124 let raw_response: RawHoursResponse = serde_json::from_value(raw.clone())
125 .map_err(|e| format!("Failed to parse hours response: {}", e))?;
126
127 let mut markets = Vec::new();
128
129 for market_times in &raw_response.finance.market_times {
130 for market_time in &market_times.market_time {
131 let tz = market_time.timezone.first();
133
134 let gmt_offset = tz
135 .and_then(|t| t.gmtoffset.as_ref())
136 .and_then(|s| s.parse::<i32>().ok());
137
138 let dst = tz
139 .and_then(|t| t.dst.as_ref())
140 .map(|s| s.eq_ignore_ascii_case("true"));
141
142 markets.push(MarketTime {
143 id: market_time.id.clone(),
144 name: market_time.name.clone(),
145 status: market_time.status.clone(),
146 message: market_time.message.clone(),
147 open: market_time.open.clone(),
148 close: market_time.close.clone(),
149 time: market_time.time.clone(),
150 timezone: tz.and_then(|t| t.text.clone()),
151 timezone_short: tz.and_then(|t| t.short.clone()),
152 gmt_offset,
153 dst,
154 });
155 }
156 }
157
158 Ok(Self { markets })
159 }
160}
161
162#[cfg(feature = "dataframe")]
163impl MarketHours {
164 pub fn to_dataframe(&self) -> ::polars::prelude::PolarsResult<::polars::prelude::DataFrame> {
166 MarketTime::vec_to_dataframe(&self.markets)
167 }
168}