summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBotahamec <botahamec@outlook.com>2022-03-03 20:18:06 -0500
committerBotahamec <botahamec@outlook.com>2022-03-03 20:18:06 -0500
commit0fb870509821eb6f8158a9ee3cc02e6a0f951c86 (patch)
tree74cbd12648e03e39abd50a1c67dafce3526f7870
parent5a4a0a5441bfd3fa590d45278e712ce24c5fa5df (diff)
Started with leap seconds
-rw-r--r--Cargo.toml3
-rw-r--r--src/datetime.rs20
-rw-r--r--src/lib.rs2
-rw-r--r--src/tai.rs144
-rw-r--r--src/utc.rs1
5 files changed, 169 insertions, 1 deletions
diff --git a/Cargo.toml b/Cargo.toml
index f86b4ff..b614093 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,4 +8,5 @@ license = "Unlicense"
[dependencies]
derive_more = "0.99"
-thiserror = "1" \ No newline at end of file
+thiserror = "1"
+parking_lot = "0.12" \ No newline at end of file
diff --git a/src/datetime.rs b/src/datetime.rs
index f856e74..df4e637 100644
--- a/src/datetime.rs
+++ b/src/datetime.rs
@@ -92,6 +92,26 @@ impl NaiveDateTime {
pub const fn nanosecond(self) -> u32 {
self.time.nanosecond()
}
+
+ #[must_use]
+ pub fn add_seconds(self, seconds: isize) -> Self {
+ let time = self.time.add_seconds(seconds);
+
+ Self {
+ date: self.date,
+ time,
+ }
+ }
+
+ #[must_use]
+ pub fn add_nanoseconds(self, nanoseconds: isize) -> Self {
+ let time = self.time.add_nanoseconds(nanoseconds);
+
+ Self {
+ date: self.date,
+ time,
+ }
+ }
}
impl PartialOrd for NaiveDateTime {
diff --git a/src/lib.rs b/src/lib.rs
index ebc132d..d83185d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,10 +1,12 @@
#![doc = include_str!("../README.md")]
// TODO must uses
+// TODO serde support
mod date;
mod datetime;
mod month;
+mod tai;
mod time;
pub mod timezone;
mod weekday;
diff --git a/src/tai.rs b/src/tai.rs
new file mode 100644
index 0000000..147b51b
--- /dev/null
+++ b/src/tai.rs
@@ -0,0 +1,144 @@
+use core::cmp::Ordering;
+use core::fmt::Display;
+
+use parking_lot::{const_rwlock, RwLock};
+use thiserror::Error;
+
+use crate::{
+ timezone::{Utc, UtcOffset},
+ Date, DateTime, NaiveDateTime, Time, TimeZone,
+};
+
+static GLOBAL_LEAP_SECONDS: RwLock<LeapSeconds> = const_rwlock(LeapSeconds::empty());
+
+struct LeapSeconds(Vec<DateTime<Utc>>);
+
+impl LeapSeconds {
+ // TODO docs
+
+ const fn empty() -> Self {
+ Self(Vec::new())
+ }
+
+ fn leap_seconds_before_inclusive(&self, date_time: DateTime<Utc>) -> usize {
+ let mut seconds = 0;
+ for leap_second in &self.0 {
+ if leap_second > &date_time {
+ break;
+ }
+ seconds += 1;
+ }
+
+ seconds
+ }
+
+ fn add_leap_second(&mut self, day: Date) {
+ let utc_datetime = NaiveDateTime::new(day, Time::MIDNIGHT);
+ let exact_time = DateTime::from_utc(utc_datetime, Utc);
+
+ let mut i = 0;
+ while i < self.0.len() {
+ match self.0[i].cmp(&exact_time) {
+ Ordering::Greater => break, // insert the new leap second here
+ Ordering::Equal => return, // it's already here, so don't add it again
+ Ordering::Less => i += 1, // check the next leap second
+ }
+ }
+
+ self.0.insert(i, exact_time);
+ }
+}
+
+pub fn add_leap_second(day: Date) {
+ let mut leap_seconds = GLOBAL_LEAP_SECONDS.write();
+ leap_seconds.add_leap_second(day);
+}
+
+#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+pub struct Tai;
+
+#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Error)]
+#[error(
+ "TAI cannot represent leap seconds, so a leap second cannot be converted to TAI. Recieved: {}",
+ given_dt
+)]
+pub struct UnexpectedLeapSecond {
+ given_dt: NaiveDateTime,
+}
+
+impl Display for Tai {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ write!(f, "TAI")
+ }
+}
+
+impl TimeZone for Tai {
+ type Err = UnexpectedLeapSecond;
+
+ fn utc_offset(&self, date_time: DateTime<Utc>) -> UtcOffset {
+ let leap_seconds = GLOBAL_LEAP_SECONDS.read();
+ let past_leap_seconds = leap_seconds.leap_seconds_before_inclusive(date_time);
+ UtcOffset::from_seconds(-(past_leap_seconds as isize + 10))
+ }
+
+ // TODO optimize
+ fn offset_from_local_time(&self, date_time: NaiveDateTime) -> Result<UtcOffset, Self::Err> {
+ // TAI times cannot have leap seconds
+ if date_time.second() == 60 {
+ return Err(UnexpectedLeapSecond {
+ given_dt: date_time,
+ });
+ }
+
+ // calculate the number of seconds that have passed since date_time in UTC
+ let leap_seconds = GLOBAL_LEAP_SECONDS.read();
+ let utc_dt = DateTime::from_utc(date_time, Utc);
+ let mut past_leap_seconds = leap_seconds.leap_seconds_before_inclusive(utc_dt);
+ let mut prev_pls = 0; // use this to see if the number of leap seconds has been updated
+
+ // check if any leap seconds were found because of this calculation
+ // 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);
+ let utc_dt = DateTime::from_utc(ndt, Utc);
+ past_leap_seconds = leap_seconds.leap_seconds_before_inclusive(utc_dt);
+ }
+
+ Ok(UtcOffset::from_seconds(-(past_leap_seconds as isize + 10)))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::{Date, Month, Time};
+
+ use super::*;
+
+ #[test]
+ fn test_conversion_no_leap_seconds() {
+ let offset = unsafe {
+ Tai.offset_from_local_time(NaiveDateTime::new(
+ Date::from_ymd_unchecked(2000.into(), Month::January, 1),
+ Time::from_hms_unchecked(0, 0, 0),
+ ))
+ .unwrap()
+ };
+
+ assert_eq!(offset, UtcOffset::from_seconds(-10))
+ }
+
+ #[test]
+ fn test_conversion_one_leap_second() {
+ add_leap_second(unsafe { Date::from_ymd_unchecked(2000.into(), Month::January, 1) });
+ let offset = unsafe {
+ Tai.offset_from_local_time(NaiveDateTime::new(
+ Date::from_ymd_unchecked(2000.into(), Month::January, 2),
+ Time::from_hms_unchecked(0, 0, 0),
+ ))
+ .unwrap()
+ };
+
+ assert_eq!(offset, UtcOffset::from_seconds(-11))
+ }
+}
diff --git a/src/utc.rs b/src/utc.rs
new file mode 100644
index 0000000..e2e5aad
--- /dev/null
+++ b/src/utc.rs
@@ -0,0 +1 @@
+// TODO move stuff around