forex_factory/service/
calendar.rs

1use chrono::{Local, NaiveDate};
2use tracing::info;
3
4use crate::error::Result;
5use crate::scraper::{CalendarParser, HttpCalendarFetcher};
6use crate::types::{EconomicEvent, EventQuery, Impact};
7
8/// High-level service for fetching and querying economic events.
9pub struct CalendarService {
10    fetcher: HttpCalendarFetcher,
11    parser: CalendarParser,
12}
13
14impl CalendarService {
15    /// Create a new calendar service.
16    ///
17    /// On native targets, automatically detects the system timezone.
18    /// On WASM targets, falls back to UTC unless you use `with_timezone()`.
19    pub fn new() -> Result<Self> {
20        let fetcher = HttpCalendarFetcher::new()?;
21        let parser = CalendarParser::new()?;
22
23        Ok(Self { fetcher, parser })
24    }
25
26    /// Create a new calendar service with a specific IANA timezone.
27    ///
28    /// Use this on WASM to set the timezone (get it from JavaScript via
29    /// `Intl.DateTimeFormat().resolvedOptions().timeZone`).
30    ///
31    /// # Example
32    /// ```ignore
33    /// let service = CalendarService::with_timezone("America/New_York")?;
34    /// ```
35    pub fn with_timezone(timezone: &str) -> Result<Self> {
36        let fetcher = HttpCalendarFetcher::with_timezone(timezone)?;
37        let parser = CalendarParser::new()?;
38
39        Ok(Self { fetcher, parser })
40    }
41
42    /// Query events matching the given criteria.
43    pub async fn query_events(&self, query: &EventQuery) -> Result<Vec<EconomicEvent>> {
44        // Determine which date to fetch
45        let base_date = query.from_date.unwrap_or_else(|| Local::now().date_naive());
46
47        info!("Fetching calendar for date: {base_date}");
48
49        // Fetch HTML from Forex Factory
50        let html = self.fetcher.fetch_date(base_date).await?;
51
52        // Parse events
53        let events = self.parser.parse(&html, base_date);
54
55        // Filter events based on query
56        let min_impact = query.min_impact.unwrap_or(Impact::Low);
57        let filtered: Vec<EconomicEvent> = events
58            .into_iter()
59            .filter(|e| e.meets_impact(min_impact))
60            .filter(|e| e.matches_currencies(&query.currencies))
61            .filter(|e| query.datetime_in_range(&e.datetime))
62            .collect();
63
64        info!("Found {} events matching query", filtered.len());
65        Ok(filtered)
66    }
67
68    /// Get events for today.
69    pub async fn get_today_events(&self) -> Result<Vec<EconomicEvent>> {
70        let today = Local::now().date_naive();
71        let html = self.fetcher.fetch_today().await?;
72        Ok(self.parser.parse(&html, today))
73    }
74
75    /// Get events for this week.
76    pub async fn get_week_events(&self) -> Result<Vec<EconomicEvent>> {
77        let today = Local::now().date_naive();
78        let html = self.fetcher.fetch_this_week().await?;
79        Ok(self.parser.parse(&html, today))
80    }
81
82    /// Get events for a specific week containing the given date.
83    pub async fn get_week_events_for(&self, date: NaiveDate) -> Result<Vec<EconomicEvent>> {
84        let html = self.fetcher.fetch_date(date).await?;
85        Ok(self.parser.parse(&html, date))
86    }
87}