diff options
| author | mrw1593 <botahamec@outlook.com> | 2022-03-07 09:56:46 -0500 |
|---|---|---|
| committer | mrw1593 <botahamec@outlook.com> | 2022-03-07 09:56:46 -0500 |
| commit | 43da205d0c486a082c380a1258229a055e5767ba (patch) | |
| tree | 2852943fb41316a47e9aa80c49a5282800c15574 /src | |
| parent | 0fb870509821eb6f8158a9ee3cc02e6a0f951c86 (diff) | |
Implemented proper second addition
Diffstat (limited to 'src')
| -rw-r--r-- | src/date.rs | 10 | ||||
| -rw-r--r-- | src/datetime.rs | 42 | ||||
| -rw-r--r-- | src/lib.rs | 2 | ||||
| -rw-r--r-- | src/tai.rs | 3 | ||||
| -rw-r--r-- | src/time.rs | 17 | ||||
| -rw-r--r-- | src/timestamp.rs | 74 |
6 files changed, 132 insertions, 16 deletions
diff --git a/src/date.rs b/src/date.rs index 194fd02..fc849f1 100644 --- a/src/date.rs +++ b/src/date.rs @@ -17,6 +17,9 @@ impl Date { /// The latest date which can be represented pub const MAX: Self = unsafe { Self::from_ymd_unchecked(Year::MAX, Month::December, 31) }; + pub const UNIX_EPOCH: Self = + unsafe { Self::from_ymd_unchecked(Year::from_i16(1970), Month::January, 1) }; + // TODO validated from_calendar_date /// Creates a date without checking to make sure that it's valid. @@ -34,29 +37,35 @@ impl Date { /// # Safety /// /// This function results in undefined behavior if the given date is not a real date + #[must_use] pub const unsafe fn from_ymd_unchecked(year: Year, month: Month, day: u8) -> Self { Self { year, month, day } } // TODO docs + #[must_use] pub const fn year(self) -> Year { self.year } + #[must_use] pub const fn month(self) -> Month { self.month } + #[must_use] pub const fn day(self) -> u8 { self.day } + #[must_use] pub const fn is_leap_year(self) -> bool { self.year.is_leap_year() } // TODO handle BCE properly + #[must_use] pub const fn days_after_common_era(self) -> isize { let year = self.year.wrapping_sub(1); let leap_years = (year.as_i16() / 4 - year.as_i16() / 100 + year.as_i16() / 400) as isize; @@ -66,6 +75,7 @@ impl Date { } // TODO test + #[must_use] pub const fn from_days_after_common_era(days: isize) -> Self { let era = days / 146097; // an era is a period of 400 year let day_of_era = days - (era * 146097); diff --git a/src/datetime.rs b/src/datetime.rs index df4e637..1cc77b5 100644 --- a/src/datetime.rs +++ b/src/datetime.rs @@ -1,6 +1,6 @@ use crate::{ timezone::{Utc, UtcOffset}, - Date, Month, Time, TimeZone, Year, + Date, Month, Time, TimeZone, UnixTimestamp, Year, }; use core::{cmp::Ordering, fmt::Display, hash::Hash}; @@ -94,23 +94,21 @@ impl NaiveDateTime { } #[must_use] - pub fn add_seconds(self, seconds: isize) -> Self { - let time = self.time.add_seconds(seconds); + pub fn add_seconds_overflowing(self, seconds: i64) -> (Self, bool) { + let timestamp: UnixTimestamp = self.into(); + let (timestamp, overflow) = timestamp.add_seconds_overflowing(seconds); + let datetime: NaiveDateTime = timestamp.into(); - Self { - date: self.date, - time, - } + (datetime, overflow) } #[must_use] - pub fn add_nanoseconds(self, nanoseconds: isize) -> Self { - let time = self.time.add_nanoseconds(nanoseconds); + pub fn add_nanoseconds_overflowing(self, nanoseconds: i64) -> (Self, bool) { + let timestamp: UnixTimestamp = self.into(); + let (timestamp, overflow) = timestamp.add_nanoseconds_overflowing(nanoseconds); + let datetime: NaiveDateTime = timestamp.into(); - Self { - date: self.date, - time, - } + (datetime, overflow) } } @@ -169,8 +167,6 @@ impl<Tz: TimeZone> Ord for DateTime<Tz> { } } -// TODO addition - impl Display for NaiveDateTime { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{} {}", self.date, self.time) @@ -182,3 +178,19 @@ impl<Tz: TimeZone> Display for DateTime<Tz> { write!(f, "{} {}", self.utc_datetime, self.timezone) } } + +impl From<UnixTimestamp> for NaiveDateTime { + fn from(timestamp: UnixTimestamp) -> Self { + const UNIX_EPOCH_DAYS_AFTER_CE: isize = Date::UNIX_EPOCH.days_after_common_era(); + let days_after_unix_epoch = timestamp.seconds_since_unix_epoch() / 86_400; + let days_after_ce = days_after_unix_epoch + UNIX_EPOCH_DAYS_AFTER_CE as i64; + let date = Date::from_days_after_common_era(days_after_ce as isize); + let seconds_after_midnight = timestamp.seconds_since_unix_epoch() % 86_400; + let nanoseconds = timestamp.nanosecond(); + let time = Time::MIDNIGHT + .add_seconds(seconds_after_midnight as isize) + .add_nanoseconds(nanoseconds as isize); + + Self::new(date, time) + } +} @@ -8,6 +8,7 @@ mod datetime; mod month; mod tai; mod time; +mod timestamp; pub mod timezone; mod weekday; mod year; @@ -17,6 +18,7 @@ pub use datetime::DateTime; pub use datetime::NaiveDateTime; pub use month::Month; pub use time::Time; +pub use timestamp::UnixTimestamp; pub use timezone::TimeZone; pub use weekday::Weekday; pub use year::Year; @@ -100,7 +100,8 @@ impl TimeZone for Tai { // keep checking until there is no longer a change in the total leap seconds while past_leap_seconds != prev_pls { prev_pls = past_leap_seconds; - let ndt = date_time.add_seconds(past_leap_seconds as isize); + // TODO think about this discard + let (ndt, _) = date_time.add_seconds_overflowing(past_leap_seconds as i64); let utc_dt = DateTime::from_utc(ndt, Utc); past_leap_seconds = leap_seconds.leap_seconds_before_inclusive(utc_dt); } diff --git a/src/time.rs b/src/time.rs index 49dba51..0f6091e 100644 --- a/src/time.rs +++ b/src/time.rs @@ -352,6 +352,23 @@ impl Time { self.add_nanoseconds_checked(nanoseconds) .unwrap_or_else(|| panic!("Overflow when adding {nanoseconds} nanoseconds to {self}")) } + + /// Gets the number of seconds since midnight + #[must_use] + pub fn seconds_from_midnight(self) -> u32 { + self.hour as u32 * 3_600_000_000 + + self.minute as u32 * 60_000_000 + + self.second as u32 * 1_000_000 + } + + /// Gets the number of nanoseconds since midnight + #[must_use] + pub fn nanoseconds_from_midnight(self) -> u64 { + self.hour as u64 * 3_600_000_000_000 + + self.minute as u64 * 60_000_000_000 + + self.second as u64 * 1_000_000_000 + + self.nanosecond as u64 + } } impl PartialOrd for Time { diff --git a/src/timestamp.rs b/src/timestamp.rs new file mode 100644 index 0000000..6798792 --- /dev/null +++ b/src/timestamp.rs @@ -0,0 +1,74 @@ +use crate::{Date, NaiveDateTime}; + +#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)] +pub struct UnixTimestamp { + seconds: i64, + nanoseconds: u32, +} + +impl UnixTimestamp { + pub const fn new(seconds: i64, nanoseconds: u32) -> Self { + Self { + seconds, + nanoseconds, + } + } + + pub const fn seconds_since_unix_epoch(self) -> i64 { + self.seconds + } + + pub const fn nanosecond(self) -> u32 { + self.nanoseconds + } + + pub const fn add_seconds_overflowing(self, seconds: i64) -> (Self, bool) { + // TODO overflowing goes first + let (seconds, overflowing) = self.seconds.overflowing_add(seconds as i64); + + let timestamp = Self::new(seconds, self.nanoseconds); + (timestamp, overflowing) + } + + pub const fn add_nanoseconds_overflowing(self, nanoseconds: i64) -> (Self, bool) { + let total_nanos = (self.nanoseconds as i64 + nanoseconds) % 1_000_000_000; + let total_nanos = total_nanos + (1_000_000_000 * total_nanos.is_negative() as i64); + let added_seconds = (self.nanoseconds as i64 + nanoseconds) / 1_000_000_000; + let total_seconds = (self.seconds as i64 + added_seconds) % 60; + let overflow = 0 > total_seconds; + let total_seconds = total_seconds + (60 * total_seconds.is_negative() as i64); + + let timestamp = Self::new(total_seconds, total_nanos as u32); + (timestamp, overflow) + } +} + +impl From<NaiveDateTime> for UnixTimestamp { + fn from(ndt: NaiveDateTime) -> Self { + const UNIX_EPOCH_DAYS: isize = Date::UNIX_EPOCH.days_after_common_era(); + // TODO don't require the .date() + let days = (ndt.date().days_after_common_era() - UNIX_EPOCH_DAYS) as i64; + let seconds = days * 86_400 + ndt.time().seconds_from_midnight() as i64; + let nanoseconds = ndt.nanosecond(); + + Self::new(seconds, nanoseconds) + } +} + +impl PartialOrd for UnixTimestamp { + fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> { + match self.seconds.partial_cmp(&other.seconds) { + Some(core::cmp::Ordering::Equal) => self.nanoseconds.partial_cmp(&other.nanoseconds), + ord => ord, + } + } +} + +impl Ord for UnixTimestamp { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + match self.seconds.cmp(&other.seconds) { + core::cmp::Ordering::Equal => self.nanoseconds.cmp(&other.nanoseconds), + ord => ord, + } + } +} |
