mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-01-05 21:02:58 +00:00
918 lines
30 KiB
Rust
918 lines
30 KiB
Rust
// Copyright 2023 Greptime Team
|
|
//
|
|
// Licensed 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.
|
|
|
|
use core::default::Default;
|
|
use std::cmp::Ordering;
|
|
use std::fmt::{Display, Formatter};
|
|
use std::hash::{Hash, Hasher};
|
|
use std::str::FromStr;
|
|
use std::time::Duration;
|
|
|
|
use chrono::offset::Local;
|
|
use chrono::{DateTime, LocalResult, NaiveDateTime, TimeZone, Utc};
|
|
use serde::{Deserialize, Serialize};
|
|
use snafu::{OptionExt, ResultExt};
|
|
|
|
use crate::error;
|
|
use crate::error::{ArithmeticOverflowSnafu, Error, ParseTimestampSnafu, TimestampOverflowSnafu};
|
|
use crate::util::div_ceil;
|
|
|
|
#[derive(Debug, Clone, Default, Copy, Serialize, Deserialize)]
|
|
pub struct Timestamp {
|
|
value: i64,
|
|
unit: TimeUnit,
|
|
}
|
|
|
|
impl Timestamp {
|
|
/// Creates current timestamp in millisecond.
|
|
pub fn current_millis() -> Self {
|
|
Self {
|
|
value: crate::util::current_time_millis(),
|
|
unit: TimeUnit::Millisecond,
|
|
}
|
|
}
|
|
|
|
/// Subtracts a duration from timestamp.
|
|
/// # Note
|
|
/// The result time unit remains unchanged even if `duration` has a different unit with `self`.
|
|
/// For example, a timestamp with value 1 and time unit second, subtracted by 1 millisecond
|
|
/// and the result is still 1 second.
|
|
pub fn sub(&self, duration: Duration) -> error::Result<Self> {
|
|
let duration: i64 = match self.unit {
|
|
TimeUnit::Second => {
|
|
i64::try_from(duration.as_secs()).context(TimestampOverflowSnafu)?
|
|
}
|
|
TimeUnit::Millisecond => {
|
|
i64::try_from(duration.as_millis()).context(TimestampOverflowSnafu)?
|
|
}
|
|
TimeUnit::Microsecond => {
|
|
i64::try_from(duration.as_micros()).context(TimestampOverflowSnafu)?
|
|
}
|
|
TimeUnit::Nanosecond => {
|
|
i64::try_from(duration.as_nanos()).context(TimestampOverflowSnafu)?
|
|
}
|
|
};
|
|
|
|
let value = self
|
|
.value
|
|
.checked_sub(duration)
|
|
.with_context(|| ArithmeticOverflowSnafu {
|
|
msg: format!(
|
|
"Try to subtract timestamp: {:?} with duration: {:?}",
|
|
self, duration
|
|
),
|
|
})?;
|
|
Ok(Timestamp {
|
|
value,
|
|
unit: self.unit,
|
|
})
|
|
}
|
|
|
|
pub fn new(value: i64, unit: TimeUnit) -> Self {
|
|
Self { unit, value }
|
|
}
|
|
|
|
pub fn new_second(value: i64) -> Self {
|
|
Self {
|
|
value,
|
|
unit: TimeUnit::Second,
|
|
}
|
|
}
|
|
|
|
pub fn new_millisecond(value: i64) -> Self {
|
|
Self {
|
|
value,
|
|
unit: TimeUnit::Millisecond,
|
|
}
|
|
}
|
|
|
|
pub fn new_microsecond(value: i64) -> Self {
|
|
Self {
|
|
value,
|
|
unit: TimeUnit::Microsecond,
|
|
}
|
|
}
|
|
|
|
pub fn new_nanosecond(value: i64) -> Self {
|
|
Self {
|
|
value,
|
|
unit: TimeUnit::Nanosecond,
|
|
}
|
|
}
|
|
|
|
pub fn unit(&self) -> TimeUnit {
|
|
self.unit
|
|
}
|
|
|
|
pub fn value(&self) -> i64 {
|
|
self.value
|
|
}
|
|
|
|
/// Convert a timestamp to given time unit.
|
|
/// Conversion from a timestamp with smaller unit to a larger unit may cause rounding error.
|
|
/// Return `None` if conversion causes overflow.
|
|
pub fn convert_to(&self, unit: TimeUnit) -> Option<Timestamp> {
|
|
if self.unit().factor() >= unit.factor() {
|
|
let mul = self.unit().factor() / unit.factor();
|
|
let value = self.value.checked_mul(mul as i64)?;
|
|
Some(Timestamp::new(value, unit))
|
|
} else {
|
|
let mul = unit.factor() / self.unit().factor();
|
|
Some(Timestamp::new(self.value.div_euclid(mul as i64), unit))
|
|
}
|
|
}
|
|
|
|
/// Convert a timestamp to given time unit.
|
|
/// Conversion from a timestamp with smaller unit to a larger unit will round the value
|
|
/// to ceil (positive infinity).
|
|
/// Return `None` if conversion causes overflow.
|
|
pub fn convert_to_ceil(&self, unit: TimeUnit) -> Option<Timestamp> {
|
|
if self.unit().factor() >= unit.factor() {
|
|
let mul = self.unit().factor() / unit.factor();
|
|
let value = self.value.checked_mul(mul as i64)?;
|
|
Some(Timestamp::new(value, unit))
|
|
} else {
|
|
let mul = unit.factor() / self.unit().factor();
|
|
Some(Timestamp::new(div_ceil(self.value, mul as i64), unit))
|
|
}
|
|
}
|
|
|
|
/// Split a [Timestamp] into seconds part and nanoseconds part.
|
|
/// Notice the seconds part of split result is always rounded down to floor.
|
|
fn split(&self) -> (i64, u32) {
|
|
let sec_mul = (TimeUnit::Second.factor() / self.unit.factor()) as i64;
|
|
let nsec_mul = (self.unit.factor() / TimeUnit::Nanosecond.factor()) as i64;
|
|
|
|
let sec_div = self.value.div_euclid(sec_mul);
|
|
let sec_mod = self.value.rem_euclid(sec_mul);
|
|
// safety: the max possible value of `sec_mod` is 999,999,999
|
|
let nsec = u32::try_from(sec_mod * nsec_mul).unwrap();
|
|
(sec_div, nsec)
|
|
}
|
|
|
|
/// Format timestamp to ISO8601 string. If the timestamp exceeds what chrono timestamp can
|
|
/// represent, this function simply print the timestamp unit and value in plain string.
|
|
pub fn to_iso8601_string(&self) -> String {
|
|
self.as_formatted_string("%Y-%m-%d %H:%M:%S%.f%z")
|
|
}
|
|
|
|
pub fn to_local_string(&self) -> String {
|
|
self.as_formatted_string("%Y-%m-%d %H:%M:%S%.f")
|
|
}
|
|
|
|
fn as_formatted_string(self, pattern: &str) -> String {
|
|
if let Some(v) = self.to_chrono_datetime() {
|
|
let local = Local {};
|
|
format!("{}", local.from_utc_datetime(&v).format(pattern))
|
|
} else {
|
|
format!("[Timestamp{}: {}]", self.unit, self.value)
|
|
}
|
|
}
|
|
|
|
pub fn to_chrono_datetime(&self) -> Option<NaiveDateTime> {
|
|
let (sec, nsec) = self.split();
|
|
NaiveDateTime::from_timestamp_opt(sec, nsec)
|
|
}
|
|
}
|
|
|
|
impl FromStr for Timestamp {
|
|
type Err = Error;
|
|
|
|
/// Accepts a string in RFC3339 / ISO8601 standard format and some variants and converts it to a nanosecond precision timestamp.
|
|
/// This code is copied from [arrow-datafusion](https://github.com/apache/arrow-datafusion/blob/arrow2/datafusion-physical-expr/src/arrow_temporal_util.rs#L71)
|
|
/// with some bugfixes.
|
|
/// Supported format:
|
|
/// - `2022-09-20T14:16:43.012345Z` (Zulu timezone)
|
|
/// - `2022-09-20T14:16:43.012345+08:00` (Explicit offset)
|
|
/// - `2022-09-20T14:16:43.012345` (local timezone, with T)
|
|
/// - `2022-09-20T14:16:43` (local timezone, no fractional seconds, with T)
|
|
/// - `2022-09-20 14:16:43.012345Z` (Zulu timezone, without T)
|
|
/// - `2022-09-20 14:16:43` (local timezone, without T)
|
|
/// - `2022-09-20 14:16:43.012345` (local timezone, without T)
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
// RFC3339 timestamp (with a T)
|
|
if let Ok(ts) = DateTime::parse_from_rfc3339(s) {
|
|
return Ok(Timestamp::new(ts.timestamp_nanos(), TimeUnit::Nanosecond));
|
|
}
|
|
if let Ok(ts) = DateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S%.f%:z") {
|
|
return Ok(Timestamp::new(ts.timestamp_nanos(), TimeUnit::Nanosecond));
|
|
}
|
|
if let Ok(ts) = Utc.datetime_from_str(s, "%Y-%m-%d %H:%M:%S%.fZ") {
|
|
return Ok(Timestamp::new(ts.timestamp_nanos(), TimeUnit::Nanosecond));
|
|
}
|
|
|
|
if let Ok(ts) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S") {
|
|
return naive_datetime_to_timestamp(s, ts);
|
|
}
|
|
|
|
if let Ok(ts) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.f") {
|
|
return naive_datetime_to_timestamp(s, ts);
|
|
}
|
|
|
|
if let Ok(ts) = NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S") {
|
|
return naive_datetime_to_timestamp(s, ts);
|
|
}
|
|
|
|
if let Ok(ts) = NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S%.f") {
|
|
return naive_datetime_to_timestamp(s, ts);
|
|
}
|
|
|
|
ParseTimestampSnafu { raw: s }.fail()
|
|
}
|
|
}
|
|
|
|
/// Converts the naive datetime (which has no specific timezone) to a
|
|
/// nanosecond epoch timestamp relative to UTC.
|
|
/// This code is copied from [arrow-datafusion](https://github.com/apache/arrow-datafusion/blob/arrow2/datafusion-physical-expr/src/arrow_temporal_util.rs#L137).
|
|
fn naive_datetime_to_timestamp(
|
|
s: &str,
|
|
datetime: NaiveDateTime,
|
|
) -> crate::error::Result<Timestamp> {
|
|
let l = Local {};
|
|
|
|
match l.from_local_datetime(&datetime) {
|
|
LocalResult::None => ParseTimestampSnafu { raw: s }.fail(),
|
|
LocalResult::Single(local_datetime) => Ok(Timestamp::new(
|
|
local_datetime.with_timezone(&Utc).timestamp_nanos(),
|
|
TimeUnit::Nanosecond,
|
|
)),
|
|
LocalResult::Ambiguous(local_datetime, _) => Ok(Timestamp::new(
|
|
local_datetime.with_timezone(&Utc).timestamp_nanos(),
|
|
TimeUnit::Nanosecond,
|
|
)),
|
|
}
|
|
}
|
|
|
|
impl From<i64> for Timestamp {
|
|
fn from(v: i64) -> Self {
|
|
Self {
|
|
value: v,
|
|
unit: TimeUnit::Millisecond,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<Timestamp> for i64 {
|
|
fn from(t: Timestamp) -> Self {
|
|
t.value
|
|
}
|
|
}
|
|
|
|
impl From<Timestamp> for serde_json::Value {
|
|
fn from(d: Timestamp) -> Self {
|
|
serde_json::Value::String(d.to_iso8601_string())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub enum TimeUnit {
|
|
Second,
|
|
#[default]
|
|
Millisecond,
|
|
Microsecond,
|
|
Nanosecond,
|
|
}
|
|
|
|
impl Display for TimeUnit {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
TimeUnit::Second => {
|
|
write!(f, "Second")
|
|
}
|
|
TimeUnit::Millisecond => {
|
|
write!(f, "Millisecond")
|
|
}
|
|
TimeUnit::Microsecond => {
|
|
write!(f, "Microsecond")
|
|
}
|
|
TimeUnit::Nanosecond => {
|
|
write!(f, "Nanosecond")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TimeUnit {
|
|
pub fn factor(&self) -> u32 {
|
|
match self {
|
|
TimeUnit::Second => 1_000_000_000,
|
|
TimeUnit::Millisecond => 1_000_000,
|
|
TimeUnit::Microsecond => 1_000,
|
|
TimeUnit::Nanosecond => 1,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl PartialOrd for Timestamp {
|
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
Some(self.cmp(other))
|
|
}
|
|
}
|
|
|
|
/// A proper implementation of total order requires antisymmetry, reflexivity, transitivity and totality.
|
|
/// In this comparison implementation, we map a timestamp uniquely to a `(i64, i64)` tuple which is respectively
|
|
/// total order.
|
|
impl Ord for Timestamp {
|
|
fn cmp(&self, other: &Self) -> Ordering {
|
|
// fast path: most comparisons use the same unit.
|
|
if self.unit == other.unit {
|
|
return self.value.cmp(&other.value);
|
|
}
|
|
|
|
let (s_sec, s_nsec) = self.split();
|
|
let (o_sec, o_nsec) = other.split();
|
|
match s_sec.cmp(&o_sec) {
|
|
Ordering::Less => Ordering::Less,
|
|
Ordering::Greater => Ordering::Greater,
|
|
Ordering::Equal => s_nsec.cmp(&o_nsec),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl PartialEq for Timestamp {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
self.cmp(other) == Ordering::Equal
|
|
}
|
|
}
|
|
|
|
impl Eq for Timestamp {}
|
|
|
|
impl Hash for Timestamp {
|
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
let (sec, nsec) = self.split();
|
|
state.write_i64(sec);
|
|
state.write_u32(nsec);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::collections::hash_map::DefaultHasher;
|
|
|
|
use chrono::Offset;
|
|
use rand::Rng;
|
|
use serde_json::Value;
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
pub fn test_time_unit() {
|
|
assert_eq!(
|
|
TimeUnit::Millisecond.factor() * 1000,
|
|
TimeUnit::Second.factor()
|
|
);
|
|
assert_eq!(
|
|
TimeUnit::Microsecond.factor() * 1000000,
|
|
TimeUnit::Second.factor()
|
|
);
|
|
assert_eq!(
|
|
TimeUnit::Nanosecond.factor() * 1000000000,
|
|
TimeUnit::Second.factor()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
pub fn test_timestamp() {
|
|
let t = Timestamp::new(1, TimeUnit::Millisecond);
|
|
assert_eq!(TimeUnit::Millisecond, t.unit());
|
|
assert_eq!(1, t.value());
|
|
assert_eq!(Timestamp::new(1000, TimeUnit::Microsecond), t);
|
|
assert!(t > Timestamp::new(999, TimeUnit::Microsecond));
|
|
}
|
|
|
|
#[test]
|
|
fn test_timestamp_antisymmetry() {
|
|
let t1 = Timestamp::new(1, TimeUnit::Second);
|
|
let t2 = Timestamp::new(1000, TimeUnit::Millisecond);
|
|
assert!(t1 >= t2);
|
|
assert!(t2 >= t1);
|
|
assert_eq!(Ordering::Equal, t1.cmp(&t2));
|
|
}
|
|
|
|
fn gen_random_ts() -> Timestamp {
|
|
let units = [
|
|
TimeUnit::Second,
|
|
TimeUnit::Millisecond,
|
|
TimeUnit::Microsecond,
|
|
TimeUnit::Nanosecond,
|
|
];
|
|
let mut rng = rand::thread_rng();
|
|
let unit_idx: usize = rng.gen_range(0..4);
|
|
let unit = units[unit_idx];
|
|
let value: i64 = rng.gen();
|
|
Timestamp::new(value, unit)
|
|
}
|
|
|
|
#[test]
|
|
fn test_timestamp_reflexivity() {
|
|
for _ in 0..1000 {
|
|
let ts = gen_random_ts();
|
|
assert!(ts >= ts, "ts: {ts:?}");
|
|
}
|
|
}
|
|
|
|
/// Generate timestamp less than or equal to `threshold`
|
|
fn gen_ts_le(threshold: &Timestamp) -> Timestamp {
|
|
let mut rng = rand::thread_rng();
|
|
let timestamp = rng.gen_range(i64::MIN..=threshold.value);
|
|
Timestamp::new(timestamp, threshold.unit)
|
|
}
|
|
|
|
#[test]
|
|
fn test_timestamp_transitivity() {
|
|
let t0 = Timestamp::new_millisecond(100);
|
|
let t1 = gen_ts_le(&t0);
|
|
let t2 = gen_ts_le(&t1);
|
|
assert!(t0 >= t1, "t0: {t0:?}, t1: {t1:?}");
|
|
assert!(t1 >= t2, "t1: {t1:?}, t2: {t2:?}");
|
|
assert!(t0 >= t2, "t0: {t0:?}, t2: {t2:?}");
|
|
|
|
let t0 = Timestamp::new_millisecond(-100);
|
|
let t1 = gen_ts_le(&t0); // t0 >= t1
|
|
let t2 = gen_ts_le(&t1); // t1 >= t2
|
|
assert!(t0 >= t1, "t0: {t0:?}, t1: {t1:?}");
|
|
assert!(t1 >= t2, "t1: {t1:?}, t2: {t2:?}");
|
|
assert!(t0 >= t2, "t0: {t0:?}, t2: {t2:?}"); // check if t0 >= t2
|
|
}
|
|
|
|
#[test]
|
|
fn test_antisymmetry() {
|
|
let t0 = Timestamp::new(1, TimeUnit::Second);
|
|
let t1 = Timestamp::new(1000, TimeUnit::Millisecond);
|
|
assert!(t0 >= t1, "t0: {t0:?}, t1: {t1:?}");
|
|
assert!(t1 >= t0, "t0: {t0:?}, t1: {t1:?}");
|
|
assert_eq!(t1, t0, "t0: {t0:?}, t1: {t1:?}");
|
|
}
|
|
|
|
#[test]
|
|
fn test_strong_connectivity() {
|
|
let mut values = Vec::with_capacity(1000);
|
|
for _ in 0..1000 {
|
|
values.push(gen_random_ts());
|
|
}
|
|
|
|
for l in &values {
|
|
for r in &values {
|
|
assert!(l >= r || l <= r, "l: {l:?}, r: {r:?}");
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_cmp_timestamp() {
|
|
let t1 = Timestamp::new(0, TimeUnit::Millisecond);
|
|
let t2 = Timestamp::new(0, TimeUnit::Second);
|
|
assert_eq!(t2, t1);
|
|
|
|
let t1 = Timestamp::new(1, TimeUnit::Millisecond);
|
|
let t2 = Timestamp::new(-1, TimeUnit::Second);
|
|
assert!(t1 > t2);
|
|
|
|
let t1 = Timestamp::new(i64::MAX / 1000 * 1000, TimeUnit::Millisecond);
|
|
let t2 = Timestamp::new(i64::MAX / 1000, TimeUnit::Second);
|
|
assert_eq!(t2, t1);
|
|
|
|
let t1 = Timestamp::new(i64::MAX, TimeUnit::Millisecond);
|
|
let t2 = Timestamp::new(i64::MAX / 1000 + 1, TimeUnit::Second);
|
|
assert!(t2 > t1);
|
|
|
|
let t1 = Timestamp::new(i64::MAX, TimeUnit::Millisecond);
|
|
let t2 = Timestamp::new(i64::MAX / 1000, TimeUnit::Second);
|
|
assert!(t2 < t1);
|
|
|
|
let t1 = Timestamp::new(10_010_001, TimeUnit::Millisecond);
|
|
let t2 = Timestamp::new(100, TimeUnit::Second);
|
|
assert!(t1 > t2);
|
|
|
|
let t1 = Timestamp::new(-100 * 10_001, TimeUnit::Millisecond);
|
|
let t2 = Timestamp::new(-100, TimeUnit::Second);
|
|
assert!(t2 > t1);
|
|
|
|
let t1 = Timestamp::new(i64::MIN, TimeUnit::Millisecond);
|
|
let t2 = Timestamp::new(i64::MIN / 1000 - 1, TimeUnit::Second);
|
|
assert!(t1 > t2);
|
|
|
|
let t1 = Timestamp::new(i64::MIN, TimeUnit::Millisecond);
|
|
let t2 = Timestamp::new(i64::MIN + 1, TimeUnit::Millisecond);
|
|
assert!(t2 > t1);
|
|
|
|
let t1 = Timestamp::new(i64::MAX, TimeUnit::Millisecond);
|
|
let t2 = Timestamp::new(i64::MIN, TimeUnit::Second);
|
|
assert!(t1 > t2);
|
|
|
|
let t1 = Timestamp::new(0, TimeUnit::Nanosecond);
|
|
let t2 = Timestamp::new(i64::MIN, TimeUnit::Second);
|
|
assert!(t1 > t2);
|
|
}
|
|
|
|
fn check_hash_eq(t1: Timestamp, t2: Timestamp) {
|
|
let mut hasher = DefaultHasher::new();
|
|
t1.hash(&mut hasher);
|
|
let t1_hash = hasher.finish();
|
|
|
|
let mut hasher = DefaultHasher::new();
|
|
t2.hash(&mut hasher);
|
|
let t2_hash = hasher.finish();
|
|
assert_eq!(t2_hash, t1_hash);
|
|
}
|
|
|
|
#[test]
|
|
fn test_hash() {
|
|
check_hash_eq(
|
|
Timestamp::new(0, TimeUnit::Millisecond),
|
|
Timestamp::new(0, TimeUnit::Second),
|
|
);
|
|
check_hash_eq(
|
|
Timestamp::new(1000, TimeUnit::Millisecond),
|
|
Timestamp::new(1, TimeUnit::Second),
|
|
);
|
|
check_hash_eq(
|
|
Timestamp::new(1_000_000, TimeUnit::Microsecond),
|
|
Timestamp::new(1, TimeUnit::Second),
|
|
);
|
|
check_hash_eq(
|
|
Timestamp::new(1_000_000_000, TimeUnit::Nanosecond),
|
|
Timestamp::new(1, TimeUnit::Second),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
pub fn test_from_i64() {
|
|
let t: Timestamp = 42.into();
|
|
assert_eq!(42, t.value());
|
|
assert_eq!(TimeUnit::Millisecond, t.unit());
|
|
}
|
|
|
|
// Input timestamp string is regarded as local timezone if no timezone is specified,
|
|
// but expected timestamp is in UTC timezone
|
|
fn check_from_str(s: &str, expect: &str) {
|
|
let ts = Timestamp::from_str(s).unwrap();
|
|
let time = NaiveDateTime::from_timestamp_opt(
|
|
ts.value / 1_000_000_000,
|
|
(ts.value % 1_000_000_000) as u32,
|
|
)
|
|
.unwrap();
|
|
assert_eq!(expect, time.to_string());
|
|
}
|
|
|
|
#[test]
|
|
fn test_from_str() {
|
|
// Explicit Z means timestamp in UTC
|
|
check_from_str("2020-09-08 13:42:29Z", "2020-09-08 13:42:29");
|
|
check_from_str("2020-09-08T13:42:29+08:00", "2020-09-08 05:42:29");
|
|
|
|
check_from_str(
|
|
"2020-09-08 13:42:29",
|
|
&NaiveDateTime::from_timestamp_opt(
|
|
1599572549
|
|
- Local
|
|
.timestamp_opt(0, 0)
|
|
.unwrap()
|
|
.offset()
|
|
.fix()
|
|
.local_minus_utc() as i64,
|
|
0,
|
|
)
|
|
.unwrap()
|
|
.to_string(),
|
|
);
|
|
|
|
check_from_str(
|
|
"2020-09-08T13:42:29",
|
|
&NaiveDateTime::from_timestamp_opt(
|
|
1599572549
|
|
- Local
|
|
.timestamp_opt(0, 0)
|
|
.unwrap()
|
|
.offset()
|
|
.fix()
|
|
.local_minus_utc() as i64,
|
|
0,
|
|
)
|
|
.unwrap()
|
|
.to_string(),
|
|
);
|
|
|
|
check_from_str(
|
|
"2020-09-08 13:42:29.042",
|
|
&NaiveDateTime::from_timestamp_opt(
|
|
1599572549
|
|
- Local
|
|
.timestamp_opt(0, 0)
|
|
.unwrap()
|
|
.offset()
|
|
.fix()
|
|
.local_minus_utc() as i64,
|
|
42000000,
|
|
)
|
|
.unwrap()
|
|
.to_string(),
|
|
);
|
|
check_from_str("2020-09-08 13:42:29.042Z", "2020-09-08 13:42:29.042");
|
|
check_from_str("2020-09-08 13:42:29.042+08:00", "2020-09-08 05:42:29.042");
|
|
check_from_str(
|
|
"2020-09-08T13:42:29.042",
|
|
&NaiveDateTime::from_timestamp_opt(
|
|
1599572549
|
|
- Local
|
|
.timestamp_opt(0, 0)
|
|
.unwrap()
|
|
.offset()
|
|
.fix()
|
|
.local_minus_utc() as i64,
|
|
42000000,
|
|
)
|
|
.unwrap()
|
|
.to_string(),
|
|
);
|
|
check_from_str("2020-09-08T13:42:29+08:00", "2020-09-08 05:42:29");
|
|
check_from_str(
|
|
"2020-09-08T13:42:29.0042+08:00",
|
|
"2020-09-08 05:42:29.004200",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_to_iso8601_string() {
|
|
std::env::set_var("TZ", "Asia/Shanghai");
|
|
let datetime_str = "2020-09-08 13:42:29.042+0000";
|
|
let ts = Timestamp::from_str(datetime_str).unwrap();
|
|
assert_eq!("2020-09-08 21:42:29.042+0800", ts.to_iso8601_string());
|
|
|
|
let ts_millis = 1668070237000;
|
|
let ts = Timestamp::new_millisecond(ts_millis);
|
|
assert_eq!("2022-11-10 16:50:37+0800", ts.to_iso8601_string());
|
|
|
|
let ts_millis = -1000;
|
|
let ts = Timestamp::new_millisecond(ts_millis);
|
|
assert_eq!("1970-01-01 07:59:59+0800", ts.to_iso8601_string());
|
|
|
|
let ts_millis = -1;
|
|
let ts = Timestamp::new_millisecond(ts_millis);
|
|
assert_eq!("1970-01-01 07:59:59.999+0800", ts.to_iso8601_string());
|
|
|
|
let ts_millis = -1001;
|
|
let ts = Timestamp::new_millisecond(ts_millis);
|
|
assert_eq!("1970-01-01 07:59:58.999+0800", ts.to_iso8601_string());
|
|
}
|
|
|
|
#[test]
|
|
fn test_serialize_to_json_value() {
|
|
std::env::set_var("TZ", "Asia/Shanghai");
|
|
assert_eq!(
|
|
"1970-01-01 08:00:01+0800",
|
|
match serde_json::Value::from(Timestamp::new(1, TimeUnit::Second)) {
|
|
Value::String(s) => s,
|
|
_ => unreachable!(),
|
|
}
|
|
);
|
|
|
|
assert_eq!(
|
|
"1970-01-01 08:00:00.001+0800",
|
|
match serde_json::Value::from(Timestamp::new(1, TimeUnit::Millisecond)) {
|
|
Value::String(s) => s,
|
|
_ => unreachable!(),
|
|
}
|
|
);
|
|
|
|
assert_eq!(
|
|
"1970-01-01 08:00:00.000001+0800",
|
|
match serde_json::Value::from(Timestamp::new(1, TimeUnit::Microsecond)) {
|
|
Value::String(s) => s,
|
|
_ => unreachable!(),
|
|
}
|
|
);
|
|
|
|
assert_eq!(
|
|
"1970-01-01 08:00:00.000000001+0800",
|
|
match serde_json::Value::from(Timestamp::new(1, TimeUnit::Nanosecond)) {
|
|
Value::String(s) => s,
|
|
_ => unreachable!(),
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_convert_timestamp() {
|
|
let ts = Timestamp::new(1, TimeUnit::Second);
|
|
assert_eq!(
|
|
Timestamp::new(1000, TimeUnit::Millisecond),
|
|
ts.convert_to(TimeUnit::Millisecond).unwrap()
|
|
);
|
|
assert_eq!(
|
|
Timestamp::new(1_000_000, TimeUnit::Microsecond),
|
|
ts.convert_to(TimeUnit::Microsecond).unwrap()
|
|
);
|
|
assert_eq!(
|
|
Timestamp::new(1_000_000_000, TimeUnit::Nanosecond),
|
|
ts.convert_to(TimeUnit::Nanosecond).unwrap()
|
|
);
|
|
|
|
let ts = Timestamp::new(1_000_100_100, TimeUnit::Nanosecond);
|
|
assert_eq!(
|
|
Timestamp::new(1_000_100, TimeUnit::Microsecond),
|
|
ts.convert_to(TimeUnit::Microsecond).unwrap()
|
|
);
|
|
assert_eq!(
|
|
Timestamp::new(1000, TimeUnit::Millisecond),
|
|
ts.convert_to(TimeUnit::Millisecond).unwrap()
|
|
);
|
|
assert_eq!(
|
|
Timestamp::new(1, TimeUnit::Second),
|
|
ts.convert_to(TimeUnit::Second).unwrap()
|
|
);
|
|
|
|
let ts = Timestamp::new(1_000_100_100, TimeUnit::Nanosecond);
|
|
assert_eq!(ts, ts.convert_to(TimeUnit::Nanosecond).unwrap());
|
|
let ts = Timestamp::new(1_000_100_100, TimeUnit::Microsecond);
|
|
assert_eq!(ts, ts.convert_to(TimeUnit::Microsecond).unwrap());
|
|
let ts = Timestamp::new(1_000_100_100, TimeUnit::Millisecond);
|
|
assert_eq!(ts, ts.convert_to(TimeUnit::Millisecond).unwrap());
|
|
let ts = Timestamp::new(1_000_100_100, TimeUnit::Second);
|
|
assert_eq!(ts, ts.convert_to(TimeUnit::Second).unwrap());
|
|
|
|
// -9223372036854775808 in milliseconds should be rounded up to -9223372036854776 in seconds
|
|
assert_eq!(
|
|
Timestamp::new(-9223372036854776, TimeUnit::Second),
|
|
Timestamp::new(i64::MIN, TimeUnit::Millisecond)
|
|
.convert_to(TimeUnit::Second)
|
|
.unwrap()
|
|
);
|
|
|
|
assert!(Timestamp::new(i64::MAX, TimeUnit::Second)
|
|
.convert_to(TimeUnit::Millisecond)
|
|
.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_split() {
|
|
assert_eq!((0, 0), Timestamp::new(0, TimeUnit::Second).split());
|
|
assert_eq!((1, 0), Timestamp::new(1, TimeUnit::Second).split());
|
|
assert_eq!(
|
|
(0, 1_000_000),
|
|
Timestamp::new(1, TimeUnit::Millisecond).split()
|
|
);
|
|
|
|
assert_eq!((0, 1_000), Timestamp::new(1, TimeUnit::Microsecond).split());
|
|
assert_eq!((0, 1), Timestamp::new(1, TimeUnit::Nanosecond).split());
|
|
|
|
assert_eq!(
|
|
(1, 1_000_000),
|
|
Timestamp::new(1001, TimeUnit::Millisecond).split()
|
|
);
|
|
|
|
assert_eq!(
|
|
(-2, 999_000_000),
|
|
Timestamp::new(-1001, TimeUnit::Millisecond).split()
|
|
);
|
|
|
|
// check min value of nanos
|
|
let (sec, nsec) = Timestamp::new(i64::MIN, TimeUnit::Nanosecond).split();
|
|
assert_eq!(
|
|
i64::MIN as i128,
|
|
sec as i128 * (TimeUnit::Second.factor() / TimeUnit::Nanosecond.factor()) as i128
|
|
+ nsec as i128
|
|
);
|
|
|
|
assert_eq!(
|
|
(i64::MAX, 0),
|
|
Timestamp::new(i64::MAX, TimeUnit::Second).split()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_convert_to_ceil() {
|
|
assert_eq!(
|
|
Timestamp::new(1, TimeUnit::Second),
|
|
Timestamp::new(1000, TimeUnit::Millisecond)
|
|
.convert_to_ceil(TimeUnit::Second)
|
|
.unwrap()
|
|
);
|
|
|
|
// These two cases shows how `Timestamp::convert_to_ceil` behaves differently
|
|
// from `Timestamp::convert_to` when converting larger unit to smaller unit.
|
|
assert_eq!(
|
|
Timestamp::new(1, TimeUnit::Second),
|
|
Timestamp::new(1001, TimeUnit::Millisecond)
|
|
.convert_to(TimeUnit::Second)
|
|
.unwrap()
|
|
);
|
|
assert_eq!(
|
|
Timestamp::new(2, TimeUnit::Second),
|
|
Timestamp::new(1001, TimeUnit::Millisecond)
|
|
.convert_to_ceil(TimeUnit::Second)
|
|
.unwrap()
|
|
);
|
|
|
|
assert_eq!(
|
|
Timestamp::new(-1, TimeUnit::Second),
|
|
Timestamp::new(-1, TimeUnit::Millisecond)
|
|
.convert_to(TimeUnit::Second)
|
|
.unwrap()
|
|
);
|
|
assert_eq!(
|
|
Timestamp::new(0, TimeUnit::Second),
|
|
Timestamp::new(-1, TimeUnit::Millisecond)
|
|
.convert_to_ceil(TimeUnit::Second)
|
|
.unwrap()
|
|
);
|
|
|
|
// When converting large unit to smaller unit, there will be no rounding error,
|
|
// so `Timestamp::convert_to_ceil` behaves just like `Timestamp::convert_to`
|
|
assert_eq!(
|
|
Timestamp::new(-1, TimeUnit::Second).convert_to(TimeUnit::Millisecond),
|
|
Timestamp::new(-1, TimeUnit::Second).convert_to_ceil(TimeUnit::Millisecond)
|
|
);
|
|
assert_eq!(
|
|
Timestamp::new(1000, TimeUnit::Second).convert_to(TimeUnit::Millisecond),
|
|
Timestamp::new(1000, TimeUnit::Second).convert_to_ceil(TimeUnit::Millisecond)
|
|
);
|
|
assert_eq!(
|
|
Timestamp::new(1, TimeUnit::Second).convert_to(TimeUnit::Millisecond),
|
|
Timestamp::new(1, TimeUnit::Second).convert_to_ceil(TimeUnit::Millisecond)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_split_overflow() {
|
|
Timestamp::new(i64::MAX, TimeUnit::Second).split();
|
|
Timestamp::new(i64::MIN, TimeUnit::Second).split();
|
|
Timestamp::new(i64::MAX, TimeUnit::Millisecond).split();
|
|
Timestamp::new(i64::MIN, TimeUnit::Millisecond).split();
|
|
Timestamp::new(i64::MAX, TimeUnit::Microsecond).split();
|
|
Timestamp::new(i64::MIN, TimeUnit::Microsecond).split();
|
|
Timestamp::new(i64::MAX, TimeUnit::Nanosecond).split();
|
|
Timestamp::new(i64::MIN, TimeUnit::Nanosecond).split();
|
|
let (sec, nsec) = Timestamp::new(i64::MIN, TimeUnit::Nanosecond).split();
|
|
let time = NaiveDateTime::from_timestamp_opt(sec, nsec).unwrap();
|
|
assert_eq!(sec, time.timestamp());
|
|
assert_eq!(nsec, time.timestamp_subsec_nanos());
|
|
}
|
|
|
|
#[test]
|
|
fn test_timestamp_sub() {
|
|
let res = Timestamp::new(1, TimeUnit::Second)
|
|
.sub(Duration::from_secs(1))
|
|
.unwrap();
|
|
assert_eq!(0, res.value);
|
|
assert_eq!(TimeUnit::Second, res.unit);
|
|
|
|
let res = Timestamp::new(0, TimeUnit::Second)
|
|
.sub(Duration::from_secs(1))
|
|
.unwrap();
|
|
assert_eq!(-1, res.value);
|
|
assert_eq!(TimeUnit::Second, res.unit);
|
|
|
|
let res = Timestamp::new(1, TimeUnit::Second)
|
|
.sub(Duration::from_millis(1))
|
|
.unwrap();
|
|
assert_eq!(1, res.value);
|
|
assert_eq!(TimeUnit::Second, res.unit);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_in_time_zone() {
|
|
std::env::set_var("TZ", "Asia/Shanghai");
|
|
assert_eq!(
|
|
Timestamp::new(0, TimeUnit::Nanosecond),
|
|
Timestamp::from_str("1970-01-01 08:00:00.000").unwrap()
|
|
);
|
|
|
|
assert_eq!(
|
|
Timestamp::new(0, TimeUnit::Second),
|
|
Timestamp::from_str("1970-01-01 08:00:00").unwrap()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_to_local_string() {
|
|
std::env::set_var("TZ", "Asia/Shanghai");
|
|
|
|
assert_eq!(
|
|
"1970-01-01 08:00:00.000000001",
|
|
Timestamp::new(1, TimeUnit::Nanosecond).to_local_string()
|
|
);
|
|
|
|
assert_eq!(
|
|
"1970-01-01 08:00:00.001",
|
|
Timestamp::new(1, TimeUnit::Millisecond).to_local_string()
|
|
);
|
|
|
|
assert_eq!(
|
|
"1970-01-01 08:00:01",
|
|
Timestamp::new(1, TimeUnit::Second).to_local_string()
|
|
);
|
|
}
|
|
}
|