use super::ParseDateTimeError;
const DAYS_IN_MONTH: [u8; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
const DAYS_IN_MONTH_LEAP: [u8; 12] = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
const DOY_AT_MONTH: [i32; 12] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
const DOY_AT_MONTH_LEAP: [i32; 12] = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335];
pub(crate) const fn is_leap(year: i64) -> bool {
(year & 0b11) == 0 && (year % 100 != 0 || year % 400 == 0)
}
pub(crate) const fn days_in_month(year: i32, month: u8) -> u8 {
let month_idx = (month - 1) as usize;
if is_leap(year as i64) {
DAYS_IN_MONTH_LEAP[month_idx]
} else {
DAYS_IN_MONTH[month_idx]
}
}
pub(crate) const fn day_of_year(year: i32, month: u8, day: u8) -> i32 {
let month_idx = (month - 1) as usize;
let table = if is_leap(year as i64) {
&DOY_AT_MONTH_LEAP
} else {
&DOY_AT_MONTH
};
(day - 1) as i32 + table[month_idx]
}
pub(crate) fn month_and_day_of_month(year: i64, doy: i32) -> (u8, u8) {
let table = if is_leap(year) {
&DOY_AT_MONTH_LEAP
} else {
&DOY_AT_MONTH
};
let idx = table.binary_search(&doy).unwrap_or_else(|e| e - 1);
let month = idx as u8 + 1;
let day = (doy - table[idx]) as u8 + 1;
(month, day)
}
pub(crate) const fn days_from_year_0(year: i32) -> i64 {
let year = year as i64;
let offset = (year > 0) as i64;
let y = year - offset;
let m4 = y / 4 + offset;
let m100 = y / 100;
let m400 = m100 / 4;
year * 365 + m4 - m100 + m400
}
pub(crate) fn secs_to_date_time(secs_from_year_0: i128) -> (i64, i32, i64) {
let mut n_period = (secs_from_year_0 / (146097 * 86400)) as i64;
let mut sec = (secs_from_year_0 % (146097 * 86400)) as i64;
if sec < 0 {
n_period -= 1;
sec += 146097 * 86400;
}
let mut year = 400 * n_period;
let mut day = sec / 86400;
let sec = sec - day * 86400;
if day < 366 {
return (year, day as i32, sec);
}
for (years_in_period, days_in_period, starts_with_non_leap_year) in
[(100, 36524, 1), (4, 1461, 0), (1, 365, 1)]
{
day -= starts_with_non_leap_year;
let n_period = day / days_in_period;
year += years_in_period * n_period;
day -= n_period * days_in_period;
if day < (366 - starts_with_non_leap_year) {
return (year, day as i32, sec);
}
day += starts_with_non_leap_year;
}
unreachable!();
}
#[allow(clippy::type_complexity)]
pub(crate) fn parse_date_time(
stream: &str,
) -> Result<(i32, u8, u8, u8, u8, u8, u32), ParseDateTimeError> {
fn pull_two_digits<'a, E>(
stream: &'a str,
delimiter: &'a [char],
missing_delimiter_error: E,
) -> Result<(u8, Result<&'a str, E>), ParseDateTimeError> {
let (token, stream) = stream
.split_once(delimiter)
.map(|(t, s)| (t, Ok(s)))
.unwrap_or((stream, Err(missing_delimiter_error)));
if token.len() != 2 {
return Err(ParseDateTimeError::InvalidFieldWidth);
}
token
.parse()
.map_err(|_| ParseDateTimeError::InvalidFieldValue)
.map(|token| (token, stream))
}
let (stream, sign) = match stream.chars().next() {
Some('+') => (&stream[1..], 1),
Some('-') => (&stream[1..], -1),
_ => (stream, 1),
};
let (year, stream) = stream
.split_once('-')
.ok_or(ParseDateTimeError::MissingField)?;
let year_digits = year.len();
if year_digits < 4 {
return Err(ParseDateTimeError::InvalidFieldWidth);
}
let year = year
.parse()
.map(|year: i32| sign * year)
.map_err(|_| ParseDateTimeError::InvalidFieldValue)?;
let (month, stream) = pull_two_digits(stream, &['-'], ParseDateTimeError::MissingField)?;
let (day, stream) =
pull_two_digits(stream?, &[' ', 'T', 't'], ParseDateTimeError::MissingField)?;
let (hour, stream) = pull_two_digits(stream?, &[':'], ParseDateTimeError::MissingField)?;
let (min, stream) = pull_two_digits(stream?, &[':'], ParseDateTimeError::MissingField)?;
let (sec, stream) = pull_two_digits(stream?, &['.'], ())?;
let mut nano = 0u32;
match stream {
Ok("") => return Err(ParseDateTimeError::MissingField),
Ok(stream) => {
let mut weight = 100_000_000;
for c in stream.chars() {
let digit = c
.to_digit(10)
.ok_or(ParseDateTimeError::InvalidFieldValue)?;
nano += digit * weight;
weight /= 10;
}
}
Err(_) => {}
}
Ok((year, month, day, hour, min, sec, nano))
}