use std::{str::FromStr, time::SystemTime};
use jiff::{civil::Date, fmt::rfc2822, tz::TimeZone, Zoned};
use crate::parse::git::parse_git_date_format;
use crate::parse::raw::parse_raw;
use crate::{
parse::relative,
time::format::{DEFAULT, GITOXIDE, ISO8601, ISO8601_STRICT, SHORT},
Error, OffsetInSeconds, SecondsSinceUnixEpoch, Time,
};
use gix_error::{Exn, ResultExt};
pub fn parse(input: &str, now: Option<SystemTime>) -> Result<Time, Exn<Error>> {
Ok(if let Ok(val) = Date::strptime(SHORT.0, input) {
let val = val
.to_zoned(TimeZone::UTC)
.or_raise(|| Error::new_with_input("Timezone conversion failed", input))?;
Time::new(val.timestamp().as_second(), val.offset().seconds())
} else if let Ok(val) = rfc2822_relaxed(input) {
Time::new(val.timestamp().as_second(), val.offset().seconds())
} else if let Ok(val) = strptime_relaxed(ISO8601.0, input) {
Time::new(val.timestamp().as_second(), val.offset().seconds())
} else if let Ok(val) = strptime_relaxed(ISO8601_STRICT.0, input) {
Time::new(val.timestamp().as_second(), val.offset().seconds())
} else if let Ok(val) = strptime_relaxed(GITOXIDE.0, input) {
Time::new(val.timestamp().as_second(), val.offset().seconds())
} else if let Ok(val) = strptime_relaxed(DEFAULT.0, input) {
Time::new(val.timestamp().as_second(), val.offset().seconds())
} else if let Ok(val) = SecondsSinceUnixEpoch::from_str(input) {
Time::new(val, 0)
} else if let Some(val) = parse_git_date_format(input) {
val
} else if let Some(val) = relative::parse(input, now).transpose()? {
Time::new(val.timestamp().as_second(), val.offset().seconds())
} else if let Some(val) = parse_raw(input) {
val
} else {
return Err(Error::new_with_input("Unknown date format", input))?;
})
}
pub fn parse_header(input: &str) -> Option<Time> {
pub enum Sign {
Plus,
Minus,
}
fn parse_offset(offset: &str) -> Option<OffsetInSeconds> {
if (offset.len() != 5) && (offset.len() != 7) {
return None;
}
let sign = match offset.get(..1)? {
"-" => Some(Sign::Minus),
"+" => Some(Sign::Plus),
_ => None,
}?;
if offset.as_bytes().get(1).is_some_and(|b| !b.is_ascii_digit()) {
return None;
}
let hours: i32 = offset.get(1..3)?.parse().ok()?;
let minutes: i32 = offset.get(3..5)?.parse().ok()?;
let offset_seconds: i32 = if offset.len() == 7 {
offset.get(5..7)?.parse().ok()?
} else {
0
};
let mut offset_in_seconds = hours * 3600 + minutes * 60 + offset_seconds;
if matches!(sign, Sign::Minus) {
offset_in_seconds *= -1;
}
Some(offset_in_seconds)
}
if input.contains(':') {
return None;
}
let mut split = input.split_whitespace();
let seconds = split.next()?;
let seconds = match seconds.parse::<SecondsSinceUnixEpoch>() {
Ok(s) => s,
Err(_err) => {
let first_digits: String = seconds.chars().take_while(char::is_ascii_digit).collect();
first_digits.parse().ok()?
}
};
let offset = match split.next() {
None => 0,
Some(offset) => {
if split.next().is_some() {
0
} else {
parse_offset(offset).unwrap_or_default()
}
}
};
let time = Time { seconds, offset };
Some(time)
}
fn strptime_relaxed(fmt: &str, input: &str) -> std::result::Result<Zoned, jiff::Error> {
let mut tm = jiff::fmt::strtime::parse(fmt, input)?;
tm.set_weekday(None);
tm.to_zoned()
}
fn rfc2822_relaxed(input: &str) -> std::result::Result<Zoned, jiff::Error> {
static P: rfc2822::DateTimeParser = rfc2822::DateTimeParser::new().relaxed_weekday(true);
P.parse_zoned(input)
}