summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authormrw1593 <botahamec@outlook.com>2022-03-07 09:56:46 -0500
committermrw1593 <botahamec@outlook.com>2022-03-07 09:56:46 -0500
commit43da205d0c486a082c380a1258229a055e5767ba (patch)
tree2852943fb41316a47e9aa80c49a5282800c15574 /src
parent0fb870509821eb6f8158a9ee3cc02e6a0f951c86 (diff)
Implemented proper second addition
Diffstat (limited to 'src')
-rw-r--r--src/date.rs10
-rw-r--r--src/datetime.rs42
-rw-r--r--src/lib.rs2
-rw-r--r--src/tai.rs3
-rw-r--r--src/time.rs17
-rw-r--r--src/timestamp.rs74
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)
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index d83185d..3286f8c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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;
diff --git a/src/tai.rs b/src/tai.rs
index 147b51b..fb9bfc8 100644
--- a/src/tai.rs
+++ b/src/tai.rs
@@ -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,
+ }
+ }
+}