use thiserror::Error;
#[derive(Debug)]
pub struct Time {
pub year: i32,
pub month: i8,
pub day: i8,
pub hour: i8,
pub minute: i8,
pub second: i8,
}
#[derive(Debug, Error)]
pub enum TimeError {
#[error("Year {0} is out of valid range")]
InvalidYear(i32),
#[error("Month {0} is out of valid range")]
InvalidMonth(i8),
#[error("Day {0} is out of valid range")]
InvalidDay(i8),
#[error("Hour {0} is out of valid range")]
InvalidHour(i8),
#[error("Minute {0} is out of valid range")]
InvalidMinute(i8),
#[error("Second {0} is out of valid range")]
InvalidSecond(i8),
}
pub fn is_leap_year(year: u64) -> bool {
(year % 4 == 0 && year % 100 != 0) || year % 400 == 0
}
pub fn str2ts(date_str: &str) -> Result<u64, TimeError> {
str2time(date_str).try_into()
}
pub fn str2time(date_str: &str) -> Time {
let mut year = -1;
let mut month: i8 = -1;
let mut day: i8 = -1;
let mut hour: i8 = -1;
let mut minute: i8 = -1;
let mut second: i8 = -1;
let mut current_number = String::new();
#[allow(clippy::never_loop)]
'out: loop {
for c in date_str.chars() {
if c.is_ascii_digit() {
current_number.push(c);
} else if !current_number.is_empty() {
if year == -1 {
year = current_number.parse().unwrap();
} else {
let n = current_number.parse().unwrap();
if month == -1 {
month = n;
} else if day == -1 {
day = n;
} else if hour == -1 {
hour = n;
} else if minute == -1 {
minute = n;
} else if second == -1 {
second = n;
break 'out;
}
}
current_number.clear();
}
}
if !current_number.is_empty() {
second = current_number.parse().unwrap();
}
break;
}
Time {
year,
month,
day,
hour,
minute,
second,
}
}
impl TryFrom<Time> for u64 {
type Error = TimeError;
fn try_from(time: Time) -> Result<Self, Self::Error> {
let year = if time.year == -1 { 1970 } else { time.year };
if year < 1970 {
return Err(TimeError::InvalidYear(time.year));
}
let year = year as u64;
let month = if time.month == -1 { 1 } else { time.month };
if !(1..=12).contains(&month) {
return Err(TimeError::InvalidMonth(time.month));
}
let month = month as u64;
let day = if time.day == -1 { 1 } else { time.day };
let max_days = match month {
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
4 | 6 | 9 | 11 => 30,
2 if is_leap_year(year) => 29,
2 => 28,
_ => unreachable!(), };
if !(1..=max_days).contains(&{ day }) {
return Err(TimeError::InvalidDay(time.day));
}
let day = day as u64;
let hour = if time.hour == -1 { 0 } else { time.hour };
if !(0..=23).contains(&hour) {
return Err(TimeError::InvalidHour(time.hour));
}
let hour = hour as u64;
let minute = if time.minute == -1 { 0 } else { time.minute };
if !(0..=59).contains(&minute) {
return Err(TimeError::InvalidMinute(time.minute));
}
let minute = minute as u64;
let second = if time.second == -1 { 0 } else { time.second };
if !(0..=59).contains(&second) {
return Err(TimeError::InvalidSecond(time.second));
}
let second = second as u64;
let days_in_month = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
let mut days =
(year - 1970) * 365 + (year - 1969) / 4 - (year - 1901) / 100 + (year - 1601) / 400;
days += days_in_month[month as usize - 1];
if month > 2 && is_leap_year(year) {
days += 1;
}
days += day - 1;
Ok(days * 86400 + hour * 3600 + minute * 60 + second)
}
}