use chrono::{DateTime, Datelike, NaiveDateTime, Utc};
use regex::Regex;
use std::sync::LazyLock;
#[must_use]
pub fn parse_date_to_timestamp(date_str: &str) -> Option<i64> {
let s = date_str
.trim()
.trim_start_matches('[')
.trim_end_matches(']')
.trim_end_matches([',', ';', ':']);
if s.is_empty() || s.eq_ignore_ascii_case("null") || s.eq_ignore_ascii_case("nil") {
return None;
}
if let Ok(dt) = DateTime::parse_from_rfc3339(s) {
return Some(dt.timestamp());
}
if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.f") {
return Some(dt.and_utc().timestamp());
}
if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S") {
return Some(dt.and_utc().timestamp());
}
if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S") {
return Some(dt.and_utc().timestamp());
}
if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S%.f") {
return Some(dt.and_utc().timestamp());
}
if let Ok(dt) = chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d") {
return dt.and_hms_opt(0, 0, 0).map(|ndt| ndt.and_utc().timestamp());
}
if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%Y/%m/%d %H:%M:%S") {
return Some(dt.and_utc().timestamp());
}
if let Ok(dt) = chrono::NaiveDate::parse_from_str(s, "%Y/%m/%d") {
return dt.and_hms_opt(0, 0, 0).map(|ndt| ndt.and_utc().timestamp());
}
if let Ok(dt) = chrono::NaiveDate::parse_from_str(s, "%m/%d/%Y") {
return dt.and_hms_opt(0, 0, 0).map(|ndt| ndt.and_utc().timestamp());
}
if s.len() == 6
&& s.chars().all(|c| c.is_ascii_digit())
&& let Ok(dt) = chrono::NaiveDate::parse_from_str(s, "%d%m%y")
{
return dt.and_hms_opt(0, 0, 0).map(|ndt| ndt.and_utc().timestamp());
}
if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%a %b %e %H:%M:%S %Y") {
return Some(dt.and_utc().timestamp());
}
let parts: Vec<&str> = s.split_whitespace().collect();
if parts.len() == 3 {
let t = parts[2];
if t.len() >= 8
&& t.as_bytes().get(2) == Some(&b':')
&& t.as_bytes().get(5) == Some(&b':')
&& t[..2].chars().all(|c| c.is_ascii_digit())
&& t[3..5].chars().all(|c| c.is_ascii_digit())
&& t[6..8].chars().all(|c| c.is_ascii_digit())
{
let with_year = format!(
"{} {} {} {}",
parts[0],
parts[1],
parts[2],
Utc::now().year()
);
if let Ok(dt) = NaiveDateTime::parse_from_str(&with_year, "%b %e %H:%M:%S %Y") {
return Some(dt.and_utc().timestamp());
}
if t.len() > 8
&& t.as_bytes().get(8) == Some(&b'.')
&& let Ok(dt) = NaiveDateTime::parse_from_str(&with_year, "%b %e %H:%M:%S%.f %Y")
{
return Some(dt.and_utc().timestamp());
}
}
}
None
}
#[must_use]
pub fn is_boolean(value: &str) -> bool {
matches!(
value.to_lowercase().as_str(),
"true" | "false" | "yes" | "no" | "1" | "0" | "y" | "n"
)
}
const MIN_TIMESTAMP_SECONDS: i64 = 1_000_000_000;
const MAX_TIMESTAMP_SECONDS: i64 = 4_000_000_000;
const MIN_TIMESTAMP_MILLIS: i64 = 1_000_000_000_000;
const MAX_TIMESTAMP_MILLIS: i64 = 4_000_000_000_000;
const MIN_TIMESTAMP_SECONDS_F64: f64 = 1_000_000_000.0;
const MAX_TIMESTAMP_SECONDS_F64: f64 = 4_000_000_000.0;
const MIN_TIMESTAMP_MILLIS_F64: f64 = 1_000_000_000_000.0;
const MAX_TIMESTAMP_MILLIS_F64: f64 = 4_000_000_000_000.0;
#[must_use]
pub fn parse_timestamp_to_seconds(value: &str) -> Option<i64> {
if let Ok(ts) = value.parse::<i64>() {
if (MIN_TIMESTAMP_MILLIS..=MAX_TIMESTAMP_MILLIS).contains(&ts) {
return Some(ts / 1000);
}
if (MIN_TIMESTAMP_SECONDS..=MAX_TIMESTAMP_SECONDS).contains(&ts) {
return Some(ts);
}
}
if let Ok(ts) = value.parse::<f64>() {
if (MIN_TIMESTAMP_MILLIS_F64..=MAX_TIMESTAMP_MILLIS_F64).contains(&ts) {
return Some((ts / 1000.0) as i64);
}
if (MIN_TIMESTAMP_SECONDS_F64..=MAX_TIMESTAMP_SECONDS_F64).contains(&ts) {
return Some(ts as i64);
}
}
None
}
#[must_use]
pub fn contains_unix_timestamp(s: &str) -> bool {
let b = s.as_bytes();
for len in [10, 11, 12, 13] {
let Some(end) = b.len().checked_sub(len) else {
continue;
};
for start in 0..=end {
let slice = &b[start..start + len];
if slice.iter().all(|&c| c.is_ascii_digit())
&& let Ok(sub) = std::str::from_utf8(slice)
&& parse_timestamp_to_seconds(sub).is_some()
{
return true;
}
}
}
false
}
#[must_use]
pub fn is_number(value: &str) -> bool {
if parse_timestamp_to_seconds(value).is_some() {
return false;
}
if value.parse::<i64>().is_ok() {
return true;
}
if value.parse::<f64>().is_ok() {
return true;
}
false
}
static ISO_DATE_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^\d{4}-\d{2}-\d{2}(?:\s+\d{2}:\d{2}:\d{2}(?:\.\d+)?)?$")
.expect("ISO_DATE_REGEX pattern is valid")
});
static ISO_DATE_T_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:[+-]\d{2}:\d{2}|Z)?$")
.expect("ISO_DATE_T_REGEX pattern is valid")
});
static US_DATE_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^\d{1,2}[/-]\d{1,2}[/-]\d{4}$").expect("US_DATE_REGEX pattern is valid")
});
#[must_use]
pub fn is_date(value: &str) -> bool {
if ISO_DATE_REGEX.is_match(value) {
return true;
}
if ISO_DATE_T_REGEX.is_match(value) {
return true;
}
US_DATE_REGEX.is_match(value)
}
#[must_use]
pub fn is_stderr_tty() -> bool {
std::io::IsTerminal::is_terminal(&std::io::stderr())
}