use crate::constants::TimeRange;
use crate::models::chart::{CapitalGain, Dividend, Split};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
#[inline]
pub(crate) fn now_unix_secs() -> i64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as i64
}
pub(crate) const EVICTION_THRESHOLD: usize = 64;
pub(crate) struct CacheEntry<T> {
pub(crate) value: T,
fetched_at: Instant,
}
impl<T> CacheEntry<T> {
#[inline]
pub(crate) fn new(value: T) -> Self {
Self {
value,
fetched_at: Instant::now(),
}
}
#[inline]
pub(crate) fn is_fresh(&self, ttl: Duration) -> bool {
self.fetched_at.elapsed() < ttl
}
#[inline]
pub(crate) fn is_fresh_with_ttl(entry: Option<&CacheEntry<T>>, ttl: Option<Duration>) -> bool {
match (ttl, entry) {
(Some(ttl), Some(e)) => e.is_fresh(ttl),
_ => false,
}
}
}
pub(crate) trait HasTimestamp {
fn timestamp(&self) -> i64;
}
impl HasTimestamp for Dividend {
fn timestamp(&self) -> i64 {
self.timestamp
}
}
impl HasTimestamp for Split {
fn timestamp(&self) -> i64 {
self.timestamp
}
}
impl HasTimestamp for CapitalGain {
fn timestamp(&self) -> i64 {
self.timestamp
}
}
pub(crate) fn range_to_cutoff(range: TimeRange) -> i64 {
let now = now_unix_secs();
const DAY: i64 = 86400;
match range {
TimeRange::OneDay => now - DAY,
TimeRange::FiveDays => now - 5 * DAY,
TimeRange::OneMonth => now - 30 * DAY,
TimeRange::ThreeMonths => now - 90 * DAY,
TimeRange::SixMonths => now - 180 * DAY,
TimeRange::OneYear => now - 365 * DAY,
TimeRange::TwoYears => now - 2 * 365 * DAY,
TimeRange::FiveYears => now - 5 * 365 * DAY,
TimeRange::TenYears => now - 10 * 365 * DAY,
TimeRange::YearToDate => {
let epoch_days = now / DAY;
let mut year = 1970i32;
let mut remaining = epoch_days;
loop {
let days_in_year = if is_leap_year(year) { 366 } else { 365 };
if remaining < days_in_year {
break;
}
remaining -= days_in_year;
year += 1;
}
(epoch_days - remaining) * DAY
}
TimeRange::Max => 0, }
}
const fn is_leap_year(year: i32) -> bool {
(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
}
pub(crate) fn filter_by_range<T: HasTimestamp>(items: Vec<T>, range: TimeRange) -> Vec<T> {
match range {
TimeRange::Max => items,
range => {
let cutoff = range_to_cutoff(range);
items
.into_iter()
.filter(|item| item.timestamp() >= cutoff)
.collect()
}
}
}