use crate::stdlib::StdlibValue;
use chrono::{DateTime, Datelike, Local, NaiveDate, NaiveDateTime, Timelike, Utc};
pub fn get_attribute(attr: &str) -> Option<StdlibValue> {
match attr {
"MINYEAR" => Some(StdlibValue::Int(1)),
"MAXYEAR" => Some(StdlibValue::Int(9999)),
_ => None,
}
}
pub fn get_function(func: &str) -> Option<DatetimeFunction> {
match func {
"datetime" => Some(DatetimeFunction::Datetime),
"date" => Some(DatetimeFunction::Date),
"time" => Some(DatetimeFunction::Time),
"timedelta" => Some(DatetimeFunction::Timedelta),
"timezone" => Some(DatetimeFunction::Timezone),
"tzinfo" => Some(DatetimeFunction::Tzinfo),
_ => None,
}
}
#[derive(Debug, Clone)]
pub enum DatetimeFunction {
Datetime,
Date,
Time,
Timedelta,
Timezone,
Tzinfo,
}
pub fn get_datetime_method(method: &str) -> Option<DatetimeMethod> {
match method {
"now" => Some(DatetimeMethod::Now),
"today" => Some(DatetimeMethod::Today),
"fromtimestamp" => Some(DatetimeMethod::Fromtimestamp),
"fromisoformat" => Some(DatetimeMethod::Fromisoformat),
"strftime" => Some(DatetimeMethod::Strftime),
"strptime" => Some(DatetimeMethod::Strptime),
"replace" => Some(DatetimeMethod::Replace),
"timestamp" => Some(DatetimeMethod::Timestamp),
"isoformat" => Some(DatetimeMethod::Isoformat),
"weekday" => Some(DatetimeMethod::Weekday),
"isoweekday" => Some(DatetimeMethod::Isoweekday),
_ => None,
}
}
#[derive(Debug, Clone)]
pub enum DatetimeMethod {
Now,
Today,
Fromtimestamp,
Fromisoformat,
Strftime,
Strptime,
Replace,
Timestamp,
Isoformat,
Weekday,
Isoweekday,
}
pub fn datetime_now_utc() -> i64 {
Utc::now().timestamp()
}
pub fn datetime_now_local() -> i64 {
Local::now().timestamp()
}
pub fn date_today() -> (i32, u32, u32) {
let today = Local::now().date_naive();
(today.year(), today.month(), today.day())
}
pub fn datetime_from_timestamp(timestamp: i64) -> Option<(i32, u32, u32, u32, u32, u32)> {
DateTime::from_timestamp(timestamp, 0).map(|dt| {
(
dt.year(),
dt.month(),
dt.day(),
dt.hour(),
dt.minute(),
dt.second(),
)
})
}
pub fn datetime_from_iso(iso_str: &str) -> Result<(i32, u32, u32, u32, u32, u32), String> {
NaiveDateTime::parse_from_str(iso_str, "%Y-%m-%dT%H:%M:%S")
.map(|dt| {
(
dt.year(),
dt.month(),
dt.day(),
dt.hour(),
dt.minute(),
dt.second(),
)
})
.map_err(|e| format!("Failed to parse datetime: {e}"))
}
pub fn datetime_to_iso(
year: i32,
month: u32,
day: u32,
hour: u32,
minute: u32,
second: u32,
) -> Result<String, String> {
NaiveDate::from_ymd_opt(year, month, day)
.and_then(|d| d.and_hms_opt(hour, minute, second))
.map(|dt| dt.format("%Y-%m-%dT%H:%M:%S").to_string())
.ok_or_else(|| "Invalid datetime values".to_string())
}
pub fn datetime_strftime(
year: i32,
month: u32,
day: u32,
hour: u32,
minute: u32,
second: u32,
format: &str,
) -> Result<String, String> {
NaiveDate::from_ymd_opt(year, month, day)
.and_then(|d| d.and_hms_opt(hour, minute, second))
.map(|dt| dt.format(format).to_string())
.ok_or_else(|| "Invalid datetime values".to_string())
}
#[allow(clippy::too_many_arguments)]
pub fn datetime_add_timedelta(
year: i32,
month: u32,
day: u32,
hour: u32,
minute: u32,
second: u32,
microsecond: u32,
td_days: i32,
td_seconds: i32,
td_microseconds: i32,
) -> Option<(i32, u32, u32, u32, u32, u32, u32)> {
use chrono::Duration;
let dt = NaiveDate::from_ymd_opt(year, month, day)?.and_hms_micro_opt(
hour,
minute,
second,
microsecond,
)?;
let duration = Duration::days(td_days as i64)
+ Duration::seconds(td_seconds as i64)
+ Duration::microseconds(td_microseconds as i64);
let new_dt = dt.checked_add_signed(duration)?;
Some((
new_dt.year(),
new_dt.month(),
new_dt.day(),
new_dt.hour(),
new_dt.minute(),
new_dt.second(),
new_dt.nanosecond() / 1000, ))
}
#[allow(clippy::too_many_arguments)]
pub fn datetime_sub_timedelta(
year: i32,
month: u32,
day: u32,
hour: u32,
minute: u32,
second: u32,
microsecond: u32,
td_days: i32,
td_seconds: i32,
td_microseconds: i32,
) -> Option<(i32, u32, u32, u32, u32, u32, u32)> {
datetime_add_timedelta(
year,
month,
day,
hour,
minute,
second,
microsecond,
-td_days,
-td_seconds,
-td_microseconds,
)
}
#[allow(clippy::too_many_arguments)]
pub fn datetime_diff(
year1: i32,
month1: u32,
day1: u32,
hour1: u32,
minute1: u32,
second1: u32,
microsecond1: u32,
year2: i32,
month2: u32,
day2: u32,
hour2: u32,
minute2: u32,
second2: u32,
microsecond2: u32,
) -> Option<(i32, i32, i32)> {
let dt1 = NaiveDate::from_ymd_opt(year1, month1, day1)?.and_hms_micro_opt(
hour1,
minute1,
second1,
microsecond1,
)?;
let dt2 = NaiveDate::from_ymd_opt(year2, month2, day2)?.and_hms_micro_opt(
hour2,
minute2,
second2,
microsecond2,
)?;
let duration = dt1.signed_duration_since(dt2);
let total_seconds = duration.num_seconds();
let days = (total_seconds / 86400) as i32;
let remaining_seconds = (total_seconds % 86400) as i32;
let microseconds = (duration.num_microseconds().unwrap_or(0) % 1_000_000) as i32;
Some((days, remaining_seconds, microseconds))
}
pub fn date_add_timedelta(
year: i32,
month: u32,
day: u32,
td_days: i32,
) -> Option<(i32, u32, u32)> {
use chrono::Duration;
let date = NaiveDate::from_ymd_opt(year, month, day)?;
let new_date = date.checked_add_signed(Duration::days(td_days as i64))?;
Some((new_date.year(), new_date.month(), new_date.day()))
}
pub fn date_diff(
year1: i32,
month1: u32,
day1: u32,
year2: i32,
month2: u32,
day2: u32,
) -> Option<i32> {
let date1 = NaiveDate::from_ymd_opt(year1, month1, day1)?;
let date2 = NaiveDate::from_ymd_opt(year2, month2, day2)?;
Some(date1.signed_duration_since(date2).num_days() as i32)
}
pub fn date_weekday(year: i32, month: u32, day: u32) -> Option<u32> {
NaiveDate::from_ymd_opt(year, month, day).map(|d| d.weekday().num_days_from_monday())
}
pub fn date_isoweekday(year: i32, month: u32, day: u32) -> Option<u32> {
NaiveDate::from_ymd_opt(year, month, day).map(|d| d.weekday().number_from_monday())
}
pub fn date_to_iso(year: i32, month: u32, day: u32) -> Option<String> {
NaiveDate::from_ymd_opt(year, month, day).map(|d| d.format("%Y-%m-%d").to_string())
}
pub fn time_to_iso(hour: u32, minute: u32, second: u32, microsecond: u32) -> String {
if microsecond == 0 {
format!("{hour:02}:{minute:02}:{second:02}")
} else {
format!("{hour:02}:{minute:02}:{second:02}.{microsecond:06}")
}
}
pub fn date_from_iso(iso_str: &str) -> Result<(i32, u32, u32), String> {
NaiveDate::parse_from_str(iso_str, "%Y-%m-%d")
.map(|d| (d.year(), d.month(), d.day()))
.map_err(|e| format!("Failed to parse date: {e}"))
}
pub fn time_from_iso(iso_str: &str) -> Result<(u32, u32, u32, u32), String> {
if let Ok(t) = chrono::NaiveTime::parse_from_str(iso_str, "%H:%M:%S%.f") {
return Ok((t.hour(), t.minute(), t.second(), t.nanosecond() / 1000));
}
chrono::NaiveTime::parse_from_str(iso_str, "%H:%M:%S")
.map(|t| (t.hour(), t.minute(), t.second(), 0))
.map_err(|e| format!("Failed to parse time: {e}"))
}