use std::fmt;
use std::cmp::Ordering;
use std::ops::{Add, Sub, Mul, Div, Neg};
use std::time::SystemTime;
use crate::Location;
const SECONDS_PER_DAY: i64 = 86400;
const SECONDS_PER_HOUR: i64 = 3600;
const SECONDS_PER_MINUTE: i64 = 60;
const NANOS_PER_SECOND: i64 = 1_000_000_000;
pub trait DateLike {
fn year(&self) -> i32;
fn month(&self) -> u32;
fn date(&self) -> u32;
fn hour(&self) -> u32;
fn minute(&self) -> u32;
fn second(&self) -> u32;
fn week(&self) -> u32;
fn add_duration(&self, duration: Duration) -> Self;
fn sub_duration(&self, duration: Duration) -> Self;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Date {
year: i32,
month: u32,
day: u32,
}
impl Date {
pub fn new(year: i32, month: u32, day: u32) -> Option<Self> {
if is_valid_date(year, month, day) {
Some(Self { year, month, day })
} else {
None
}
}
pub fn next_day(&self) -> Self {
let (y, m, d) = civil_from_days(days_from_civil(self.year, self.month, self.day) + 1);
Self::new(y, m as u32, d as u32).unwrap()
}
pub fn previous_day(&self) -> Self {
let (y, m, d) = civil_from_days(days_from_civil(self.year, self.month, self.day) - 1);
Self::new(y, m as u32, d as u32).unwrap()
}
pub fn add_years(&self, years: i32) -> Self {
let new_year = self.year + years;
let new_day = std::cmp::min(self.day, days_in_month(new_year, self.month));
Self::new(new_year, self.month, new_day).unwrap()
}
pub fn add_months(&self, months: i32) -> Self {
let mut months = months;
let mut year = self.year;
let mut month = self.month;
let day = self.day;
while months > 0 {
if month == 12 {
year += 1;
month = 1;
} else {
month += 1;
}
months -= 1;
}
Self::new(year, month, day).unwrap()
}
pub fn add_days(&self, days: i32) -> Self {
let mut days = days;
let mut year = self.year;
let mut month = self.month;
let mut day = self.day;
while days > 0 {
day += 1;
if day > days_in_month(year, month) {
month += 1;
day = 1;
}
if month > 12 {
year += 1;
month = 1;
}
days -= 1;
}
Self::new(year, month, day).unwrap()
}
pub fn sub_years(&self, years: i32) -> Self {
let new_year = self.year - years;
let new_day = std::cmp::min(self.day, days_in_month(new_year, self.month));
Self::new(new_year, self.month, new_day).unwrap()
}
pub fn sub_months(&self, months: i32) -> Self {
let mut months = months;
let mut year = self.year;
let mut month = self.month;
let day = self.day;
while months < 0 {
if month == 1 {
year -= 1;
month = 12;
} else {
month -= 1;
}
months -= 1;
}
Self::new(year, month, day).unwrap()
}
pub fn sub_days(&self, days: i32) -> Self {
let mut days = days;
let mut year = self.year;
let mut month = self.month;
let mut day = self.day;
while days < 0 {
day -= 1;
if day == 0 {
month -= 1;
if month == 0 {
year -= 1;
month = 12;
}
day = days_in_month(year, month);
}
days -= 1;
}
Self::new(year, month, day).unwrap()
}
pub fn with_year(self, year: i32) -> Self {
let new_day = std::cmp::min(self.day, days_in_month(year, self.month));
Self::new(year, self.month, new_day).unwrap()
}
pub fn with_month(self, month: u32) -> Self {
let new_day = std::cmp::min(self.day, days_in_month(self.year, month));
Self::new(self.year, month, new_day).unwrap()
}
pub fn with_day(self, day: u32) -> Self {
let new_day = std::cmp::min(day, days_in_month(self.year, self.month));
Self::new(self.year, self.month, new_day).unwrap()
}
pub fn now() -> Self {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("Time went backwards");
let days_since_epoch = now.as_secs() / 86400;
let (year, month, day) = civil_from_days(days_since_epoch as i32);
Self::new(year, month as u32, day as u32).unwrap()
}
}
impl DateLike for Date {
fn year(&self) -> i32 { self.year }
fn month(&self) -> u32 { self.month }
fn date(&self) -> u32 { self.day }
fn hour(&self) -> u32 { 0 }
fn minute(&self) -> u32 { 0 }
fn second(&self) -> u32 { 0 }
fn week(&self) -> u32 {
let days = days_from_civil(self.year, self.month, self.day) + 4;
let weeks = days / 7;
let remainder = days % 7;
let week = (weeks % 52 + 52) % 52 + 1;
week as u32
}
fn add_duration(&self, duration: Duration) -> Self {
self.add_days(duration.total_seconds() as i32 / SECONDS_PER_DAY as i32)
}
fn sub_duration(&self, duration: Duration) -> Self {
self.add_duration(-duration)
}
}
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 Default for Date {
fn default() -> Self {
Date::new(1970, 1, 1).unwrap()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Time {
hour: u32,
minute: u32,
second: u32,
}
impl Time {
pub fn new(hour: u32, minute: u32, second: u32) -> Self {
let total_seconds = hour * 3600 + minute * 60 + second;
let normalized_hour = (total_seconds / 3600) % 24;
let normalized_minute = (total_seconds % 3600) / 60;
let normalized_second = total_seconds % 60;
Self {
hour: normalized_hour,
minute: normalized_minute,
second: normalized_second,
}
}
pub fn from_hours(hours: f64) -> Self {
let total_seconds = (hours * 3600.0).round() as u32;
let hour = total_seconds / 3600;
let minute = (total_seconds % 3600) / 60;
let second = total_seconds % 60;
Self::new(hour, minute, second)
}
pub fn hour(&self) -> u32 { self.hour }
pub fn minute(&self) -> u32 { self.minute }
pub fn second(&self) -> u32 { self.second }
pub fn hour_fraction(&self) -> f64 {
self.hour as f64 + self.minute as f64 / 60.0 + self.second as f64 / 3600.0
}
}
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)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TimeZone {
offset: i32,
}
impl TimeZone {
pub fn new(hours: f32) -> Self {
let total_minutes = (hours * 60.0).round() as i32;
Self { offset: total_minutes }
}
pub fn utc() -> Self {
Self { offset: 0 }
}
pub fn offset_seconds(&self) -> i32 {
self.offset * 60
}
}
impl Default for TimeZone {
fn default() -> Self {
Self::utc()
}
}
impl From<f32> for TimeZone {
fn from(offset: f32) -> Self {
Self::new(offset)
}
}
impl From<Location> for TimeZone {
fn from(location: Location) -> Self {
Self::new(location.timezone_offset() as f32 / 60.0)
}
}
impl fmt::Display for TimeZone {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let hours = self.offset / 60;
let minutes = self.offset.abs() % 60;
write!(f, "{:+03}:{:02}", hours, minutes)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DateTime {
date: Date,
time: Time,
timezone: TimeZone,
}
impl DateTime {
pub fn new(date: Date, time: Time, timezone: TimeZone) -> Self {
Self { date, time, timezone }
}
pub fn from_timestamp(timestamp: i64, timezone: TimeZone) -> Self {
let days = timestamp / SECONDS_PER_DAY;
let seconds = timestamp % SECONDS_PER_DAY;
let (y, m, d) = civil_from_days(days as i32);
let date = Date::new(y, m as u32, d as u32).unwrap();
let hour = (seconds / SECONDS_PER_HOUR) as u32;
let minute = ((seconds % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE) as u32;
let second = (seconds % SECONDS_PER_MINUTE) as u32;
let time = Time::new(hour, minute, second);
Self { date, time, timezone }
}
pub fn to_utc(&self) -> Self {
let timestamp = self.timestamp();
Self::from_timestamp(timestamp, TimeZone::utc())
}
pub fn with_timezone(&self, timezone: TimeZone) -> Self {
let timestamp = self.timestamp();
Self::from_timestamp(timestamp, timezone)
}
pub fn timestamp(&self) -> i64 {
let days = days_from_civil(self.date.year, self.date.month, self.date.day);
let seconds = self.time.hour as i64 * SECONDS_PER_HOUR
+ self.time.minute as i64 * SECONDS_PER_MINUTE
+ self.time.second as i64;
days as i64 * SECONDS_PER_DAY + seconds - self.timezone.offset_seconds() as i64
}
pub fn date(&self) -> Date { self.date }
pub fn time(&self) -> Time { self.time }
pub fn timezone(&self) -> TimeZone { self.timezone }
pub fn year(&self) -> i32 { self.date.year }
pub fn month(&self) -> u32 { self.date.month }
pub fn day(&self) -> u32 { self.date.day }
pub fn hour(&self) -> u32 { self.time.hour }
pub fn minute(&self) -> u32 { self.time.minute }
pub fn second(&self) -> u32 { self.time.second }
pub fn hour_fraction(&self) -> f64 {
self.time.hour_fraction()
}
pub fn add_years(&self, years: i32) -> Self {
let new_date = self.date.add_years(years);
Self::new(new_date, self.time, self.timezone)
}
pub fn add_months(&self, months: i32) -> Self {
let new_date = self.date.add_months(months);
Self::new(new_date, self.time, self.timezone)
}
pub fn add_days(&self, days: i32) -> Self {
let new_date = self.date.add_days(days);
Self::new(new_date, self.time, self.timezone)
}
pub fn add_hours(&self, hours: i32) -> Self {
self.add_duration(Duration::from_hours(hours as i64))
}
pub fn add_minutes(&self, minutes: i32) -> Self {
self.add_duration(Duration::from_minutes(minutes as i64))
}
pub fn add_seconds(&self, seconds: i32) -> Self {
self.add_duration(Duration::from_seconds(seconds as i64))
}
pub fn sub_years(&self, years: i32) -> Self { self.add_years(-years) }
pub fn sub_months(&self, months: i32) -> Self { self.add_months(-months) }
pub fn sub_days(&self, days: i32) -> Self { self.add_days(-days) }
pub fn sub_hours(&self, hours: i32) -> Self { self.add_hours(-hours) }
pub fn sub_minutes(&self, minutes: i32) -> Self { self.add_minutes(-minutes) }
pub fn sub_seconds(&self, seconds: i32) -> Self { self.add_seconds(-seconds) }
pub fn create_utc(year: i32, month: u32, day: u32, hour: f64) -> Self {
let date = Date::new(year, month, day).unwrap();
let time = Time::from_hours(hour);
Self::new(date, time, TimeZone::utc())
}
pub fn create_local(year: i32, month: u32, day: u32, hour: f64, timezone_offset: f32) -> Self {
let date = Date::new(year, month, day).unwrap();
let time = Time::from_hours(hour);
let timezone = TimeZone::new(timezone_offset);
Self::new(date, time, timezone)
}
pub fn at_location(location: impl Into<Location>, datetime: impl Into<DateTime>) -> Self {
let datetime = datetime.into();
let location = location.into();
let timezone = TimeZone::from(location);
datetime.with_timezone(timezone)
}
pub fn time_distance(&self, other: Self) -> Duration {
Duration::from_seconds(self.timestamp() - other.timestamp())
}
pub fn from_date_time(date: impl Into<Date>, time: impl Into<Time>, timezone: impl Into<TimeZone>) -> Self {
let date = date.into();
let time = time.into();
let timezone = timezone.into();
Self::new(date, time, timezone)
}
pub fn now() -> Self {
let date = Date::new(1970, 1, 1).unwrap();
let time = Time::new(0, 0, 0);
let timezone = TimeZone::utc();
Self::new(date, time, timezone)
}
}
impl DateLike for DateTime {
fn year(&self) -> i32 { self.date.year }
fn month(&self) -> u32 { self.date.month }
fn date(&self) -> u32 { self.date.day }
fn hour(&self) -> u32 { self.time.hour }
fn minute(&self) -> u32 { self.time.minute }
fn second(&self) -> u32 { self.time.second }
fn week(&self) -> u32 { self.date.week() }
fn add_duration(&self, duration: Duration) -> Self {
let new_timestamp = self.timestamp() + duration.total_seconds();
Self::from_timestamp(new_timestamp, self.timezone)
}
fn sub_duration(&self, duration: Duration) -> Self {
self.add_duration(-duration)
}
}
impl PartialOrd for DateTime {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.timestamp().partial_cmp(&other.timestamp())
}
}
impl Ord for DateTime {
fn cmp(&self, other: &Self) -> Ordering {
self.timestamp().cmp(&other.timestamp())
}
}
impl Add<Duration> for DateTime {
type Output = Self;
fn add(self, duration: Duration) -> Self::Output {
self.add_duration(duration)
}
}
impl Sub<Duration> for DateTime {
type Output = Self;
fn sub(self, duration: Duration) -> Self::Output {
self.sub_duration(duration)
}
}
impl Sub<DateTime> for DateTime {
type Output = Duration;
fn sub(self, other: DateTime) -> Duration {
Duration::from_seconds(self.timestamp() - other.timestamp())
}
}
impl fmt::Display for DateTime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}{}",
self.year(), self.month(), self.date(), self.hour(), self.minute(), self.second(), self.timezone)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Duration {
seconds: i64,
nanos: i32,
}
impl Into<Duration> for (i32, u32, u32, f64) {
fn into(self) -> Duration {
let (year, month, day, hour) = self;
let datetime = DateTime::create_utc(year, month, day, hour);
datetime.time_distance(DateTime::create_utc(year, month, day, 0.0))
}
}
impl Duration {
pub fn new<S: Into<i64>, N: Into<i32>>(seconds: S, nanos: N) -> Self {
let mut seconds = seconds.into();
let mut nanos = nanos.into();
if nanos >= NANOS_PER_SECOND as i32 || nanos <= -(NANOS_PER_SECOND as i32) {
seconds += nanos as i64 / NANOS_PER_SECOND;
nanos %= NANOS_PER_SECOND as i32;
}
if seconds > 0 && nanos < 0 {
seconds -= 1;
nanos += NANOS_PER_SECOND as i32;
} else if seconds < 0 && nanos > 0 {
seconds += 1;
nanos -= NANOS_PER_SECOND as i32;
}
Self { seconds, nanos }
}
pub fn from_seconds<S: Into<i64>>(seconds: S) -> Self {
Self::new(seconds, 0)
}
pub fn from_days<D: Into<i64>>(days: D) -> Self {
Self::from_seconds(days.into() * SECONDS_PER_DAY)
}
pub fn from_years<Y: Into<i64>>(years: Y) -> Self {
Self::from_seconds(years.into() * SECONDS_PER_DAY * 365)
}
pub fn from_hours<H: Into<i64>>(hours: H) -> Self {
Self::from_seconds(hours.into() * SECONDS_PER_HOUR)
}
pub fn from_minutes<M: Into<i64>>(minutes: M) -> Self {
Self::from_seconds(minutes.into() * SECONDS_PER_MINUTE)
}
pub fn total_seconds(&self) -> i64 {
self.seconds
}
pub fn abs(&self) -> Self {
if self.seconds < 0 || (self.seconds == 0 && self.nanos < 0) {
self.neg()
} else {
*self
}
}
}
impl Add for Duration {
type Output = Self;
fn add(self, other: Self) -> Self::Output {
Self::new(
self.seconds + other.seconds,
self.nanos + other.nanos,
)
}
}
impl Sub for Duration {
type Output = Self;
fn sub(self, other: Self) -> Self::Output {
Self::new(
self.seconds - other.seconds,
self.nanos - other.nanos,
)
}
}
impl Mul<i64> for Duration {
type Output = Self;
fn mul(self, rhs: i64) -> Self::Output {
Self::new(self.seconds * rhs, self.nanos * rhs as i32)
}
}
impl Div<i64> for Duration {
type Output = Self;
fn div(self, rhs: i64) -> Self::Output {
let total_nanos = self.seconds * NANOS_PER_SECOND + self.nanos as i64;
let result_nanos = total_nanos / rhs;
Self::new(result_nanos / NANOS_PER_SECOND, (result_nanos % NANOS_PER_SECOND) as i32)
}
}
impl Neg for Duration {
type Output = Self;
fn neg(self) -> Self::Output {
Self::new(-self.seconds, -self.nanos)
}
}
impl fmt::Display for Duration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut first = true;
let abs = self.abs();
if self.seconds < 0 || self.nanos < 0 {
write!(f, "-")?;
}
let components = [
(abs.seconds / SECONDS_PER_DAY, "d"),
((abs.seconds % SECONDS_PER_DAY) / SECONDS_PER_HOUR, "h"),
((abs.seconds % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE, "m"),
(abs.seconds % SECONDS_PER_MINUTE, "s"),
];
for (value, unit) in components.iter() {
if *value > 0 || (first && *unit == "s") {
if !first {
write!(f, " ")?;
}
write!(f, "{}{}", value, unit)?;
first = false;
}
}
if abs.nanos > 0 {
write!(f, ".{:09}s", abs.nanos)?;
}
Ok(())
}
}
pub fn is_valid_date(year: i32, month: u32, day: u32) -> bool {
if year < 1 || month < 1 || month > 12 || day < 1 {
return false;
}
let days_in_month = match month {
4 | 6 | 9 | 11 => 30,
2 => {
if is_leap_year(year) {
29
} else {
28
}
}
_ => 31,
};
day <= days_in_month
}
pub fn is_leap_year(year: i32) -> bool {
(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
}
pub fn days_before_year(year: i32) -> i32 {
365 * (year - 1) + (year - 1) / 4 - (year - 1) / 100 + (year - 1) / 400
}
pub fn civil_from_days(days: i32) -> (i32, i32, i32) {
let z = days + 719468;
let era = (if z >= 0 { z } else { z - 146096 }) / 146097;
let doe = z - era * 146097;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let m = mp + (if mp < 10 { 3 } else { -9 });
(y + (if m <= 2 { 1 } else { 0 }), m, d)
}
pub fn days_from_civil(y: i32, m: u32, d: u32) -> i32 {
let y = y - (m <= 2) as i32;
let era = (if y >= 0 { y } else { y - 399 }) / 400;
let yoe = y - era * 400;
let doy = (153 * (m + (if m > 10 { 11 } else { 0 }) - 1) + 2) / 5 + d - 1;
let doe = ( era * 146097 + (doy as i32) + 719468
) as i32;
doe
}
pub fn days_in_month(year: i32, month: u32) -> u32 {
match month {
4 | 6 | 9 | 11 => 30,
2 => if is_leap_year(year) { 29 } else { 28 },
_ => 31,
}
}
impl From<(i32, u32, u32)> for Date {
fn from(ymd: (i32, u32, u32)) -> Self {
Self::new(ymd.0, ymd.1, ymd.2).unwrap()
}
}
impl From<(u32, u32, u32)> for Time {
fn from(hms: (u32, u32, u32)) -> Self {
Self::new(hms.0, hms.1, hms.2)
}
}
impl From<(i32, i32)> for TimeZone {
fn from(hm: (i32, i32)) -> Self {
Self::new(hm.0 as f32 + hm.1 as f32 / 60.0)
}
}
impl From<std::time::SystemTime> for DateTime {
fn from(system_time: std::time::SystemTime) -> Self {
let duration = system_time.duration_since(std::time::UNIX_EPOCH)
.expect("SystemTime before UNIX EPOCH!");
let timestamp = duration.as_secs() as i64;
DateTime::from_timestamp(timestamp, TimeZone::utc())
}
}
impl From<DateTime> for std::time::SystemTime {
fn from(dt: DateTime) -> Self {
let timestamp = dt.timestamp();
std::time::UNIX_EPOCH + std::time::Duration::from_secs(timestamp as u64)
}
}
impl From<(i32, u32, u32, f64)> for DateTime {
fn from(ymdh: (i32, u32, u32, f64)) -> Self {
let (year, month, day, hour) = ymdh;
DateTime::create_utc(year, month, day, hour)
}
}
impl Into<DateTime> for (i32, i32, i32, f64) {
fn into(self) -> DateTime {
let (year, month, day, hour) = self;
DateTime::create_utc(year, month as u32, day as u32, hour)
}
}