Skip to main content

egui_charts/model/
date_range.rs

1//! Preset date-range selectors for the chart toolbar.
2//!
3//! Mirrors the "1D / 5D / 1M / 3M / 6M / YTD / 1Y / 5Y / All" buttons
4//! found on most financial charting platforms.
5
6/// Preset date range for quick navigation.
7///
8/// Each variant describes a lookback window from the current time.
9/// The default preset is [`Month1`](DateRange::Month1).
10///
11/// Use [`start_timestamp`](DateRange::start_timestamp) to convert a preset into
12/// an absolute `DateTime`, or [`estimated_bars`](DateRange::estimated_bars) to
13/// estimate how many bars to fetch for a given timeframe.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
15pub enum DateRange {
16    /// 1 Day
17    Day1,
18    /// 5 Days
19    Day5,
20    /// 1 Month
21    #[default]
22    Month1,
23    /// 3 Months
24    Month3,
25    /// 6 Months
26    Month6,
27    /// Year to Date (from Jan 1 of current year)
28    YTD,
29    /// 1 Year
30    Year1,
31    /// 5 Years
32    Year5,
33    /// All available data
34    All,
35}
36
37impl DateRange {
38    /// Get display label for the range
39    pub fn label(&self) -> &'static str {
40        match self {
41            DateRange::Day1 => "1D",
42            DateRange::Day5 => "5D",
43            DateRange::Month1 => "1M",
44            DateRange::Month3 => "3M",
45            DateRange::Month6 => "6M",
46            DateRange::YTD => "YTD",
47            DateRange::Year1 => "1Y",
48            DateRange::Year5 => "5Y",
49            DateRange::All => "All",
50        }
51    }
52
53    /// Get tooltip desc for the range
54    pub fn tooltip(&self) -> &'static str {
55        match self {
56            DateRange::Day1 => "1 day in 1-minute intervals",
57            DateRange::Day5 => "5 days in 5-minute intervals",
58            DateRange::Month1 => "1 month in 30-minute intervals",
59            DateRange::Month3 => "3 months in 1-hour intervals",
60            DateRange::Month6 => "6 months in 2-hour intervals",
61            DateRange::YTD => "Year to date in 1-day intervals",
62            DateRange::Year1 => "1 year in 1-day intervals",
63            DateRange::Year5 => "5 years in 1-week intervals",
64            DateRange::All => "All data in 1-month intervals",
65        }
66    }
67
68    /// Get all standard ranges in order
69    pub fn default_presets() -> Vec<DateRange> {
70        vec![
71            DateRange::Day1,
72            DateRange::Day5,
73            DateRange::Month1,
74            DateRange::Month3,
75            DateRange::Month6,
76            DateRange::YTD,
77            DateRange::Year1,
78            DateRange::Year5,
79            DateRange::All,
80        ]
81    }
82
83    /// Calculate the start ts for this range
84    /// Returns None for DateRange::All (meaning "from earliest available")
85    pub fn start_timestamp(&self) -> Option<chrono::DateTime<chrono::Utc>> {
86        use chrono::{Datelike, Duration, NaiveDate, Utc};
87        let now = Utc::now();
88
89        match self {
90            DateRange::Day1 => Some(now - Duration::days(1)),
91            DateRange::Day5 => Some(now - Duration::days(5)),
92            DateRange::Month1 => Some(now - Duration::days(30)),
93            DateRange::Month3 => Some(now - Duration::days(90)),
94            DateRange::Month6 => Some(now - Duration::days(180)),
95            DateRange::YTD => {
96                let year_start = NaiveDate::from_ymd_opt(now.year(), 1, 1)?;
97                Some(chrono::DateTime::from_naive_utc_and_offset(
98                    year_start.and_hms_opt(0, 0, 0)?,
99                    Utc,
100                ))
101            }
102            DateRange::Year1 => Some(now - Duration::days(365)),
103            DateRange::Year5 => Some(now - Duration::days(365 * 5)),
104            DateRange::All => None,
105        }
106    }
107
108    /// Get the date range as (start, end) ts
109    /// Returns (None, now) for All (meaning earliest to now)
110    pub fn to_timestamps(
111        &self,
112    ) -> (
113        Option<chrono::DateTime<chrono::Utc>>,
114        chrono::DateTime<chrono::Utc>,
115    ) {
116        (self.start_timestamp(), chrono::Utc::now())
117    }
118
119    /// Calculate approximate number of bars needed for this range at a given timeframe
120    pub fn estimated_bars(&self, bar_duration_seconds: u64) -> usize {
121        let duration_seconds = match self {
122            DateRange::Day1 => 24 * 3600,
123            DateRange::Day5 => 5 * 24 * 3600,
124            DateRange::Month1 => 30 * 24 * 3600,
125            DateRange::Month3 => 90 * 24 * 3600,
126            DateRange::Month6 => 180 * 24 * 3600,
127            DateRange::YTD => {
128                let now = chrono::Utc::now();
129                let days_since_year_start = chrono::Datelike::ordinal(&now) as u64;
130                days_since_year_start * 24 * 3600
131            }
132            DateRange::Year1 => 365 * 24 * 3600,
133            DateRange::Year5 => 5 * 365 * 24 * 3600,
134            DateRange::All => 10 * 365 * 24 * 3600,
135        };
136
137        (duration_seconds / bar_duration_seconds.max(1)) as usize
138    }
139}
140
141impl std::fmt::Display for DateRange {
142    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143        write!(f, "{}", self.label())
144    }
145}