blob: d538aa5edf37b1beaaf78297bfdadd66d986c24a [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//! Time related utils.
use crate::Error;
use std::fmt;
use std::ops::{Add, AddAssign, Sub, SubAssign};
use std::str::FromStr;
use std::time::{Duration, SystemTime};
/// An instant in time represented as the number of nanoseconds since the Unix epoch.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Timestamp(jiff::Timestamp);
impl FromStr for Timestamp {
type Err = Error;
/// Parse a timestamp by the default [`DateTimeParser`].
///
/// All of them are valid time:
///
/// - `2022-03-13T07:20:04Z`
/// - `2022-03-01T08:12:34+00:00`
/// - `2022-03-01T08:12:34.00+00:00`
/// - `2022-07-08T02:14:07+02:00[Europe/Paris]`
///
/// [`DateTimeParser`]: jiff::fmt::temporal::DateTimeParser
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 {
/// Create the timestamp of now.
pub fn now() -> Self {
Self(jiff::Timestamp::now())
}
/// Format the timestamp into date: `20220301`
pub fn format_date(self) -> String {
self.0.strftime("%Y%m%d").to_string()
}
/// Format the timestamp into ISO8601: `20220313T072004Z`
pub fn format_iso8601(self) -> String {
self.0.strftime("%Y%m%dT%H%M%SZ").to_string()
}
/// Format the timestamp into http date: `Sun, 06 Nov 1994 08:49:37 GMT`
///
/// ## Note
///
/// HTTP date is slightly different from RFC2822.
///
/// - Timezone is fixed to GMT.
/// - Day must be 2 digit.
pub fn format_http_date(self) -> String {
self.0.strftime("%a, %d %b %Y %T GMT").to_string()
}
/// Format the timestamp into RFC3339 in Zulu: `2022-03-13T07:20:04Z`
pub fn format_rfc3339_zulu(self) -> String {
self.0.strftime("%FT%TZ").to_string()
}
/// Returns this timestamp as a number of seconds since the Unix epoch.
///
/// This only returns the number of whole seconds. That is, if there are
/// any fractional seconds in this timestamp, then they are truncated.
pub fn as_second(self) -> i64 {
self.0.as_second()
}
/// Returns the fractional second component of this timestamp in units of
/// nanoseconds.
///
/// It is guaranteed that this will never return a value that is greater
/// than 1 second (or less than -1 second).
pub fn subsec_nanosecond(self) -> i32 {
self.0.subsec_nanosecond()
}
/// Convert to `SystemTime`.
pub fn as_system_time(self) -> SystemTime {
SystemTime::from(self.0)
}
/// Creates a new instant in time from the number of milliseconds elapsed
/// since the Unix epoch.
///
/// When `millisecond` is negative, it corresponds to an instant in time
/// before the Unix epoch. A smaller number corresponds to an instant in
/// time further into the past.
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)),
}
}
/// Creates a new instant in time from the number of seconds elapsed since
/// the Unix epoch.
///
/// When `second` is negative, it corresponds to an instant in time before
/// the Unix epoch. A smaller number corresponds to an instant in time
/// further into the past.
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)),
}
}
/// Parse a timestamp from RFC2822.
///
/// All of them are valid time:
///
/// - `Sat, 13 Jul 2024 15:09:59 -0400`
/// - `Mon, 15 Aug 2022 16:50:12 GMT`
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))
}
}
}
/// Parse the string format "2023-10-31 21:59:10.000000".
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"));
}
}
}