Skip to content

Commit

Permalink
unify timestamp error kinds
Browse files Browse the repository at this point in the history
  • Loading branch information
danburkert committed Apr 4, 2022
1 parent 7a41f27 commit 91081d3
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 64 deletions.
34 changes: 15 additions & 19 deletions prost-types/src/datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -638,53 +638,51 @@ mod tests {
fn test_parse_timestamp() {
// RFC 3339 Section 5.8 Examples
assert_eq!(
"1985-04-12T23:20:50.52Z".parse::<Timestamp>().unwrap(),
"1985-04-12T23:20:50.52Z".parse::<Timestamp>(),
Timestamp::date_time_nanos(1985, 4, 12, 23, 20, 50, 520_000_000),
);
assert_eq!(
"1996-12-19T16:39:57-08:00".parse::<Timestamp>().unwrap(),
"1996-12-19T16:39:57-08:00".parse::<Timestamp>(),
Timestamp::date_time(1996, 12, 20, 0, 39, 57),
);
assert_eq!(
"1996-12-19T16:39:57-08:00".parse::<Timestamp>().unwrap(),
"1996-12-19T16:39:57-08:00".parse::<Timestamp>(),
Timestamp::date_time(1996, 12, 20, 0, 39, 57),
);
assert_eq!(
"1990-12-31T23:59:60Z".parse::<Timestamp>().unwrap(),
"1990-12-31T23:59:60Z".parse::<Timestamp>(),
Timestamp::date_time(1990, 12, 31, 23, 59, 59),
);
assert_eq!(
"1990-12-31T15:59:60-08:00".parse::<Timestamp>().unwrap(),
"1990-12-31T15:59:60-08:00".parse::<Timestamp>(),
Timestamp::date_time(1990, 12, 31, 23, 59, 59),
);
assert_eq!(
"1937-01-01T12:00:27.87+00:20".parse::<Timestamp>().unwrap(),
"1937-01-01T12:00:27.87+00:20".parse::<Timestamp>(),
Timestamp::date_time_nanos(1937, 1, 1, 11, 40, 27, 870_000_000),
);

// Date
assert_eq!(
"1937-01-01".parse::<Timestamp>().unwrap(),
"1937-01-01".parse::<Timestamp>(),
Timestamp::date(1937, 1, 1),
);

// Negative year
assert_eq!(
"-0008-01-01".parse::<Timestamp>().unwrap(),
"-0008-01-01".parse::<Timestamp>(),
Timestamp::date(-8, 1, 1),
);

// Plus year
assert_eq!(
"+19370-01-01".parse::<Timestamp>().unwrap(),
"+19370-01-01".parse::<Timestamp>(),
Timestamp::date(19370, 1, 1),
);

// Full nanos
assert_eq!(
"2020-02-03T01:02:03.123456789Z"
.parse::<Timestamp>()
.unwrap(),
"2020-02-03T01:02:03.123456789Z".parse::<Timestamp>(),
Timestamp::date_time_nanos(2020, 2, 3, 1, 2, 3, 123_456_789),
);

Expand All @@ -705,31 +703,29 @@ mod tests {
// Test extensions to RFC 3339.
// ' ' instead of 'T' as date/time separator.
assert_eq!(
"1985-04-12 23:20:50.52Z".parse::<Timestamp>().unwrap(),
"1985-04-12 23:20:50.52Z".parse::<Timestamp>(),
Timestamp::date_time_nanos(1985, 4, 12, 23, 20, 50, 520_000_000),
);

// No time zone specified.
assert_eq!(
"1985-04-12T23:20:50.52".parse::<Timestamp>().unwrap(),
"1985-04-12T23:20:50.52".parse::<Timestamp>(),
Timestamp::date_time_nanos(1985, 4, 12, 23, 20, 50, 520_000_000),
);

// Offset without minutes specified.
assert_eq!(
"1996-12-19T16:39:57-08".parse::<Timestamp>().unwrap(),
"1996-12-19T16:39:57-08".parse::<Timestamp>(),
Timestamp::date_time(1996, 12, 20, 0, 39, 57),
);

// Snowflake stage style.
assert_eq!(
"2015-09-12 00:47:19.591 Z".parse::<Timestamp>().unwrap(),
"2015-09-12 00:47:19.591 Z".parse::<Timestamp>(),
Timestamp::date_time_nanos(2015, 9, 12, 0, 47, 19, 591_000_000),
);
assert_eq!(
"2020-06-15 00:01:02.123 +0800"
.parse::<Timestamp>()
.unwrap(),
"2020-06-15 00:01:02.123 +0800".parse::<Timestamp>(),
Timestamp::date_time_nanos(2020, 6, 14, 16, 1, 2, 123_000_000),
);
}
Expand Down
99 changes: 54 additions & 45 deletions prost-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,12 +170,19 @@ impl Timestamp {
}

/// Creates a new `Timestamp` at the start of the provided UTC date.
pub fn date(year: i64, month: u8, day: u8) -> Timestamp {
pub fn date(year: i64, month: u8, day: u8) -> Result<Timestamp, TimestampError> {
Timestamp::date_time_nanos(year, month, day, 0, 0, 0, 0)
}

/// Creates a new `Timestamp` instance with the provided UTC date and time.
pub fn date_time(year: i64, month: u8, day: u8, hour: u8, minute: u8, second: u8) -> Timestamp {
pub fn date_time(
year: i64,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
) -> Result<Timestamp, TimestampError> {
Timestamp::date_time_nanos(year, month, day, hour, minute, second, 0)
}

Expand All @@ -188,7 +195,7 @@ impl Timestamp {
minute: u8,
second: u8,
nanos: u32,
) -> Timestamp {
) -> Result<Timestamp, TimestampError> {
let date_time = datetime::DateTime {
year,
month,
Expand All @@ -198,8 +205,12 @@ impl Timestamp {
second,
nanos,
};
assert!(date_time.is_valid(), "invalid date time: {}", date_time);
Timestamp::from(date_time)

if date_time.is_valid() {
Ok(Timestamp::from(date_time))
} else {
Err(TimestampError::InvalidDateTime)
}
}
}

Expand Down Expand Up @@ -240,37 +251,51 @@ impl From<std::time::SystemTime> for Timestamp {
}
}

/// Indicates that a [`Timestamp`] could not be converted to
/// [`SystemTime`][std::time::SystemTime] because it is out of range.
///
/// The range of times that can be represented by `SystemTime` depends on the platform.
/// All `Timestamp`s are likely representable on 64-bit Unix-like platforms, but
/// other platforms, such as Windows and 32-bit Linux, may not be able to represent
/// the full range of `Timestamp`s.
#[cfg(feature = "std")]
#[derive(Debug)]
#[derive(Debug, PartialEq)]
#[non_exhaustive]
pub struct TimestampOutOfSystemRangeError {
pub timestamp: Timestamp,
pub enum TimestampError {
/// Indicates that a [`Timestamp`] could not be converted to
/// [`SystemTime`][std::time::SystemTime] because it is out of range.
///
/// The range of times that can be represented by `SystemTime` depends on the platform. All
/// `Timestamp`s are likely representable on 64-bit Unix-like platforms, but other platforms,
/// such as Windows and 32-bit Linux, may not be able to represent the full range of
/// `Timestamp`s.
OutOfSystemRange(Timestamp),

/// An error indicating failure to parse a timestamp in RFC-3339 format.
ParseFailure,

/// Indicates an error when constructing a timestamp due to invalid date or time data.
InvalidDateTime,
}

#[cfg(feature = "std")]
impl fmt::Display for TimestampOutOfSystemRangeError {
impl fmt::Display for TimestampError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{:?} is not representable as a `SystemTime` because it is out of range",
self
)
match self {
TimestampError::OutOfSystemRange(timestamp) => {
write!(
f,
"{} is not representable as a `SystemTime` because it is out of range",
timestamp
)
}
TimestampError::ParseFailure => {
write!(f, "failed to parse RFC-3339 formatted timestamp")
}
TimestampError::InvalidDateTime => {
write!(f, "invalid date or time")
}
}
}
}

#[cfg(feature = "std")]
impl std::error::Error for TimestampOutOfSystemRangeError {}
impl std::error::Error for TimestampError {}

#[cfg(feature = "std")]
impl TryFrom<Timestamp> for std::time::SystemTime {
type Error = TimestampOutOfSystemRangeError;
type Error = TimestampError;

fn try_from(mut timestamp: Timestamp) -> Result<std::time::SystemTime, Self::Error> {
let orig_timestamp = timestamp.clone();
Expand All @@ -287,31 +312,15 @@ impl TryFrom<Timestamp> for std::time::SystemTime {
system_time.checked_add(time::Duration::from_nanos(timestamp.nanos as u64))
});

system_time.ok_or(TimestampOutOfSystemRangeError {
timestamp: orig_timestamp,
})
system_time.ok_or(TimestampError::OutOfSystemRange(orig_timestamp))
}
}

/// An error indicating failure to parse a timestamp in RFC-3339 format.
#[derive(Debug)]
#[non_exhaustive]
pub struct TimestampParseError;

impl fmt::Display for TimestampParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "failed to parse RFC-3339 formatted timestamp")
}
}

#[cfg(feature = "std")]
impl std::error::Error for TimestampParseError {}

impl FromStr for Timestamp {
type Err = TimestampParseError;
type Err = TimestampError;

fn from_str(s: &str) -> Result<Timestamp, TimestampParseError> {
datetime::parse_timestamp(s).ok_or(TimestampParseError)
fn from_str(s: &str) -> Result<Timestamp, TimestampError> {
datetime::parse_timestamp(s).ok_or(TimestampError::ParseFailure)
}
}

Expand Down

0 comments on commit 91081d3

Please sign in to comment.