use crate::error::Error;
use chrono::{DateTime, Utc};
use chrono_tz::Tz;
#[non_exhaustive]
#[derive(Debug, Clone)]
pub enum TimezoneSource {
Local,
Utc,
Named(Tz),
}
impl TimezoneSource {
pub fn local() -> Self {
Self::Local
}
pub fn utc() -> Self {
Self::Utc
}
pub fn named(iana: &str) -> Result<Self, Error> {
iana.parse::<Tz>()
.map(Self::Named)
.map_err(|_| Error::InvalidIanaName(iana.to_owned()))
}
pub fn render(&self, instant: DateTime<Utc>, fmt: &str) -> String {
match self {
Self::Local => instant
.with_timezone(&chrono::Local)
.format(fmt)
.to_string(),
Self::Utc => instant.format(fmt).to_string(),
Self::Named(tz) => instant.with_timezone(tz).format(fmt).to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::TimeZone;
#[test]
fn utc_renders_hours_as_zero_offset() {
let instant = Utc.with_ymd_and_hms(2026, 5, 22, 14, 30, 45).unwrap();
let rendered = TimezoneSource::Utc.render(instant, "%H:%M:%S");
assert_eq!(rendered, "14:30:45");
}
#[test]
fn named_resolves_known_iana() {
let tz = TimezoneSource::named("America/New_York").expect("known zone");
let instant = Utc.with_ymd_and_hms(2026, 5, 22, 14, 30, 45).unwrap();
let rendered = tz.render(instant, "%H:%M");
assert_eq!(rendered, "10:30");
}
#[test]
fn named_rejects_unknown_iana() {
let result = TimezoneSource::named("Atlantis/Atlantica");
match result {
Err(Error::InvalidIanaName(name)) => assert_eq!(name, "Atlantis/Atlantica"),
other => panic!("expected InvalidIanaName, got {other:?}"),
}
}
}