fmp_rs/endpoints/
market_hours.rs1use crate::models::market_hours::{ExchangeHours, ExtendedMarketHours, MarketHoliday};
4use crate::{FmpClient, Result};
5use chrono::Datelike;
6use serde::Serialize;
7
8pub struct MarketHours {
10 client: FmpClient,
11}
12
13impl MarketHours {
14 pub(crate) fn new(client: FmpClient) -> Self {
15 Self { client }
16 }
17
18 pub async fn get_market_hours(&self) -> Result<Vec<crate::models::market_hours::MarketHours>> {
40 #[derive(Serialize)]
41 struct Query<'a> {
42 apikey: &'a str,
43 }
44
45 let url = self.client.build_url("/is-the-market-open");
46 self.client
47 .get_with_query(
48 &url,
49 &Query {
50 apikey: self.client.api_key(),
51 },
52 )
53 .await
54 }
55
56 pub async fn get_extended_hours(&self) -> Result<Vec<ExtendedMarketHours>> {
82 #[derive(Serialize)]
83 struct Query<'a> {
84 apikey: &'a str,
85 }
86
87 let url = self.client.build_url("/market-hours");
88 self.client
89 .get_with_query(
90 &url,
91 &Query {
92 apikey: self.client.api_key(),
93 },
94 )
95 .await
96 }
97
98 pub async fn get_market_holidays(&self, year: Option<i32>) -> Result<Vec<MarketHoliday>> {
123 #[derive(Serialize)]
124 struct Query<'a> {
125 #[serde(skip_serializing_if = "Option::is_none")]
126 year: Option<i32>,
127 apikey: &'a str,
128 }
129
130 let url = self.client.build_url("/market_holidays");
131 self.client
132 .get_with_query(
133 &url,
134 &Query {
135 year,
136 apikey: self.client.api_key(),
137 },
138 )
139 .await
140 }
141
142 pub async fn get_exchange_hours(&self) -> Result<Vec<ExchangeHours>> {
167 #[derive(Serialize)]
168 struct Query<'a> {
169 apikey: &'a str,
170 }
171
172 let url = self.client.build_url("/exchange-hours");
173 self.client
174 .get_with_query(
175 &url,
176 &Query {
177 apikey: self.client.api_key(),
178 },
179 )
180 .await
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 fn create_test_client() -> FmpClient {
189 FmpClient::builder().api_key("test_key").build().unwrap()
190 }
191
192 #[test]
193 fn test_new() {
194 let client = create_test_client();
195 let market_hours = MarketHours::new(client);
196 }
198
199 #[tokio::test]
200 #[ignore] async fn test_get_market_hours() {
202 let client = FmpClient::new().unwrap();
203 let result = client.market_hours().get_market_hours().await;
204 assert!(result.is_ok());
205
206 let market_status = result.unwrap();
207 if !market_status.is_empty() {
208 let first_market = &market_status[0];
209 assert!(first_market.stock_market.is_some() || first_market.exchange.is_some());
211 println!("Market status retrieved: {} markets", market_status.len());
212 }
213 }
214
215 #[tokio::test]
216 #[ignore] async fn test_get_extended_hours() {
218 let client = FmpClient::new().unwrap();
219 let result = client.market_hours().get_extended_hours().await;
220 assert!(result.is_ok());
221
222 let extended_hours = result.unwrap();
223 if !extended_hours.is_empty() {
224 let first_exchange = &extended_hours[0];
225 assert!(first_exchange.name.is_some() || first_exchange.exchange.is_some());
227 println!("Extended hours for {} exchanges", extended_hours.len());
228 }
229 }
230
231 #[tokio::test]
232 #[ignore] async fn test_get_market_holidays_current_year() {
234 let client = FmpClient::new().unwrap();
235 let result = client.market_hours().get_market_holidays(None).await;
236 assert!(result.is_ok());
237
238 let holidays = result.unwrap();
239 if !holidays.is_empty() {
240 let first_holiday = &holidays[0];
241 assert!(first_holiday.date.is_some());
242 assert!(first_holiday.holiday.is_some());
243 println!("Found {} holidays for current year", holidays.len());
244 }
245 }
246
247 #[tokio::test]
248 #[ignore] async fn test_get_market_holidays_specific_year() {
250 let client = FmpClient::new().unwrap();
251 let result = client.market_hours().get_market_holidays(Some(2024)).await;
252 assert!(result.is_ok());
253
254 let holidays = result.unwrap();
255 if !holidays.is_empty() {
256 if let Some(first_holiday) = holidays.first() {
258 assert_eq!(first_holiday.year, Some(2024));
259 println!("Found {} holidays for 2024", holidays.len());
260 }
261 }
262 }
263
264 #[tokio::test]
265 #[ignore] async fn test_get_exchange_hours() {
267 let client = FmpClient::new().unwrap();
268 let result = client.market_hours().get_exchange_hours().await;
269 assert!(result.is_ok());
270
271 let exchange_hours = result.unwrap();
272 if !exchange_hours.is_empty() {
273 let first_exchange = &exchange_hours[0];
274 assert!(first_exchange.exchange_name.is_some() || first_exchange.exchange.is_some());
276 println!("Trading hours for {} exchanges", exchange_hours.len());
277 }
278 }
279
280 #[test]
281 fn test_market_hours_url_building() {
282 let client = create_test_client();
283 let market_hours = MarketHours::new(client);
284 }
286
287 #[test]
288 fn test_holiday_year_validation() {
289 let current_year = chrono::Utc::now().year();
291 let future_year = current_year + 1;
292 let past_year = current_year - 1;
293
294 assert!(past_year > 1900);
296 assert!(future_year < 2100);
297 }
298
299 #[test]
300 fn test_market_hours_models_serialization() {
301 use serde_json;
302
303 let market_hours = crate::models::market_hours::MarketHours {
305 exchange: Some("NYSE".to_string()),
306 stock_exchange_name: Some("New York Stock Exchange".to_string()),
307 stock_market: Some("NYSE".to_string()),
308 is_market_open: Some(true),
309 opening_hour: Some("09:30".to_string()),
310 closing_hour: Some("16:00".to_string()),
311 current_date_time: Some("2024-01-15 14:30:00".to_string()),
312 timezone: Some("EST".to_string()),
313 };
314
315 let json = serde_json::to_string(&market_hours).unwrap();
316 let deserialized: crate::models::market_hours::MarketHours =
317 serde_json::from_str(&json).unwrap();
318 assert_eq!(deserialized.exchange, Some("NYSE".to_string()));
319 assert_eq!(deserialized.is_market_open, Some(true));
320
321 let holiday = MarketHoliday {
323 date: Some("2024-12-25".to_string()),
324 year: Some(2024),
325 holiday: Some("Christmas Day".to_string()),
326 exchange: Some("NYSE".to_string()),
327 market_status: Some("closed".to_string()),
328 early_close_time: None,
329 };
330
331 let json = serde_json::to_string(&holiday).unwrap();
332 let deserialized: MarketHoliday = serde_json::from_str(&json).unwrap();
333 assert_eq!(deserialized.holiday, Some("Christmas Day".to_string()));
334 assert_eq!(deserialized.year, Some(2024));
335 }
336
337 #[test]
338 fn test_exchange_hours_model_completeness() {
339 let exchange = ExchangeHours {
340 exchange: Some("NASDAQ".to_string()),
341 exchange_name: Some("NASDAQ Stock Market".to_string()),
342 country: Some("United States".to_string()),
343 timezone: Some("EST".to_string()),
344 market_open: Some("09:30".to_string()),
345 market_close: Some("16:00".to_string()),
346 pre_market_start: Some("04:00".to_string()),
347 pre_market_end: Some("09:30".to_string()),
348 after_hours_start: Some("16:00".to_string()),
349 after_hours_end: Some("20:00".to_string()),
350 is_open: Some(false),
351 current_time: Some("2024-01-15 18:30:00".to_string()),
352 day_of_week: Some("Monday".to_string()),
353 };
354
355 assert_eq!(exchange.exchange, Some("NASDAQ".to_string()));
357 assert_eq!(exchange.country, Some("United States".to_string()));
358 assert_eq!(exchange.is_open, Some(false));
359 assert_eq!(exchange.day_of_week, Some("Monday".to_string()));
360 }
361
362 #[tokio::test]
364 #[ignore = "requires FMP API key"]
365 async fn test_get_market_holidays_future_year() {
366 let client = FmpClient::new().unwrap();
367 let future_year = chrono::Utc::now().year() + 1;
368 let result = client
369 .market_hours()
370 .get_market_holidays(Some(future_year))
371 .await;
372 assert!(result.is_ok());
373 let holidays = result.unwrap();
374 if !holidays.is_empty() {
376 assert!(holidays[0].year.is_some());
377 }
378 }
379
380 #[tokio::test]
381 #[ignore = "requires FMP API key"]
382 async fn test_get_market_holidays_past_year() {
383 let client = FmpClient::new().unwrap();
384 let past_year = chrono::Utc::now().year() - 1;
385 let result = client
386 .market_hours()
387 .get_market_holidays(Some(past_year))
388 .await;
389 assert!(result.is_ok());
390 let holidays = result.unwrap();
391 assert!(!holidays.is_empty());
393 }
394
395 #[tokio::test]
396 #[ignore = "requires FMP API key"]
397 async fn test_market_hours_consistency() {
398 let client = FmpClient::new().unwrap();
399 let market_hours_result = client.market_hours().get_market_hours().await;
400 let extended_hours_result = client.market_hours().get_extended_hours().await;
401
402 assert!(market_hours_result.is_ok());
403 assert!(extended_hours_result.is_ok());
404
405 let market_hours = market_hours_result.unwrap();
407 let extended_hours = extended_hours_result.unwrap();
408
409 if !market_hours.is_empty() && !extended_hours.is_empty() {
410 assert!(market_hours[0].exchange.is_some());
412 assert!(extended_hours[0].exchange.is_some());
413 }
414 }
415}