mirror of
https://github.com/facebook/sapling.git
synced 2024-10-11 01:07:15 +03:00
mononoke-types: add a type to represent datetimes
Summary: Represented as a chrono datetime in Rust and an (i64, i32) pair in Thrift. Reviewed By: jsgf Differential Revision: D7325332 fbshipit-source-id: 22c3b17961ffb0b4fdb4e6e8aece3b257b3c718e
This commit is contained in:
parent
d33426121b
commit
bb6e012ad8
@ -40,3 +40,10 @@ typedef Blake2 ContentId (hs.newtype)
|
||||
// manifests can be applied in a streaming way.
|
||||
typedef binary MPathElement (hs.newtype)
|
||||
typedef list<MPathElement> MPath (hs.newtype)
|
||||
|
||||
struct DateTime {
|
||||
1: required i64 timestamp_secs,
|
||||
// Timezones can go up to UTC+13 (which would be represented as -46800), so
|
||||
// an i16 can't fit them.
|
||||
2: required i32 tz_offset_secs,
|
||||
}
|
||||
|
43
mononoke-types/mocks/datetime.rs
Normal file
43
mononoke-types/mocks/datetime.rs
Normal file
@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2018-present, Facebook, Inc.
|
||||
// All Rights Reserved.
|
||||
//
|
||||
// This software may be used and distributed according to the terms of the
|
||||
// GNU General Public License version 2 or any later version.
|
||||
|
||||
use chrono::{FixedOffset, TimeZone};
|
||||
|
||||
use mononoke_types::DateTime;
|
||||
|
||||
/// Return a `DateTime` corresponding to <year>-01-01 00:00:00 UTC.
|
||||
pub fn day_1_utc(year: i32) -> DateTime {
|
||||
DateTime::new(FixedOffset::west(0).ymd(year, 1, 1).and_hms(0, 0, 0))
|
||||
}
|
||||
|
||||
/// Return a `DateTime` corresponding to <year>-01-01 00:00:00 UTC,
|
||||
/// with the specified offset applied.
|
||||
pub fn day_1_tz(year: i32, offset: i32) -> DateTime {
|
||||
DateTime::new(FixedOffset::west(offset).ymd(year, 1, 1).and_hms(0, 0, 0))
|
||||
}
|
||||
|
||||
pub const PST_OFFSET: i32 = 7 * 3600;
|
||||
|
||||
lazy_static! {
|
||||
/// 1970-01-01 00:00:00 UTC.
|
||||
pub static ref EPOCH_ZERO: DateTime = DateTime::from_timestamp(0, 0).unwrap();
|
||||
/// 1970-01-01 00:00:00 UTC-07.
|
||||
pub static ref EPOCH_ZERO_PST: DateTime = DateTime::from_timestamp(0, PST_OFFSET).unwrap();
|
||||
|
||||
/// 1900-01-01 00:00:00 UTC.
|
||||
pub static ref YEAR_1900: DateTime = day_1_utc(1900);
|
||||
/// 1900-01-01 00:00:00 UTC-07.
|
||||
pub static ref YEAR_1900_PST: DateTime = day_1_tz(1900, PST_OFFSET);
|
||||
|
||||
/// 2000-01-01 00:00:00 UTC.
|
||||
pub static ref YEAR_2000: DateTime = day_1_utc(2000);
|
||||
/// 2000-01-01 00:00:00 UTC-07.
|
||||
pub static ref YEAR_2000_PST: DateTime = day_1_tz(2000, PST_OFFSET);
|
||||
|
||||
/// 2100-01-01 00:00:00 UTC.
|
||||
pub static ref YEAR_2100: DateTime = day_1_utc(2000);
|
||||
pub static ref YEAR_2100_PST: DateTime = day_1_tz(2100, PST_OFFSET);
|
||||
}
|
@ -7,9 +7,14 @@
|
||||
#![deny(warnings)]
|
||||
#![feature(const_fn)]
|
||||
|
||||
extern crate chrono;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
extern crate mononoke_types;
|
||||
|
||||
pub mod changesetid;
|
||||
pub mod contentid;
|
||||
pub mod datetime;
|
||||
pub mod hash;
|
||||
pub mod unodeid;
|
||||
|
142
mononoke-types/src/datetime.rs
Normal file
142
mononoke-types/src/datetime.rs
Normal file
@ -0,0 +1,142 @@
|
||||
// Copyright (c) 2018-present, Facebook, Inc.
|
||||
// All Rights Reserved.
|
||||
//
|
||||
// This software may be used and distributed according to the terms of the
|
||||
// GNU General Public License version 2 or any later version.
|
||||
|
||||
use std::fmt::{self, Display};
|
||||
|
||||
use chrono::{DateTime as ChronoDateTime, FixedOffset, LocalResult, TimeZone};
|
||||
use quickcheck::{empty_shrinker, Arbitrary, Gen};
|
||||
|
||||
use errors::*;
|
||||
use thrift;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub struct DateTime(ChronoDateTime<FixedOffset>);
|
||||
|
||||
impl DateTime {
|
||||
#[inline]
|
||||
pub fn new(dt: ChronoDateTime<FixedOffset>) -> Self {
|
||||
DateTime(dt)
|
||||
}
|
||||
|
||||
pub fn from_timestamp(secs: i64, tz_offset_secs: i32) -> Result<Self> {
|
||||
let tz = FixedOffset::west_opt(tz_offset_secs).ok_or_else(|| {
|
||||
ErrorKind::InvalidDateTime(format!("timezone offset out of range: {}", tz_offset_secs))
|
||||
})?;
|
||||
let dt = match tz.timestamp_opt(secs, 0) {
|
||||
LocalResult::Single(dt) => dt,
|
||||
_ => bail_err!(ErrorKind::InvalidDateTime(format!(
|
||||
"seconds out of range: {}",
|
||||
secs
|
||||
))),
|
||||
};
|
||||
Ok(Self::new(dt))
|
||||
}
|
||||
|
||||
pub(crate) fn from_thrift(dt: thrift::DateTime) -> Result<Self> {
|
||||
Self::from_timestamp(dt.timestamp_secs, dt.tz_offset_secs)
|
||||
}
|
||||
|
||||
/// Retrieves the Unix timestamp in UTC.
|
||||
#[inline]
|
||||
pub fn timestamp_secs(&self) -> i64 {
|
||||
self.0.timestamp()
|
||||
}
|
||||
|
||||
/// Retrieves the timezone offset, as represented by the number of seconds to
|
||||
/// add to convert local time to UTC.
|
||||
#[inline]
|
||||
pub fn tz_offset_secs(&self) -> i32 {
|
||||
// This is the same as the way Mercurial stores timezone offsets.
|
||||
self.0.offset().utc_minus_local()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_chrono(&self) -> &ChronoDateTime<FixedOffset> {
|
||||
&self.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn into_chrono(self) -> ChronoDateTime<FixedOffset> {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub(crate) fn into_thrift(self) -> thrift::DateTime {
|
||||
thrift::DateTime {
|
||||
timestamp_secs: self.timestamp_secs(),
|
||||
tz_offset_secs: self.tz_offset_secs(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for DateTime {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(fmt, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Arbitrary for DateTime {
|
||||
fn arbitrary<G: Gen>(g: &mut G) -> Self {
|
||||
// Ensure a large domain from which to get second values.
|
||||
let secs = g.gen_range(i32::min_value(), i32::max_value()) as i64;
|
||||
// Timezone offsets in the range [-86399, 86399] (both inclusive) are valid.
|
||||
// gen_range generates a value in the range [low, high).
|
||||
let tz_offset_secs = g.gen_range(-86_399, 86_400);
|
||||
DateTime::from_timestamp(secs, tz_offset_secs)
|
||||
.expect("Arbitrary instances should always be valid")
|
||||
}
|
||||
|
||||
fn shrink(&self) -> Box<Iterator<Item = Self>> {
|
||||
empty_shrinker()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
quickcheck! {
|
||||
fn thrift_roundtrip(dt: DateTime) -> bool {
|
||||
let thrift_dt = dt.into_thrift();
|
||||
let dt2 = DateTime::from_thrift(thrift_dt)
|
||||
.expect("roundtrip instances should always be valid");
|
||||
// Equality on DateTime structs doesn't pay attention to the time zone,
|
||||
// in order to be consistent with Ord.
|
||||
dt == dt2 && dt.tz_offset_secs() == dt2.tz_offset_secs()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_inputs() {
|
||||
DateTime::from_timestamp(0, 86_400)
|
||||
.expect_err("unexpected OK - tz_offset_secs out of bounds");
|
||||
DateTime::from_timestamp(0, -86_400)
|
||||
.expect_err("unexpected OK - tz_offset_secs out of bounds");
|
||||
DateTime::from_timestamp(i64::min_value(), 0)
|
||||
.expect_err("unexpected OK - timestamp_secs out of bounds");
|
||||
DateTime::from_timestamp(i64::max_value(), 0)
|
||||
.expect_err("unexpected OK - timestamp_secs out of bounds");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_thrift() {
|
||||
DateTime::from_thrift(thrift::DateTime {
|
||||
timestamp_secs: 0,
|
||||
tz_offset_secs: 86_400,
|
||||
}).expect_err("unexpected OK - tz_offset_secs out of bounds");
|
||||
DateTime::from_thrift(thrift::DateTime {
|
||||
timestamp_secs: 0,
|
||||
tz_offset_secs: -86_400,
|
||||
}).expect_err("unexpected OK - tz_offset_secs out of bounds");
|
||||
DateTime::from_thrift(thrift::DateTime {
|
||||
timestamp_secs: i64::min_value(),
|
||||
tz_offset_secs: 0,
|
||||
}).expect_err("unexpected OK - timestamp_secs out of bounds");
|
||||
DateTime::from_thrift(thrift::DateTime {
|
||||
timestamp_secs: i64::max_value(),
|
||||
tz_offset_secs: 0,
|
||||
}).expect_err("unexpected OK - timestamp_secs out of bounds");
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ pub enum ErrorKind {
|
||||
#[fail(display = "invalid path '{}': {}", _0, _1)] InvalidPath(String, String),
|
||||
#[fail(display = "invalid Mononoke path '{}': {}", _0, _1)] InvalidMPath(MPath, String),
|
||||
#[fail(display = "invalid Thrift structure '{}': {}", _0, _1)] InvalidThrift(String, String),
|
||||
#[fail(display = "invalid changeset date: {}", _0)] InvalidDateTime(String),
|
||||
}
|
||||
|
||||
pub type Result<T> = ::std::result::Result<T, Error>;
|
||||
|
@ -18,6 +18,7 @@ extern crate ascii;
|
||||
extern crate assert_matches;
|
||||
extern crate bincode;
|
||||
extern crate blake2;
|
||||
extern crate chrono;
|
||||
#[macro_use]
|
||||
extern crate failure_ext as failure;
|
||||
extern crate heapsize;
|
||||
@ -35,11 +36,13 @@ extern crate serde_derive;
|
||||
|
||||
extern crate mononoke_types_thrift;
|
||||
|
||||
pub mod datetime;
|
||||
pub mod errors;
|
||||
pub mod hash;
|
||||
pub mod path;
|
||||
pub mod typed_hash;
|
||||
|
||||
pub use datetime::DateTime;
|
||||
pub use path::{MPath, MPathElement, RepoPath};
|
||||
pub use typed_hash::{ChangesetId, ContentId, UnodeId};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user