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]]
name = "proc-macro2"
version = "1.0.78"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.35"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "serde"
version = "1.0.196"
version = "1.0.216"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.196"
version = "1.0.216"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
dependencies = [
"proc-macro2",
"quote",
@ -49,9 +49,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.48"
version = "2.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
dependencies = [
"proc-macro2",
"quote",
@ -60,6 +60,6 @@ dependencies = [
[[package]]
name = "unicode-ident"
version = "1.0.12"
version = "1.0.14"
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"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
serde = ["dep:serde"]
[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 {
crate::{
month::Month, weekday::Weekday, year::Year, zone::TimeZone, SECONDS_PER_DAY,
SECONDS_PER_HOUR, SECONDS_PER_MINUTE,
},
serde::{Deserialize, Serialize},
std::{cmp, fmt},
std::{
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 year: Year,
pub month: Month,
@ -59,39 +93,40 @@ impl DateTime {
seconds
}
#[allow(clippy::missing_panics_doc)]
/// Converts a Unix timestamp to a `DateTime` struct
pub fn from_timestamp(ts: i64) -> Self {
if ts < 0 {
let mut seconds: i64 = 0;
let mut year = Year::new(-1);
while seconds > -year.seconds() {
seconds -= year.seconds();
let mut seconds: i64 = ts;
let mut year = Year::new(1969);
while seconds < -year.seconds() {
seconds += year.seconds();
year = year.previous();
}
let mut month = Some(Month::December);
while month.is_some() {
let m = month.unwrap();
if seconds > m.seconds(year) {
if -seconds < m.seconds(year) {
break;
}
seconds -= m.seconds(year);
seconds += m.seconds(year);
month = m.previous();
}
let month = month.unwrap();
let mut day = month.days(year);
while day > 0 && seconds < -SECONDS_PER_DAY {
seconds -= SECONDS_PER_HOUR;
seconds += SECONDS_PER_DAY;
day -= 1;
}
let mut hour: i8 = 23;
while hour >= 0 && seconds < -SECONDS_PER_HOUR {
seconds -= SECONDS_PER_HOUR;
while hour > 0 && seconds < -SECONDS_PER_HOUR {
seconds += SECONDS_PER_HOUR;
hour -= 1;
}
let hour = hour.try_into().unwrap();
let mut minute: i8 = 60;
while minute >= 0 && seconds < -60 {
seconds -= 60;
let mut minute: i8 = 59;
while minute > 0 && seconds < -60 {
seconds += 60;
minute -= 1;
}
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 {
let ts = self.timestamp_naive();
let days = ts / SECONDS_PER_DAY;
let rem = days % 7;
let mut days = ts / SECONDS_PER_DAY;
// 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()
}
/// Returns a string representing this date and time in human readable format
pub fn display(&self) -> String {
format!(
"{} {} {} {:0>2}:{:0>2}:{:0>2} {} {}",
@ -227,6 +279,28 @@ mod tests {
},
};
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]
@ -268,6 +342,22 @@ mod tests {
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]
fn fmt() {
let mut dt = DateTime::from_timestamp(1706571482);

View file

@ -8,6 +8,8 @@ pub(crate) mod year;
pub(crate) mod zone;
pub mod prelude;
pub use datetime::DateTime;
pub use datetime::Error as DateTimeError;
pub static SECONDS_PER_MINUTE: i64 = 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 {
crate::{year::Year, SECONDS_PER_DAY},
serde::{Deserialize, Serialize},
std::{cmp, error, fmt},
std::{cmp, error, fmt, str},
};
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[repr(u8)]
pub enum Month {
Janurary = 1,
@ -138,7 +139,7 @@ impl fmt::Display for Month {
}
}
impl FromStr for Month {
impl str::FromStr for Month {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {

View file

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

View file

@ -1,8 +1,10 @@
use std::fmt;
#[cfg(feature = "serde")]
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)]
pub enum Weekday {
Thursday,

View file

@ -1,10 +1,13 @@
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use {
crate::SECONDS_PER_DAY,
serde::{Deserialize, Serialize},
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 {
Normal(i32),
Leap(i32),

View file

@ -1,10 +1,13 @@
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use {
crate::SECONDS_PER_HOUR,
serde::{Deserialize, Serialize},
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 {
Positive,
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 {
Offset { sign: Sign, hours: u8, minutes: u8 },
Utc,