forex-factory 0.1.0

Async Rust library for scraping economic event data from Forex Factory calendar
Documentation
use chrono::{Local, NaiveDate};
use tracing::info;

use crate::error::Result;
use crate::scraper::{CalendarParser, HttpCalendarFetcher};
use crate::types::{EconomicEvent, EventQuery, Impact};

/// High-level service for fetching and querying economic events.
pub struct CalendarService {
    fetcher: HttpCalendarFetcher,
    parser: CalendarParser,
}

impl CalendarService {
    /// Create a new calendar service.
    ///
    /// On native targets, automatically detects the system timezone.
    /// On WASM targets, falls back to UTC unless you use `with_timezone()`.
    pub fn new() -> Result<Self> {
        let fetcher = HttpCalendarFetcher::new()?;
        let parser = CalendarParser::new()?;

        Ok(Self { fetcher, parser })
    }

    /// Create a new calendar service with a specific IANA timezone.
    ///
    /// Use this on WASM to set the timezone (get it from JavaScript via
    /// `Intl.DateTimeFormat().resolvedOptions().timeZone`).
    ///
    /// # Example
    /// ```ignore
    /// let service = CalendarService::with_timezone("America/New_York")?;
    /// ```
    pub fn with_timezone(timezone: &str) -> Result<Self> {
        let fetcher = HttpCalendarFetcher::with_timezone(timezone)?;
        let parser = CalendarParser::new()?;

        Ok(Self { fetcher, parser })
    }

    /// Query events matching the given criteria.
    pub async fn query_events(&self, query: &EventQuery) -> Result<Vec<EconomicEvent>> {
        // Determine which date to fetch
        let base_date = query.from_date.unwrap_or_else(|| Local::now().date_naive());

        info!("Fetching calendar for date: {base_date}");

        // Fetch HTML from Forex Factory
        let html = self.fetcher.fetch_date(base_date).await?;

        // Parse events
        let events = self.parser.parse(&html, base_date);

        // Filter events based on query
        let min_impact = query.min_impact.unwrap_or(Impact::Low);
        let filtered: Vec<EconomicEvent> = events
            .into_iter()
            .filter(|e| e.meets_impact(min_impact))
            .filter(|e| e.matches_currencies(&query.currencies))
            .filter(|e| query.datetime_in_range(&e.datetime))
            .collect();

        info!("Found {} events matching query", filtered.len());
        Ok(filtered)
    }

    /// Get events for today.
    pub async fn get_today_events(&self) -> Result<Vec<EconomicEvent>> {
        let today = Local::now().date_naive();
        let html = self.fetcher.fetch_today().await?;
        Ok(self.parser.parse(&html, today))
    }

    /// Get events for this week.
    pub async fn get_week_events(&self) -> Result<Vec<EconomicEvent>> {
        let today = Local::now().date_naive();
        let html = self.fetcher.fetch_this_week().await?;
        Ok(self.parser.parse(&html, today))
    }

    /// Get events for a specific week containing the given date.
    pub async fn get_week_events_for(&self, date: NaiveDate) -> Result<Vec<EconomicEvent>> {
        let html = self.fetcher.fetch_date(date).await?;
        Ok(self.parser.parse(&html, date))
    }
}