use crate::datetime::common::{get_month_map, parse_timezone, RE_TZ_IN_STRING};
use once_cell::sync::Lazy;
use pyo3::prelude::*;
use pyo3::IntoPyObjectExt;
use regex::Regex;
static RE_GLOBAL_NUMERIC: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^(\d{1,2})[-/](\d{1,2})[-/](\d{4})(?:\s+(.+))?$").unwrap());
static RE_GLOBAL_NAMED: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"^(\d{1,2})[-/](Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|January|February|March|April|May|June|July|August|September|October|November|December)[-/](\d{4})(?:\s+(.+))?$").unwrap()
});
pub fn parse_global_datetime(py: Python, value: &str) -> PyResult<PyObject> {
let datetime_module = py.import("datetime")?;
let datetime_class = datetime_module.getattr("datetime")?;
let month_map = get_month_map();
let parse_tz = |tz_str: &str| -> PyResult<PyObject> { parse_timezone(py, tz_str) };
let parse_time_with_ampm = |time_str: &str| -> Result<(u8, u8, u8), PyErr> {
crate::datetime::common::parse_time_with_ampm(time_str)
};
if let Some(caps) = RE_GLOBAL_NUMERIC.captures(value) {
if let (Some(day_match), Some(month_match), Some(year_match)) =
(caps.get(1), caps.get(2), caps.get(3))
{
let day: u8 = day_match
.as_str()
.parse()
.map_err(|_| PyErr::new::<pyo3::exceptions::PyValueError, _>("Invalid day"))?;
let month: u8 = month_match
.as_str()
.parse()
.map_err(|_| PyErr::new::<pyo3::exceptions::PyValueError, _>("Invalid month"))?;
let year: i32 = year_match
.as_str()
.parse()
.map_err(|_| PyErr::new::<pyo3::exceptions::PyValueError, _>("Invalid year"))?;
let (hour, minute, second) = if let Some(time_part) = caps.get(4) {
let time_str = time_part.as_str().trim();
if let Some(tz_match) = RE_TZ_IN_STRING.captures(time_str).and_then(|c| c.get(1)) {
let tz_str = tz_match.as_str();
let time_only = time_str[..time_str.len() - tz_str.len()].trim();
let (h, m, s) = parse_time_with_ampm(time_only)?;
let tzinfo = parse_tz(tz_str)?;
let dt = datetime_class.call1((year, month, day, h, m, s, 0, tzinfo))?;
return dt.into_py_any(py);
} else {
parse_time_with_ampm(time_str)?
}
} else {
(0, 0, 0)
};
let dt =
datetime_class.call1((year, month, day, hour, minute, second, 0, py.None()))?;
return dt.into_py_any(py);
}
}
if let Some(caps) = RE_GLOBAL_NAMED.captures(value) {
if let (Some(day_match), Some(month_match), Some(year_match)) =
(caps.get(1), caps.get(2), caps.get(3))
{
let day: u8 = day_match
.as_str()
.parse()
.map_err(|_| PyErr::new::<pyo3::exceptions::PyValueError, _>("Invalid day"))?;
let month_name = month_match.as_str();
let month = *month_map.get(month_name).ok_or_else(|| {
PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
"Invalid month: {}",
month_name
))
})?;
let year: i32 = year_match
.as_str()
.parse()
.map_err(|_| PyErr::new::<pyo3::exceptions::PyValueError, _>("Invalid year"))?;
let (hour, minute, second, tzinfo) = if let Some(time_part) = caps.get(4) {
let time_str = time_part.as_str().trim();
if let Some(tz_match) = RE_TZ_IN_STRING.captures(time_str).and_then(|c| c.get(1)) {
let tz_str = tz_match.as_str();
let time_only = time_str[..time_str.len() - tz_str.len()].trim();
let (h, m, s) = parse_time_with_ampm(time_only)?;
let tz = parse_tz(tz_str)?;
(h, m, s, tz)
} else {
let (h, m, s) = parse_time_with_ampm(time_str)?;
(h, m, s, py.None())
}
} else {
(0, 0, 0, py.None())
};
let dt = datetime_class.call1((year, month, day, hour, minute, second, 0, tzinfo))?;
return dt.into_py_any(py);
}
}
Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
"Invalid Global datetime: {}",
value
)))
}