Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert timestamp methods on DateTime to return Results #1495

Merged
merged 6 commits into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 61 additions & 102 deletions src/datetime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
use crate::offset::{FixedOffset, Offset, TimeZone, Utc};
#[cfg(any(feature = "clock", feature = "std"))]
use crate::OutOfRange;
use crate::{expect, ok, try_opt};
use crate::{Datelike, Months, TimeDelta, Timelike, Weekday};
use crate::{try_err, try_ok_or};
use crate::{Datelike, Error, Months, TimeDelta, Timelike, Weekday};

#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
use rkyv::{Archive, Deserialize, Serialize};
Expand Down Expand Up @@ -134,7 +134,8 @@
/// let dt: DateTime<Utc> = Utc.with_ymd_and_hms(2015, 5, 15, 0, 0, 0).unwrap();
/// assert_eq!(dt.timestamp(), 1431648000);
///
/// assert_eq!(DateTime::from_timestamp(dt.timestamp(), dt.timestamp_subsec_nanos()).unwrap(), dt);
/// assert_eq!(DateTime::from_timestamp(dt.timestamp(), dt.timestamp_subsec_nanos())?, dt);
/// # Ok::<(), chrono::Error>(())
/// ```
#[inline]
#[must_use]
Expand All @@ -149,23 +150,14 @@
/// # Example
///
/// ```
/// use chrono::{NaiveDate, Utc};
/// use chrono::NaiveDate;
///
/// let dt = NaiveDate::from_ymd(1970, 1, 1)
/// .unwrap()
/// .and_hms_milli(0, 0, 1, 444)
/// .unwrap()
/// .and_local_timezone(Utc)
/// .unwrap();
/// let dt = NaiveDate::from_ymd(1970, 1, 1)?.and_hms_milli(0, 0, 1, 444)?.and_utc();
/// assert_eq!(dt.timestamp_millis(), 1_444);
///
/// let dt = NaiveDate::from_ymd(2001, 9, 9)
/// .unwrap()
/// .and_hms_milli(1, 46, 40, 555)
/// .unwrap()
/// .and_local_timezone(Utc)
/// .unwrap();
/// let dt = NaiveDate::from_ymd(2001, 9, 9)?.and_hms_milli(1, 46, 40, 555)?.and_utc();
/// assert_eq!(dt.timestamp_millis(), 1_000_000_000_555);
/// # Ok::<(), chrono::Error>(())
/// ```
#[inline]
#[must_use]
Expand All @@ -179,23 +171,14 @@
/// # Example
///
/// ```
/// use chrono::{NaiveDate, Utc};
/// use chrono::NaiveDate;
///
/// let dt = NaiveDate::from_ymd(1970, 1, 1)
/// .unwrap()
/// .and_hms_micro(0, 0, 1, 444)
/// .unwrap()
/// .and_local_timezone(Utc)
/// .unwrap();
/// let dt = NaiveDate::from_ymd(1970, 1, 1)?.and_hms_micro(0, 0, 1, 444)?.and_utc();
/// assert_eq!(dt.timestamp_micros(), 1_000_444);
///
/// let dt = NaiveDate::from_ymd(2001, 9, 9)
/// .unwrap()
/// .and_hms_micro(1, 46, 40, 555)
/// .unwrap()
/// .and_local_timezone(Utc)
/// .unwrap();
/// let dt = NaiveDate::from_ymd(2001, 9, 9)?.and_hms_micro(1, 46, 40, 555)?.and_utc();
/// assert_eq!(dt.timestamp_micros(), 1_000_000_000_000_555);
/// # Ok::<(), chrono::Error>(())
/// ```
#[inline]
#[must_use]
Expand All @@ -209,67 +192,37 @@
/// # Errors
///
/// An `i64` with nanosecond precision can span a range of ~584 years. This function returns
/// `None` on an out of range `DateTime`.
/// [`Error::OutOfRange`] on an out of range `DateTime`.
///
/// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:43.145224192
/// and 2262-04-11T23:47:16.854775807.
///
/// # Example
///
/// ```
/// use chrono::{NaiveDate, Utc};
/// use chrono::{Error, NaiveDate};
///
/// let dt = NaiveDate::from_ymd(1970, 1, 1)
/// .unwrap()
/// .and_hms_nano(0, 0, 1, 444)
/// .unwrap()
/// .and_local_timezone(Utc)
/// .unwrap();
/// assert_eq!(dt.timestamp_nanos(), Some(1_000_000_444));
/// let dt = NaiveDate::from_ymd(1970, 1, 1)?.and_hms_nano(0, 0, 1, 444)?.and_utc();
/// assert_eq!(dt.timestamp_nanos(), Ok(1_000_000_444));
///
/// let dt = NaiveDate::from_ymd(2001, 9, 9)
/// .unwrap()
/// .and_hms_nano(1, 46, 40, 555)
/// .unwrap()
/// .and_local_timezone(Utc)
/// .unwrap();
/// assert_eq!(dt.timestamp_nanos(), Some(1_000_000_000_000_000_555));
/// let dt = NaiveDate::from_ymd(2001, 9, 9)?.and_hms_nano(1, 46, 40, 555)?.and_utc();
/// assert_eq!(dt.timestamp_nanos(), Ok(1_000_000_000_000_000_555));
///
/// let dt = NaiveDate::from_ymd(1677, 9, 21)
/// .unwrap()
/// .and_hms_nano(0, 12, 43, 145_224_192)
/// .unwrap()
/// .and_local_timezone(Utc)
/// .unwrap();
/// assert_eq!(dt.timestamp_nanos(), Some(-9_223_372_036_854_775_808));
/// let dt = NaiveDate::from_ymd(1677, 9, 21)?.and_hms_nano(0, 12, 43, 145_224_192)?.and_utc();
/// assert_eq!(dt.timestamp_nanos(), Ok(-9_223_372_036_854_775_808));
///
/// let dt = NaiveDate::from_ymd(2262, 4, 11)
/// .unwrap()
/// .and_hms_nano(23, 47, 16, 854_775_807)
/// .unwrap()
/// .and_local_timezone(Utc)
/// .unwrap();
/// assert_eq!(dt.timestamp_nanos(), Some(9_223_372_036_854_775_807));
/// let dt = NaiveDate::from_ymd(2262, 4, 11)?.and_hms_nano(23, 47, 16, 854_775_807)?.and_utc();
/// assert_eq!(dt.timestamp_nanos(), Ok(9_223_372_036_854_775_807));
///
/// let dt = NaiveDate::from_ymd(1677, 9, 21)
/// .unwrap()
/// .and_hms_nano(0, 12, 43, 145_224_191)
/// .unwrap()
/// .and_local_timezone(Utc)
/// .unwrap();
/// assert_eq!(dt.timestamp_nanos(), None);
/// let dt = NaiveDate::from_ymd(1677, 9, 21)?.and_hms_nano(0, 12, 43, 145_224_191)?.and_utc();
/// assert_eq!(dt.timestamp_nanos(), Err(Error::OutOfRange));
///
/// let dt = NaiveDate::from_ymd(2262, 4, 11)
/// .unwrap()
/// .and_hms_nano(23, 47, 16, 854_775_808)
/// .unwrap()
/// .and_local_timezone(Utc)
/// .unwrap();
/// assert_eq!(dt.timestamp_nanos(), None);
/// let dt = NaiveDate::from_ymd(2262, 4, 11)?.and_hms_nano(23, 47, 16, 854_775_808)?.and_utc();
/// assert_eq!(dt.timestamp_nanos(), Err(Error::OutOfRange));
/// # Ok::<(), Error>(())
/// ```
#[inline]
#[must_use]
pub const fn timestamp_nanos(&self) -> Option<i64> {
pub const fn timestamp_nanos(&self) -> Result<i64, Error> {
let mut timestamp = self.timestamp();
let mut subsec_nanos = self.timestamp_subsec_nanos() as i64;
// `(timestamp * 1_000_000_000) + subsec_nanos` may create a temporary that underflows while
Expand All @@ -282,7 +235,8 @@
subsec_nanos -= 1_000_000_000;
timestamp += 1;
}
try_opt!(timestamp.checked_mul(1_000_000_000)).checked_add(subsec_nanos)
let ts = try_ok_or!(timestamp.checked_mul(1_000_000_000), Error::OutOfRange);
Ok(try_ok_or!(ts.checked_add(subsec_nanos), Error::OutOfRange))
}

