Skip to content

Commit

Permalink
Add ms and us timestamp adapters
Browse files Browse the repository at this point in the history
Summary:
Adds millisecond and microsecond adapters.

This further converts the implementation and the tests into macros, since functionally they're all identical, just with different conversion functions.

Reviewed By: kcdode

Differential Revision: D52919985

fbshipit-source-id: 33e2bbf1ae9b8b04be4b55bf61e63bf802095dbc
  • Loading branch information
edward-shen authored and facebook-github-bot committed Jan 22, 2024
1 parent 20e03c3 commit fe31e0a
Showing 1 changed file with 166 additions and 45 deletions.
211 changes: 166 additions & 45 deletions shed/fbthrift_ext/adapters/chrono.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,55 +65,176 @@ pub struct OutOfRangeError(i64);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct UtcTimestampAdapter;

impl ThriftAdapter for UtcTimestampAdapter {
type StandardType = i64;
type AdaptedType = DateTime<Utc>;
/// Adapts Thrift `i64`s as a [`DateTime<Utc>`].
///
/// This adapter interprets the `i64` as the number of non-leap milliseconds
/// since midnight UTC on January 1, 1970.
///
/// Note that negative numbers are valid and are interpreted as how many seconds
/// before January 1, 1970. Other langauges and implementations should be
/// careful not to intepret negative numbers as values far, far in the future
/// (e.g. don't reinterpret cast to an `uint64_t`!).
///
/// Note that this adapter is only implemented on `i64`. This is intentional for
/// multiple reasons:
/// 1. All services should already be using `i64`s anyways for timestamps to
/// avoid the integer overflow in 2038.
/// 2. The underlying type natively supports `i64`s.
///
/// # Errors
///
/// This adapter is limited by the range supported by [`NaiveDateTime`]. Values
/// larger than 262,000 years away from the common era are unsuppported, and
/// will always fail.
///
/// [`DateTime<Utc>`]: chrono::DateTime
///
/// # Examples
///
/// ```thrift
/// include "thrift/annotation/rust.thrift";
///
/// @rust.Adapter{name = "::fbthrift_adapters::chrono::UtcMillisecondTimestampAdapter"}
/// typedef i64 utc_timestamp_ms;
///
/// struct Entry {
/// 1: utc_timestamp_ms expiration;
/// }
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct UtcMillisecondTimestampAdapter;

type Error = OutOfRangeError;
/// Adapts Thrift `i64`s as a [`DateTime<Utc>`].
///
/// This adapter interprets the `i64` as the number of non-leap microseconds
/// since midnight UTC on January 1, 1970.
///
/// Note that negative numbers are valid and are interpreted as how many seconds
/// before January 1, 1970. Other langauges and implementations should be
/// careful not to intepret negative numbers as values far, far in the future
/// (e.g. don't reinterpret cast to an `uint64_t`!).
///
/// Note that this adapter is only implemented on `i64`. This is intentional for
/// multiple reasons:
/// 1. All services should already be using `i64`s anyways for timestamps to
/// avoid the integer overflow in 2038.
/// 2. The underlying type natively supports `i64`s.
///
/// # Errors
///
/// This adapter is limited by the range supported by [`NaiveDateTime`]. Values
/// larger than 262,000 years away from the common era are unsuppported, and
/// will always fail.
///
/// [`DateTime<Utc>`]: chrono::DateTime
///
/// # Examples
///
/// ```thrift
/// include "thrift/annotation/rust.thrift";
///
/// @rust.Adapter{name = "::fbthrift_adapters::chrono::UtcMillisecondTimestampAdapter"}
/// typedef i64 utc_timestamp_us;
///
/// struct Entry {
/// 1: utc_timestamp_us expiration;
/// }
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct UtcMicrosecondTimestampAdapter;

macro_rules! impl_timestamp_adapter {
($adapter:ident, $to_thrift_fn:expr, $from_thrift_fn:expr) => {
impl ThriftAdapter for $adapter {
type StandardType = i64;
type AdaptedType = DateTime<Utc>;

type Error = OutOfRangeError;

fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType {
value.timestamp()
}
#[allow(clippy::redundant_closure_call)]
fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType {
$to_thrift_fn(value)
}

fn from_thrift(value: Self::StandardType) -> Result<Self::AdaptedType, Self::Error> {
NaiveDateTime::from_timestamp_opt(value, 0)
.ok_or(OutOfRangeError(value))
.map(|dt| dt.and_utc())
}
#[allow(clippy::redundant_closure_call)]
fn from_thrift(value: Self::StandardType) -> Result<Self::AdaptedType, Self::Error> {
$from_thrift_fn(value)
.ok_or(OutOfRangeError(value))
.map(|dt| dt.and_utc())
}
}
};
}

#[cfg(test)]
mod utc_timestamp {
use super::*;

#[test]
fn round_trip() {
for i in -1..=1 {
assert_eq!(
i,
UtcTimestampAdapter::to_thrift(&UtcTimestampAdapter::from_thrift(i).unwrap())
);
impl_timestamp_adapter!(UtcTimestampAdapter, DateTime::<Utc>::timestamp, |val| {
NaiveDateTime::from_timestamp_opt(val, 0)
});
impl_timestamp_adapter!(
UtcMillisecondTimestampAdapter,
DateTime::<Utc>::timestamp_millis,
NaiveDateTime::from_timestamp_millis
);
impl_timestamp_adapter!(
UtcMicrosecondTimestampAdapter,
DateTime::<Utc>::timestamp_micros,
NaiveDateTime::from_timestamp_micros
);

macro_rules! test_timestamp_adapter {
($name:ident, $adapter:ident, $one_unit_rfc3999:literal) => {
#[cfg(test)]
mod $name {
use super::*;

#[test]
fn round_trip() {
for i in -1..=1 {
assert_eq!(i, $adapter::to_thrift(&$adapter::from_thrift(i).unwrap()));
}
}

#[test]
fn negative() {
assert!($adapter::from_thrift(-1).is_ok());
}

#[test]
fn overflow() {
assert!($adapter::from_thrift(i64::MAX).is_err());
assert!($adapter::from_thrift(i64::MIN).is_err());
}

#[test]
fn positive() {
assert!($adapter::from_thrift(1).is_ok());
}

#[test]
fn zero() {
assert!($adapter::from_thrift(0).is_ok());
}

#[test]
fn one_unit() {
let val = $adapter::from_thrift(1).unwrap();
assert_eq!(val.to_rfc3339(), $one_unit_rfc3999);
}
}
}

#[test]
fn negative() {
assert!(UtcTimestampAdapter::from_thrift(-1).is_ok());
}

#[test]
fn overflow() {
assert!(UtcTimestampAdapter::from_thrift(i64::MAX).is_err());
assert!(UtcTimestampAdapter::from_thrift(i64::MIN).is_err());
}

#[test]
fn positive() {
assert!(UtcTimestampAdapter::from_thrift(1).is_ok());
}

#[test]
fn zero() {
assert!(UtcTimestampAdapter::from_thrift(0).is_ok());
}
};
}

test_timestamp_adapter!(
utc_timestamp,
UtcTimestampAdapter,
"1970-01-01T00:00:01+00:00"
);
test_timestamp_adapter!(
utc_millisecond_timestamp,
UtcMillisecondTimestampAdapter,
"1970-01-01T00:00:00.001+00:00"
);
test_timestamp_adapter!(
utc_microsecond_timestamp,
UtcMicrosecondTimestampAdapter,
"1970-01-01T00:00:00.000001+00:00"
);

0 comments on commit fe31e0a

Please sign in to comment.