use std::io::ErrorKind;
use std::sync::OnceLock;
use time::OffsetDateTime;
use time::UtcOffset;
use time::macros::offset;
use tz::TimeZone;
use crate::config::is_yes;
pub const THAILAND_UTC_OFFSET: UtcOffset = offset!(+7:00:00);
pub fn local_now() -> OffsetDateTime {
let now = OffsetDateTime::now_utc();
let unix_timestamp = now.unix_timestamp();
let local_utc_offset = local_utc_offset_impl(Some(unix_timestamp));
now.to_offset(local_utc_offset)
}
pub fn local_utc_offset() -> UtcOffset {
local_utc_offset_impl(None)
}
fn local_utc_offset_impl(unix_timestamp: Option<i64>) -> UtcOffset {
static USE_CACHE_TIMEZONE: OnceLock<bool> = OnceLock::new();
let use_cache_timezone = *USE_CACHE_TIMEZONE.get_or_init(|| {
std::env::var("BEID_CACHE_TIMEZONE").map_or(true, is_yes)
});
if cfg!(unix) {
get_unix_local_utc_offset(use_cache_timezone, unix_timestamp)
.expect("UtcOffset for unix platform")
} else {
UtcOffset::current_local_offset().expect("Non-unix platform can get local offset")
}
}
fn is_io_not_found(error: &tz::Error) -> bool {
if let tz::Error::Io(err) = error
&& let Some(err) = err.downcast_ref::<std::io::Error>() {
return err.kind() == ErrorKind::NotFound;
}
false
}
fn get_unix_timezone() -> Result<TimeZone, tz::Error> {
if let Ok(tz_string) = std::env::var("TZ") {
TimeZone::from_posix_tz(&tz_string).or_else(|_| TimeZone::local())
} else {
TimeZone::local()
}
}
fn get_unix_local_utc_offset(
use_cache_timezone: bool,
unix_timestamp: Option<i64>,
) -> Result<UtcOffset, tz::Error> {
static TIMEZONE: OnceLock<TimeZone> = OnceLock::new();
let mut non_cached_timezone = None;
let timezone_result = if use_cache_timezone {
let tz = match TIMEZONE.get() {
Some(val) => val,
None => {
let tz = get_unix_timezone()?;
TIMEZONE.get_or_init(|| tz)
}
};
Ok(tz)
} else {
get_unix_timezone().map(|v| &*non_cached_timezone.insert(v))
};
let timezone = match timezone_result {
Ok(val) => val,
Err(err) if is_io_not_found(&err) => {
return Ok(UtcOffset::UTC);
}
Err(err) => return Err(err),
};
let local_time_type = match unix_timestamp {
Some(unix_timestamp) => timezone.find_local_time_type(unix_timestamp)?,
None => timezone.find_current_local_time_type()?,
};
let seconds = local_time_type.ut_offset();
Ok(UtcOffset::from_whole_seconds(seconds).expect("tz-rs returns valid utc offset seconds"))
}