use std::error::Error;
use std::fmt::{self};
use std::path::Display;
use std::str::FromStr;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Date {
year: i32,
month: u8,
day: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Time {
hour: u8,
minute: u8,
second: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct DateTime {
pub date: Date,
pub time: Time,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DateTimeError {
InvalidYear(i32),
InvalidMonth(u8),
InvalidDay(u8),
InvalidHour(u8),
InvalidMinute(u8),
InvalidSecond(u8),
InvalidDate { year: i32, month: u8, day: u8 },
InvalidTime { hour: u8, minute: u8, second: u8 },
ParseError(String),
}
impl fmt::Display for DateTimeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidYear(year) => write!(f, "Invalid year: {}", year),
Self::InvalidMonth(month) => write!(f, "Invalid month: {}", month),
Self::InvalidDay(day) => write!(f, "Invalid day: {}", day),
Self::InvalidHour(hour) => write!(f, "Invalid hour: {}", hour),
Self::InvalidMinute(minute) => write!(f, "Invalid minute: {}", minute),
Self::InvalidSecond(second) => write!(f, "Invalid second: {}", second),
Self::InvalidDate { year, month, day } => {
write!(f, "Invalid date: {}-{}-{}", year, month, day)
}
Self::InvalidTime {
hour,
minute,
second,
} => write!(f, "Invalid time: {}:{}:{}", hour, minute, second),
Self::ParseError(msg) => write!(f, "Parse error: {}", msg),
}
}
}
impl Error for DateTimeError {}
impl Date {
pub const fn new(year: i32, month: u8, day: u8) -> Result<Self, DateTimeError> {
match (month, day) {
(m, d) if m >= 1 && m <= 12 && d >= 1 && d <= Self::days_in_month(year, m) => {
Ok(Self {
year,
month: m,
day: d,
})
}
(m, _) if m < 1 || m > 12 => Err(DateTimeError::InvalidMonth(m)),
(_, d) => Err(DateTimeError::InvalidDay(d)),
_ => unreachable!(), }
}
pub const fn days_in_month(year: i32, month: u8) -> u8 {
const DAYS: [u8; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
DAYS[month as usize - 1] + ((month == 2 && Self::is_leap_year(year)) as u8)
}
pub const fn is_leap_year(year: i32) -> bool {
year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
}
}
impl fmt::Display for Date {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:04}-{:02}-{:02}", self.year, self.month, self.day)
}
}
impl Time {
pub const fn new(hour: u8, minute: u8, second: u8) -> Result<Self, DateTimeError> {
match (hour, minute, second) {
(h, m, s) if h < 24 && m < 60 && s < 60 => Ok(Self {
hour: h,
minute: m,
second: s,
}),
(h, _, _) if h >= 24 => Err(DateTimeError::InvalidHour(h)),
(_, m, _) if m >= 60 => Err(DateTimeError::InvalidMinute(m)),
(_, _, s) if s >= 60 => Err(DateTimeError::InvalidSecond(s)),
_ => unreachable!(), }
}
}
impl fmt::Display for Time {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:02}:{:02}:{:02}", self.hour, self.minute, self.second)
}
}
impl DateTime {
pub fn now() -> Self {
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
Self::from_timestamp(now.as_secs() as i64).unwrap()
}
pub fn from_timestamp(timestamp: i64) -> Result<Self, DateTimeError> {
let (days, seconds) = (timestamp / 86400, timestamp % 86400);
let (year, month, day) = Self::calculate_ymd(days);
let (hour, minute, second) = (seconds / 3600, (seconds % 3600) / 60, seconds % 60);
Ok(Self {
date: Date::new(year, month, day + 1)?,
time: Time::new(hour as u8, minute as u8, second as u8)?,
})
}
fn calculate_ymd(mut days: i64) -> (i32, u8, u8) {
let mut year = 1970;
let mut month = 1;
while days >= 365 + Date::is_leap_year(year) as i64 {
days -= 365 + Date::is_leap_year(year) as i64;
year += 1;
}
while days >= Date::days_in_month(year, month) as i64 {
days -= Date::days_in_month(year, month) as i64;
month += 1;
}
(year, month, days as u8 + 1)
}
}
impl fmt::Display for DateTime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{:04}-{:02}-{:02} {:02}:{:02}:{:02}", self.date.year,
self.date.month,
self.date.day, self.time.hour,
self.time.minute,
self.time.second )
}
}
impl FromStr for DateTime {
type Err = DateTimeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<&str> = s.split_whitespace().collect();
if parts.len() != 2 {
return Err(DateTimeError::ParseError("Invalid format".to_string()));
}
let date_parts: Vec<&str> = parts[0].split('-').collect();
let time_parts: Vec<&str> = parts[1].split(':').collect();
if date_parts.len() != 3 || time_parts.len() != 3 {
return Err(DateTimeError::ParseError("Invalid format".to_string()));
}
fn parse_part<T>(part: &str, name: &str) -> Result<T, DateTimeError>
where
T: FromStr,
{
part.parse()
.map_err(|_| DateTimeError::ParseError(format!("Invalid {}", name)))
}
let year: i32 = parse_part(date_parts[0], "year")?;
let month: u8 = parse_part(date_parts[1], "month")?;
let day: u8 = parse_part(date_parts[2], "day")?;
let hour: u8 = parse_part(time_parts[0], "hour")?;
let minute: u8 = parse_part(time_parts[1], "minute")?;
let second: u8 = parse_part(time_parts[2], "second")?;
Ok(Self {
date: Date::new(year, month, day)?,
time: Time::new(hour, minute, second)?,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_date_creation() {
assert!(Date::new(2023, 4, 30).is_ok());
assert!(Date::new(2023, 2, 29).is_err());
assert!(Date::new(2024, 2, 29).is_ok());
}
#[test]
fn test_time_creation() {
assert!(Time::new(23, 59, 59).is_ok());
assert!(Time::new(24, 0, 0).is_err());
}
#[test]
fn test_datetime_from_timestamp() {
let dt = DateTime::from_timestamp(1682899200).unwrap();
assert_eq!(dt.to_string(), "2023-05-01 00:00:00");
}
#[test]
fn test_datetime_parsing() {
let dt: DateTime = "2023-05-01 12:34:56".parse().unwrap();
assert_eq!(dt.to_string(), "2023-05-01 12:34:56");
}
#[test]
fn test_error_display() {
let err = DateTimeError::InvalidYear(2023);
assert_eq!(err.to_string(), "Invalid year: 2023");
}
}