/// Returns the number of milliseconds since the last second boundary.
Expand Down Expand Up @@ -799,30 +753,33 @@
///
/// # Errors
///
/// Returns `None` on out-of-range number of seconds and/or
/// invalid nanosecond, otherwise returns `Some(DateTime {...})`.
/// This method returns:
/// - [`Error::OutOfRange`] if the timestamp is outside the range of a `DateTime`.
/// - [`Error::InvalidArgument`] if nanosecond >= 2,000,000,000.
/// - [`Error::DoesNotExist`] if the nanosecond part to represent a leap second is not on a
/// minute boundary.
///
/// # Example
///
/// ```
/// use chrono::DateTime;
///
/// let dt = DateTime::from_timestamp(1431648000, 0).expect("invalid timestamp");
/// let dt = DateTime::from_timestamp(1431648000, 0)?;
///
/// assert_eq!(dt.to_string(), "2015-05-15 00:00:00 UTC");
/// assert_eq!(DateTime::from_timestamp(dt.timestamp(), dt.timestamp_subsec_nanos()).unwrap(), dt);
/// assert_eq!(DateTime::from_timestamp(dt.timestamp(), dt.timestamp_subsec_nanos())?, dt);
/// # Ok::<(), chrono::Error>(())
/// ```
#[inline]
#[must_use]
pub const fn from_timestamp(secs: i64, nsecs: u32) -> Option<Self> {
pub const fn from_timestamp(secs: i64, nsecs: u32) -> Result<Self, Error> {
let days = secs.div_euclid(86_400) + UNIX_EPOCH_DAY;
let secs = secs.rem_euclid(86_400);
if days < i32::MIN as i64 || days > i32::MAX as i64 {
return None;
return Err(Error::OutOfRange);
}
let date = try_opt!(ok!(NaiveDate::from_num_days_from_ce(days as i32)));
let time = try_opt!(ok!(NaiveTime::from_num_seconds_from_midnight(secs as u32, nsecs)));
Some(date.and_time(time).and_utc())
let date = try_err!(NaiveDate::from_num_days_from_ce(days as i32));
let time = try_err!(NaiveTime::from_num_seconds_from_midnight(secs as u32, nsecs));
Ok(date.and_time(time).and_utc())
}

/// Makes a new `DateTime<Utc>` from the number of non-leap milliseconds
Expand All @@ -835,21 +792,22 @@
///
/// # Errors
///
/// Returns `None` on out-of-range number of milliseconds, otherwise returns `Some(DateTime {...})`.
/// Returns [`Error::OutOfRange`] if the timestamp in milliseconds is outside the range of a
/// `DateTime` (more than ca. 262,000 years away from common era).
///
/// # Example
///
/// ```
/// use chrono::DateTime;
///
/// let dt = DateTime::from_timestamp_millis(947638923004).expect("invalid timestamp");
/// let dt = DateTime::from_timestamp_millis(947638923004)?;
///
/// assert_eq!(dt.to_string(), "2000-01-12 01:02:03.004 UTC");
/// assert_eq!(DateTime::from_timestamp_millis(dt.timestamp_millis()).unwrap(), dt);
/// assert_eq!(DateTime::from_timestamp_millis(dt.timestamp_millis())?, dt);
/// # Ok::<(), chrono::Error>(())
/// ```
#[inline]
#[must_use]
pub const fn from_timestamp_millis(millis: i64) -> Option<Self> {
pub const fn from_timestamp_millis(millis: i64) -> Result<Self, Error> {
let secs = millis.div_euclid(1000);
let nsecs = millis.rem_euclid(1000) as u32 * 1_000_000;
Self::from_timestamp(secs, nsecs)
Expand All @@ -865,28 +823,26 @@
///
/// # Errors
///
/// Returns `None` if the number of microseconds would be out of range for a `NaiveDateTime`
/// (more than ca. 262,000 years away from common era)
/// Returns [`Error::OutOfRange`] if the timestamp in microseconds is outside the range of a
/// `DateTime` (more than ca. 262,000 years away from common era).
///
/// # Example
///
/// ```
/// use chrono::DateTime;
///
/// let timestamp_micros: i64 = 1662921288000000; // Sun, 11 Sep 2022 18:34:48 UTC
/// let dt = DateTime::from_timestamp_micros(timestamp_micros);
/// assert!(dt.is_some());
/// assert_eq!(timestamp_micros, dt.expect("invalid timestamp").timestamp_micros());
/// let dt = DateTime::from_timestamp_micros(timestamp_micros)?;
/// assert_eq!(timestamp_micros, dt.timestamp_micros());
///
/// // Negative timestamps (before the UNIX epoch) are supported as well.
/// let timestamp_micros: i64 = -2208936075000000; // Mon, 1 Jan 1900 14:38:45 UTC
/// let dt = DateTime::from_timestamp_micros(timestamp_micros);
/// assert!(dt.is_some());
/// assert_eq!(timestamp_micros, dt.expect("invalid timestamp").timestamp_micros());
/// let dt = DateTime::from_timestamp_micros(timestamp_micros)?;
/// assert_eq!(timestamp_micros, dt.timestamp_micros());
/// # Ok::<(), chrono::Error>(())
/// ```
#[inline]
#[must_use]
pub const fn from_timestamp_micros(micros: i64) -> Option<Self> {
pub const fn from_timestamp_micros(micros: i64) -> Result<Self, Error> {
let secs = micros.div_euclid(1_000_000);
let nsecs = micros.rem_euclid(1_000_000) as u32 * 1000;
Self::from_timestamp(secs, nsecs)
Expand Down Expand Up @@ -924,7 +880,10 @@
pub const fn from_timestamp_nanos(nanos: i64) -> Self {
let secs = nanos.div_euclid(1_000_000_000);
let nsecs = nanos.rem_euclid(1_000_000_000) as u32;
expect!(Self::from_timestamp(secs, nsecs), "timestamp in nanos is always in range")
match Self::from_timestamp(secs, nsecs) {
Ok(dt) => dt,
Err(_) => panic!("timestamp in nanos is always in range"),

Check warning on line 885 in src/datetime/mod.rs

View check run for this annotation

Codecov / codecov/patch

src/datetime/mod.rs#L885

Added line #L885 was not covered by tests
}
}

/// The Unix Epoch, 1970-01-01 00:00:00 UTC.
Expand Down
28 changes: 14 additions & 14 deletions src/datetime/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,9 @@
where
S: ser::Serializer,
{
serializer.serialize_i64(dt.timestamp_nanos().ok_or(ser::Error::custom(
"value out of range for a timestamp with nanosecond precision",
))?)
serializer.serialize_i64(dt.timestamp_nanos().map_err(|_| {
ser::Error::custom("value out of range for a timestamp with nanosecond precision")

Check warning on line 178 in src/datetime/serde.rs

View check run for this annotation

Codecov / codecov/patch

src/datetime/serde.rs#L178

Added line #L178 was not covered by tests
})?)
}

/// Deserialize a [`DateTime`] from a nanosecond timestamp
Expand Down Expand Up @@ -227,7 +227,7 @@
value.div_euclid(1_000_000_000),
(value.rem_euclid(1_000_000_000)) as u32,
)
.ok_or_else(|| invalid_ts(value))
.map_err(|_| invalid_ts(value))
}

/// Deserialize a timestamp in nanoseconds since the epoch
Expand All @@ -236,7 +236,7 @@
E: de::Error,
{
DateTime::from_timestamp((value / 1_000_000_000) as i64, (value % 1_000_000_000) as u32)
.ok_or_else(|| invalid_ts(value))
.map_err(|_| invalid_ts(value))
}
}
}
Expand Down Expand Up @@ -324,9 +324,9 @@
S: ser::Serializer,
{
match *opt {
Some(ref dt) => serializer.serialize_some(&dt.timestamp_nanos().ok_or(
ser::Error::custom("value out of range for a timestamp with nanosecond precision"),
)?),
Some(ref dt) => serializer.serialize_some(&dt.timestamp_nanos().map_err(|_| {
ser::Error::custom("value out of range for a timestamp with nanosecond precision")

Check warning on line 328 in src/datetime/serde.rs

View check run for this annotation

Codecov / codecov/patch

src/datetime/serde.rs#L328

Added line #L328 was not covered by tests
})?),
None => serializer.serialize_none(),
}
}
Expand Down Expand Up @@ -513,7 +513,7 @@
value.div_euclid(1_000_000),
(value.rem_euclid(1_000_000) * 1000) as u32,
)
.ok_or_else(|| invalid_ts(value))
.map_err(|_| invalid_ts(value))
}

