use crate::storage::sqlite::FrankenStorage;
use super::types::AnalyticsFilter;
pub fn day_id_to_iso(day_id: i64) -> String {
use chrono::{TimeZone, Utc};
let ms = FrankenStorage::millis_from_day_id(day_id);
Utc.timestamp_millis_opt(ms)
.single()
.map(|dt| dt.format("%Y-%m-%d").to_string())
.unwrap_or_else(|| format!("day:{day_id}"))
}
pub fn hour_id_to_iso(hour_id: i64) -> String {
use chrono::{TimeZone, Utc};
let ms = FrankenStorage::millis_from_hour_id(hour_id);
Utc.timestamp_millis_opt(ms)
.single()
.map(|dt| dt.format("%Y-%m-%dT%H:00Z").to_string())
.unwrap_or_else(|| format!("hour:{hour_id}"))
}
pub fn day_id_to_iso_week(day_id: i64) -> String {
use chrono::{Datelike, TimeZone, Utc};
let ms = FrankenStorage::millis_from_day_id(day_id);
Utc.timestamp_millis_opt(ms)
.single()
.map(|dt| {
let iso = dt.iso_week();
format!("{}-W{:02}", iso.year(), iso.week())
})
.unwrap_or_else(|| format!("day:{day_id}"))
}
pub fn day_id_to_month(day_id: i64) -> String {
use chrono::{TimeZone, Utc};
let ms = FrankenStorage::millis_from_day_id(day_id);
Utc.timestamp_millis_opt(ms)
.single()
.map(|dt| dt.format("%Y-%m").to_string())
.unwrap_or_else(|| format!("day:{day_id}"))
}
pub fn resolve_day_range(filter: &AnalyticsFilter) -> (Option<i64>, Option<i64>) {
let min_day = filter.since_ms.map(FrankenStorage::day_id_from_millis);
let max_day = filter.until_ms.map(FrankenStorage::day_id_from_millis);
(min_day, max_day)
}
pub fn resolve_hour_range(filter: &AnalyticsFilter) -> (Option<i64>, Option<i64>) {
let min_hour = filter.since_ms.map(FrankenStorage::hour_id_from_millis);
let max_hour = filter.until_ms.map(FrankenStorage::hour_id_from_millis);
(min_hour, max_hour)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn day_id_roundtrip() {
let ms = 1_749_945_600_000_i64;
let day_id = FrankenStorage::day_id_from_millis(ms);
let iso = day_id_to_iso(day_id);
assert_eq!(iso, "2025-06-15");
}
#[test]
fn hour_id_roundtrip() {
let ms = 1_749_996_000_000_i64;
let hour_id = FrankenStorage::hour_id_from_millis(ms);
let iso = hour_id_to_iso(hour_id);
assert_eq!(iso, "2025-06-15T14:00Z");
}
#[test]
fn day_id_to_week() {
let mon_ms = 1_736_121_600_000_i64; let sun_ms = 1_736_640_000_000_i64; let mon_id = FrankenStorage::day_id_from_millis(mon_ms);
let sun_id = FrankenStorage::day_id_from_millis(sun_ms);
assert_eq!(day_id_to_iso_week(mon_id), day_id_to_iso_week(sun_id));
assert!(day_id_to_iso_week(mon_id).contains("W02"));
}
#[test]
fn day_id_to_month_format() {
let ms = 1_750_032_000_000_i64; let day_id = FrankenStorage::day_id_from_millis(ms);
assert_eq!(day_id_to_month(day_id), "2025-06");
}
#[test]
fn resolve_empty_filter_gives_none() {
let f = AnalyticsFilter::default();
let (min, max) = resolve_day_range(&f);
assert!(min.is_none());
assert!(max.is_none());
let (hmin, hmax) = resolve_hour_range(&f);
assert!(hmin.is_none());
assert!(hmax.is_none());
}
#[test]
fn resolve_day_range_with_since() {
let f = AnalyticsFilter {
since_ms: Some(1_750_032_000_000),
..Default::default()
};
let (min, max) = resolve_day_range(&f);
assert!(min.is_some());
assert!(max.is_none());
}
}