Compare commits

..

10 commits

10 changed files with 166 additions and 44 deletions

24
Cargo.lock generated
View file

@ -11,36 +11,36 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.78" version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.35" version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.196" version = "1.0.216"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.196" version = "1.0.216"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -49,9 +49,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.48" version = "2.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -60,6 +60,6 @@ dependencies = [
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.12" version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"

View file

@ -4,6 +4,8 @@ version = "0.1.0"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
serde = ["dep:serde"]
[dependencies] [dependencies]
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"], optional = true }

19
README.md Normal file
View file

@ -0,0 +1,19 @@
Contents
========
- [Summary](#summary)
- [Usage](#usage)
## Summary
Epoch is a small library for converting Unix timestamps to human readable datetime
and vice versa. It includes functionality for displaying a DateTime as a human
readable string, parsing from string, and comparison for equality or ordering.
This crate is ideal when you want a lighter and simpler date/time library than the
excellent and very complete [chrono](https://crates.io/crates/chrono), which is
likely overkill for many use cases.
## Usage
Add epoch to your Cargo.toml
```Toml
epoch = { git = "http://git.hitchhiker-linux.org/jeang3nie/epoch-rs.git" }
```

View file

@ -1,13 +1,47 @@
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use { use {
crate::{ crate::{
month::Month, weekday::Weekday, year::Year, zone::TimeZone, SECONDS_PER_DAY, month::Month, weekday::Weekday, year::Year, zone::TimeZone, SECONDS_PER_DAY,
SECONDS_PER_HOUR, SECONDS_PER_MINUTE, SECONDS_PER_HOUR, SECONDS_PER_MINUTE,
}, },
serde::{Deserialize, Serialize}, std::{
std::{cmp, fmt}, cmp, fmt,
num::TryFromIntError,
time::{SystemTime, SystemTimeError, UNIX_EPOCH},
},
}; };
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] #[derive(Debug)]
pub enum Error {
System(SystemTimeError),
RangeError,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::System(e) => write!(f, "{e}"),
Self::RangeError => write!(f, "self:?"),
}
}
}
impl From<SystemTimeError> for Error {
fn from(value: SystemTimeError) -> Self {
Self::System(value)
}
}
impl From<TryFromIntError> for Error {
fn from(_value: TryFromIntError) -> Self {
Self::RangeError
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct DateTime { pub struct DateTime {
pub year: Year, pub year: Year,
pub month: Month, pub month: Month,
@ -59,39 +93,40 @@ impl DateTime {
seconds seconds
} }
#[allow(clippy::missing_panics_doc)]
/// Converts a Unix timestamp to a `DateTime` struct /// Converts a Unix timestamp to a `DateTime` struct
pub fn from_timestamp(ts: i64) -> Self { pub fn from_timestamp(ts: i64) -> Self {
if ts < 0 { if ts < 0 {
let mut seconds: i64 = 0; let mut seconds: i64 = ts;
let mut year = Year::new(-1); let mut year = Year::new(1969);
while seconds > -year.seconds() { while seconds < -year.seconds() {
seconds -= year.seconds(); seconds += year.seconds();
year = year.previous(); year = year.previous();
} }
let mut month = Some(Month::December); let mut month = Some(Month::December);
while month.is_some() { while month.is_some() {
let m = month.unwrap(); let m = month.unwrap();
if seconds > m.seconds(year) { if -seconds < m.seconds(year) {
break; break;
} }
seconds -= m.seconds(year); seconds += m.seconds(year);
month = m.previous(); month = m.previous();
} }
let month = month.unwrap(); let month = month.unwrap();
let mut day = month.days(year); let mut day = month.days(year);
while day > 0 && seconds < -SECONDS_PER_DAY { while day > 0 && seconds < -SECONDS_PER_DAY {
seconds -= SECONDS_PER_HOUR; seconds += SECONDS_PER_DAY;
day -= 1; day -= 1;
} }
let mut hour: i8 = 23; let mut hour: i8 = 23;
while hour >= 0 && seconds < -SECONDS_PER_HOUR { while hour > 0 && seconds < -SECONDS_PER_HOUR {
seconds -= SECONDS_PER_HOUR; seconds += SECONDS_PER_HOUR;
hour -= 1; hour -= 1;
} }
let hour = hour.try_into().unwrap(); let hour = hour.try_into().unwrap();
let mut minute: i8 = 60; let mut minute: i8 = 59;
while minute >= 0 && seconds < -60 { while minute > 0 && seconds < -60 {
seconds -= 60; seconds += 60;
minute -= 1; minute -= 1;
} }
let minute = minute.try_into().unwrap(); let minute = minute.try_into().unwrap();
@ -145,13 +180,30 @@ impl DateTime {
} }
} }
#[allow(clippy::missing_panics_doc)]
/// Creates a `DateTime` from the system time
pub fn now() -> Result<Self, Error> {
let now = SystemTime::now().duration_since(UNIX_EPOCH)?;
Ok(Self::from_timestamp(i64::try_from(now.as_secs())?))
}
#[allow(clippy::missing_panics_doc)]
/// Gets the day of the week for this `DateTime`
pub fn weekday(&self) -> Weekday { pub fn weekday(&self) -> Weekday {
let ts = self.timestamp_naive(); let ts = self.timestamp_naive();
let days = ts / SECONDS_PER_DAY; let mut days = ts / SECONDS_PER_DAY;
let rem = days % 7; // For negative timestamps this number is actually the following day
if ts < 0 {
days -= 1;
}
// Rusts `%` operator is modulo, not modulus. We have to use the
// operation which will gove the correct answer for either positive
// or negative remainders
let rem = days.rem_euclid(7);
rem.try_into().unwrap() rem.try_into().unwrap()
} }
/// Returns a string representing this date and time in human readable format
pub fn display(&self) -> String { pub fn display(&self) -> String {
format!( format!(
"{} {} {} {:0>2}:{:0>2}:{:0>2} {} {}", "{} {} {} {:0>2}:{:0>2}:{:0>2} {} {}",
@ -227,6 +279,28 @@ mod tests {
}, },
}; };
assert_eq!(dt.weekday(), Weekday::Saturday); assert_eq!(dt.weekday(), Weekday::Saturday);
dt = DateTime {
year: Year::new(1965),
month: Month::February,
day: 9,
hour: 4,
minute: 4,
second: 37,
zone: TimeZone::Utc,
};
assert_eq!(dt.weekday(), Weekday::Tuesday);
dt.day -= 1;
assert_eq!(dt.weekday(), Weekday::Monday);
dt.day -= 1;
assert_eq!(dt.weekday(), Weekday::Sunday);
dt.day -= 1;
assert_eq!(dt.weekday(), Weekday::Saturday);
dt.day -= 1;
assert_eq!(dt.weekday(), Weekday::Friday);
dt.day -= 1;
assert_eq!(dt.weekday(), Weekday::Thursday);
dt.day -= 1;
assert_eq!(dt.weekday(), Weekday::Wednesday);
} }
#[test] #[test]
@ -268,6 +342,22 @@ mod tests {
assert_eq!(dt, DateTime::from_timestamp(ts)); assert_eq!(dt, DateTime::from_timestamp(ts));
} }
#[test]
fn ts_negative() {
let ts: i64 = -154382123;
let dt = DateTime {
year: Year::new(1965),
month: Month::February,
day: 9,
hour: 4,
minute: 4,
second: 37,
zone: TimeZone::Utc,
};
assert_eq!(dt.timestamp(), ts);
assert_eq!(dt, DateTime::from_timestamp(ts));
}
#[test] #[test]
fn fmt() { fn fmt() {
let mut dt = DateTime::from_timestamp(1706571482); let mut dt = DateTime::from_timestamp(1706571482);

View file

@ -8,6 +8,8 @@ pub(crate) mod year;
pub(crate) mod zone; pub(crate) mod zone;
pub mod prelude; pub mod prelude;
pub use datetime::DateTime;
pub use datetime::Error as DateTimeError;
pub static SECONDS_PER_MINUTE: i64 = 60; pub static SECONDS_PER_MINUTE: i64 = 60;
pub static SECONDS_PER_HOUR: i64 = 60 * 60; pub static SECONDS_PER_HOUR: i64 = 60 * 60;

View file

@ -1,12 +1,13 @@
use std::str::FromStr; #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use { use {
crate::{year::Year, SECONDS_PER_DAY}, crate::{year::Year, SECONDS_PER_DAY},
serde::{Deserialize, Serialize}, std::{cmp, error, fmt, str},
std::{cmp, error, fmt},
}; };
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[repr(u8)] #[repr(u8)]
pub enum Month { pub enum Month {
Janurary = 1, Janurary = 1,
@ -138,7 +139,7 @@ impl fmt::Display for Month {
} }
} }
impl FromStr for Month { impl str::FromStr for Month {
type Err = Error; type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {

View file

@ -1,5 +1,4 @@
pub use crate::{ pub use crate::{
datetime::DateTime,
month::{Error as MonthError, Month}, month::{Error as MonthError, Month},
weekday::Weekday, weekday::Weekday,
year::Year, year::Year,

View file

@ -1,8 +1,10 @@
use std::fmt; #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] use std::fmt;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[repr(u8)] #[repr(u8)]
pub enum Weekday { pub enum Weekday {
Thursday, Thursday,

View file

@ -1,10 +1,13 @@
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use { use {
crate::SECONDS_PER_DAY, crate::SECONDS_PER_DAY,
serde::{Deserialize, Serialize},
std::{cmp, fmt, num::ParseIntError, str::FromStr}, std::{cmp, fmt, num::ParseIntError, str::FromStr},
}; };
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum Year { pub enum Year {
Normal(i32), Normal(i32),
Leap(i32), Leap(i32),

View file

@ -1,10 +1,13 @@
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use { use {
crate::SECONDS_PER_HOUR, crate::SECONDS_PER_HOUR,
serde::{Deserialize, Serialize},
std::{error, fmt, str::FromStr}, std::{error, fmt, str::FromStr},
}; };
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum Sign { pub enum Sign {
Positive, Positive,
Negative, Negative,
@ -23,7 +26,8 @@ impl fmt::Display for Sign {
} }
} }
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum TimeZone { pub enum TimeZone {
Offset { sign: Sign, hours: u8, minutes: u8 }, Offset { sign: Sign, hours: u8, minutes: u8 },
Utc, Utc,