use std::cell::RefCell;
use chrono::{DateTime, FixedOffset, Utc};
thread_local! {
static CURRENT_TIMEZONE: RefCell<String> = RefCell::new("UTC".to_string());
}
pub fn activate_timezone(tz_name: &str) {
CURRENT_TIMEZONE.with(|timezone| {
*timezone.borrow_mut() = tz_name.to_owned();
});
}
pub fn deactivate_timezone() {
CURRENT_TIMEZONE.with(|timezone| {
*timezone.borrow_mut() = "UTC".to_owned();
});
}
#[must_use]
pub fn get_current_timezone_name() -> String {
CURRENT_TIMEZONE.with(|timezone| timezone.borrow().clone())
}
#[must_use]
pub fn now() -> DateTime<Utc> {
Utc::now()
}
#[must_use]
pub fn localtime(dt: &DateTime<Utc>, offset_hours: i32) -> DateTime<FixedOffset> {
dt.with_timezone(&fixed_offset(offset_hours))
}
#[must_use]
pub fn is_aware(_dt: &DateTime<Utc>) -> bool {
true
}
#[must_use]
pub fn is_naive(dt_str: &str) -> bool {
!dt_str.contains('+') && !dt_str.ends_with('Z')
}
#[must_use]
pub fn make_aware(naive_str: &str, offset_hours: i32) -> String {
format!("{naive_str}{}", format_offset(offset_hours))
}
fn fixed_offset(offset_hours: i32) -> FixedOffset {
let clamped = offset_hours.clamp(-23, 23) * 3600;
FixedOffset::east_opt(clamped)
.unwrap_or_else(|| FixedOffset::east_opt(0).expect("zero offset must be valid"))
}
fn format_offset(offset_hours: i32) -> String {
let clamped = offset_hours.clamp(-23, 23);
let sign = if clamped < 0 { '-' } else { '+' };
let hours = clamped.unsigned_abs();
format!("{sign}{hours:02}:00")
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::{TimeZone, Timelike, Utc};
#[test]
fn test_timezone_activate_deactivate() {
deactivate_timezone();
activate_timezone("Europe/Berlin");
assert_eq!(get_current_timezone_name(), "Europe/Berlin");
deactivate_timezone();
assert_eq!(get_current_timezone_name(), "UTC");
}
#[test]
fn test_localtime_offset() {
let dt = Utc.with_ymd_and_hms(2024, 3, 19, 12, 0, 0).unwrap();
let local = localtime(&dt, 2);
assert_eq!(local.offset().local_minus_utc(), 2 * 3600);
assert_eq!(local.hour(), 14);
}
#[test]
fn test_is_naive_detection() {
assert!(is_naive("2024-03-19T12:00:00"));
assert!(!is_naive("2024-03-19T12:00:00+02:00"));
assert!(!is_naive("2024-03-19T12:00:00Z"));
}
#[test]
fn test_make_aware() {
assert_eq!(
make_aware("2024-03-19T12:00:00", 2),
"2024-03-19T12:00:00+02:00"
);
assert_eq!(
make_aware("2024-03-19T12:00:00", -5),
"2024-03-19T12:00:00-05:00"
);
}
#[test]
fn aware_datetimes_are_always_aware() {
let dt = Utc.with_ymd_and_hms(2024, 3, 19, 12, 0, 0).unwrap();
assert!(is_aware(&dt));
}
#[test]
fn now_returns_utc_datetime() {
assert_eq!(now().timezone(), Utc);
}
}