/// Deserialize a timestamp in milliseconds since the epoch
Expand All @@ -525,7 +525,7 @@
(value / 1_000_000) as i64,
((value % 1_000_000) * 1_000) as u32,
)
.ok_or_else(|| invalid_ts(value))
.map_err(|_| invalid_ts(value))
}
}
}
Expand Down Expand Up @@ -787,7 +787,7 @@
where
E: de::Error,
{
DateTime::from_timestamp_millis(value).ok_or_else(|| invalid_ts(value))
DateTime::from_timestamp_millis(value).map_err(|_| invalid_ts(value))
}

/// Deserialize a timestamp in milliseconds since the epoch
Expand All @@ -796,7 +796,7 @@
E: de::Error,
{
DateTime::from_timestamp((value / 1000) as i64, ((value % 1000) * 1_000_000) as u32)
.ok_or_else(|| invalid_ts(value))
.map_err(|_| invalid_ts(value))
}
}
}
Expand Down Expand Up @@ -1055,7 +1055,7 @@
where
E: de::Error,
{
DateTime::from_timestamp(value, 0).ok_or_else(|| invalid_ts(value))
DateTime::from_timestamp(value, 0).map_err(|_| invalid_ts(value))

Check warning on line 1058 in src/datetime/serde.rs

View check run for this annotation

Codecov / codecov/patch

src/datetime/serde.rs#L1058

Added line #L1058 was not covered by tests
}

/// Deserialize a timestamp in seconds since the epoch
Expand All @@ -1066,7 +1066,7 @@
if value > i64::MAX as u64 {
Err(invalid_ts(value))
} else {
DateTime::from_timestamp(value as i64, 0).ok_or_else(|| invalid_ts(value))
DateTime::from_timestamp(value as i64, 0).map_err(|_| invalid_ts(value))
}
}
}
Expand Down
Loading
Loading