use crate::Error;
use std::fmt;
use std::ops::{Add, AddAssign, Sub, SubAssign};
use std::str::FromStr;
use std::time::{Duration, SystemTime};
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Timestamp(jiff::Timestamp);
impl FromStr for Timestamp {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.parse() {
Ok(t) => Ok(Timestamp(t)),
Err(err) => Err(
Error::unexpected(format!("parse '{s}' into timestamp failed")).with_source(err),
),
}
}
}
impl fmt::Display for Timestamp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl Timestamp {
pub fn now() -> Self {
Self(jiff::Timestamp::now())
}
pub fn format_date(self) -> String {
self.0.strftime("%Y%m%d").to_string()
}
pub fn format_iso8601(self) -> String {
self.0.strftime("%Y%m%dT%H%M%SZ").to_string()
}
pub fn format_http_date(self) -> String {
self.0.strftime("%a, %d %b %Y %T GMT").to_string()
}
pub fn format_rfc3339_zulu(self) -> String {
self.0.strftime("%FT%TZ").to_string()
}
pub fn as_second(self) -> i64 {
self.0.as_second()
}
pub fn subsec_nanosecond(self) -> i32 {
self.0.subsec_nanosecond()
}
pub fn as_system_time(self) -> SystemTime {
SystemTime::from(self.0)
}
pub fn from_millisecond(millis: i64) -> crate::Result<Self> {
match jiff::Timestamp::from_millisecond(millis) {
Ok(t) => Ok(Timestamp(t)),
Err(err) => Err(Error::unexpected(format!(
"convert '{millis}' milliseconds into timestamp failed"
))
.with_source(err)),
}
}
pub fn from_second(second: i64) -> crate::Result<Self> {
match jiff::Timestamp::from_second(second) {
Ok(t) => Ok(Timestamp(t)),
Err(err) => Err(Error::unexpected(format!(
"convert '{second}' seconds into timestamp failed"
))
.with_source(err)),
}
}
pub fn parse_rfc2822(s: &str) -> crate::Result<Timestamp> {
match jiff::fmt::rfc2822::parse(s) {
Ok(zoned) => Ok(Timestamp(zoned.timestamp())),
Err(err) => {
Err(Error::unexpected(format!("parse '{s}' into rfc2822 failed")).with_source(err))
}
}
}
pub fn parse_datetime_utc(s: &str) -> crate::Result<Timestamp> {
let dt = s.parse::<jiff::civil::DateTime>().map_err(|err| {
Error::unexpected(format!("parse '{s}' into datetime failed")).with_source(err)
})?;
let ts = jiff::tz::TimeZone::UTC.to_timestamp(dt).map_err(|err| {
Error::unexpected(format!("convert '{s}' into timestamp failed")).with_source(err)
})?;
Ok(Timestamp(ts))
}
}
impl Add<Duration> for Timestamp {
type Output = Timestamp;
fn add(self, rhs: Duration) -> Timestamp {
let ts = self
.0
.checked_add(rhs)
.expect("adding unsigned duration to timestamp overflowed");
Timestamp(ts)
}
}
impl AddAssign<Duration> for Timestamp {
fn add_assign(&mut self, rhs: Duration) {
*self = *self + rhs
}
}
impl Sub<Duration> for Timestamp {
type Output = Timestamp;
fn sub(self, rhs: Duration) -> Timestamp {
let ts = self
.0
.checked_sub(rhs)
.expect("subtracting unsigned duration from timestamp overflowed");
Timestamp(ts)
}
}
impl SubAssign<Duration> for Timestamp {
fn sub_assign(&mut self, rhs: Duration) {
*self = *self - rhs
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_time() -> Timestamp {
Timestamp("2022-03-01T08:12:34Z".parse().unwrap())
}
#[test]
fn test_format_date() {
let t = test_time();
assert_eq!("20220301", t.format_date())
}
#[test]
fn test_format_ios8601() {
let t = test_time();
assert_eq!("20220301T081234Z", t.format_iso8601())
}
#[test]
fn test_format_http_date() {
let t = test_time();
assert_eq!("Tue, 01 Mar 2022 08:12:34 GMT", t.format_http_date())
}
#[test]
fn test_format_rfc3339() {
let t = test_time();
assert_eq!("2022-03-01T08:12:34Z", t.format_rfc3339_zulu())
}
#[test]
fn test_parse_rfc3339() {
let t = test_time();
for v in [
"2022-03-01T08:12:34Z",
"2022-03-01T08:12:34+00:00",
"2022-03-01T08:12:34.00+00:00",
] {
assert_eq!(t, v.parse().expect("must be valid time"));
}
}
}