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

Deprecate timestamp methods on NaiveDateTime #1473

Merged
merged 9 commits into from
Mar 1, 2024
152 changes: 128 additions & 24 deletions src/datetime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@
#[cfg(feature = "clock")]
use crate::offset::Local;
use crate::offset::{FixedOffset, Offset, TimeZone, Utc};
use crate::try_opt;
#[allow(deprecated)]
use crate::Date;
use crate::{expect, try_opt};
use crate::{Datelike, Months, TimeDelta, Timelike, Weekday};

#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
Expand Down Expand Up @@ -201,7 +201,9 @@
#[inline]
#[must_use]
pub const fn timestamp(&self) -> i64 {
self.datetime.timestamp()
let gregorian_day = self.datetime.date().num_days_from_ce() as i64;
let seconds_from_midnight = self.datetime.time().num_seconds_from_midnight() as i64;
(gregorian_day - UNIX_EPOCH_DAY) * 86_400 + seconds_from_midnight
}

/// Returns the number of non-leap-milliseconds since January 1, 1970 UTC.
Expand Down Expand Up @@ -230,7 +232,8 @@
#[inline]
#[must_use]
pub const fn timestamp_millis(&self) -> i64 {
self.datetime.timestamp_millis()
let as_ms = self.timestamp() * 1000;
as_ms + self.timestamp_subsec_millis() as i64
}

/// Returns the number of non-leap-microseconds since January 1, 1970 UTC.
Expand Down Expand Up @@ -259,7 +262,8 @@
#[inline]
#[must_use]
pub const fn timestamp_micros(&self) -> i64 {
self.datetime.timestamp_micros()
let as_us = self.timestamp() * 1_000_000;
as_us + self.timestamp_subsec_micros() as i64
}

/// Returns the number of non-leap-nanoseconds since January 1, 1970 UTC.
Expand All @@ -274,9 +278,11 @@
#[deprecated(since = "0.4.31", note = "use `timestamp_nanos_opt()` instead")]
#[inline]
#[must_use]
#[allow(deprecated)]
pub const fn timestamp_nanos(&self) -> i64 {
self.datetime.timestamp_nanos()
expect!(
self.timestamp_nanos_opt(),
"value can not be represented in a timestamp with nanosecond precision."

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

View check run for this annotation

Codecov / codecov/patch

src/datetime/mod.rs#L282-L284

Added lines #L282 - L284 were not covered by tests
)
}

/// Returns the number of non-leap-nanoseconds since January 1, 1970 UTC.
Expand Down Expand Up @@ -345,7 +351,19 @@
#[inline]
#[must_use]
pub const fn timestamp_nanos_opt(&self) -> Option<i64> {
self.datetime.timestamp_nanos_opt()
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
// the final value can be represented as an `i64`.
// As workaround we converting the negative case to:
// `((timestamp + 1) * 1_000_000_000) + (ns - 1_000_000_000)``
//
// Also see <https://github.com/chronotope/chrono/issues/1289>.
if timestamp < 0 {
subsec_nanos -= 1_000_000_000;
timestamp += 1;
}
try_opt!(timestamp.checked_mul(1_000_000_000)).checked_add(subsec_nanos)
}

/// Returns the number of milliseconds since the last second boundary.
Expand All @@ -354,7 +372,7 @@
#[inline]
#[must_use]
pub const fn timestamp_subsec_millis(&self) -> u32 {
self.datetime.timestamp_subsec_millis()
self.timestamp_subsec_nanos() / 1_000_000
}

/// Returns the number of microseconds since the last second boundary.
Expand All @@ -363,7 +381,7 @@
#[inline]
#[must_use]
pub const fn timestamp_subsec_micros(&self) -> u32 {
self.datetime.timestamp_subsec_micros()
self.timestamp_subsec_nanos() / 1_000
}

/// Returns the number of nanoseconds since the last second boundary
Expand All @@ -372,7 +390,7 @@
#[inline]
#[must_use]
pub const fn timestamp_subsec_nanos(&self) -> u32 {
self.datetime.timestamp_subsec_nanos()
self.datetime.time().nanosecond()
}

/// Retrieves an associated offset from UTC.
Expand Down Expand Up @@ -654,7 +672,7 @@
}

impl DateTime<Utc> {
/// Makes a new [`DateTime<Utc>`] from the number of non-leap seconds
/// Makes a new `DateTime<Utc>` from the number of non-leap seconds
/// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp")
/// and the number of nanoseconds since the last whole non-leap second.
///
Expand All @@ -676,27 +694,30 @@
/// # Example
///
/// ```
/// use chrono::{DateTime, Utc};
/// use chrono::DateTime;
///
/// let dt: DateTime<Utc> =
/// DateTime::<Utc>::from_timestamp(1431648000, 0).expect("invalid timestamp");
/// let dt = DateTime::from_timestamp(1431648000, 0).expect("invalid timestamp");
///
/// 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);
/// ```
#[inline]
#[must_use]
pub const fn from_timestamp(secs: i64, nsecs: u32) -> Option<Self> {
Some(DateTime {
datetime: try_opt!(NaiveDateTime::from_timestamp_opt(secs, nsecs)),
offset: Utc,
})
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;
}
let date = try_opt!(NaiveDate::from_num_days_from_ce_opt(days as i32));
let time = try_opt!(NaiveTime::from_num_seconds_from_midnight_opt(secs as u32, nsecs));
Some(date.and_time(time).and_utc())
}

/// Makes a new [`DateTime<Utc>`] from the number of non-leap milliseconds
/// Makes a new `DateTime<Utc>` from the number of non-leap milliseconds
/// since January 1, 1970 0:00:00.000 UTC (aka "UNIX timestamp").
///
/// This is guaranteed to round-trip with regard to [`timestamp_millis`](DateTime::timestamp_millis).
/// This is guaranteed to round-trip with [`timestamp_millis`](DateTime::timestamp_millis).
///
/// If you need to create a `DateTime` with a [`TimeZone`] different from [`Utc`], use
/// [`TimeZone::timestamp_millis_opt`] or [`DateTime::with_timezone`].
Expand All @@ -708,18 +729,91 @@
/// # Example
///
/// ```
/// use chrono::{DateTime, Utc};
/// use chrono::DateTime;
///
/// let dt: DateTime<Utc> =
/// DateTime::<Utc>::from_timestamp_millis(947638923004).expect("invalid timestamp");
/// let dt = DateTime::from_timestamp_millis(947638923004).expect("invalid timestamp");
///
/// assert_eq!(dt.to_string(), "2000-01-12 01:02:03.004 UTC");
/// assert_eq!(DateTime::from_timestamp_millis(dt.timestamp_millis()).unwrap(), dt);
/// ```
#[inline]
#[must_use]
pub const fn from_timestamp_millis(millis: i64) -> Option<Self> {
Some(try_opt!(NaiveDateTime::from_timestamp_millis(millis)).and_utc())
let secs = millis.div_euclid(1000);
let nsecs = millis.rem_euclid(1000) as u32 * 1_000_000;
Self::from_timestamp(secs, nsecs)
}

/// Creates a new `DateTime<Utc>` from the number of non-leap microseconds
/// since January 1, 1970 0:00:00.000 UTC (aka "UNIX timestamp").
///
/// This is guaranteed to round-trip with [`timestamp_micros`](DateTime::timestamp_micros).
///
/// If you need to create a `DateTime` with a [`TimeZone`] different from [`Utc`], use
/// [`TimeZone::timestamp_micros`] or [`DateTime::with_timezone`].
///
/// # 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)
///
/// # 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());
///
/// // 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());
/// ```
#[inline]
#[must_use]
pub const fn from_timestamp_micros(micros: i64) -> Option<Self> {
let secs = micros.div_euclid(1_000_000);
let nsecs = micros.rem_euclid(1_000_000) as u32 * 1000;
Self::from_timestamp(secs, nsecs)
}

/// Creates a new [`DateTime<Utc>`] from the number of non-leap microseconds
/// since January 1, 1970 0:00:00.000 UTC (aka "UNIX timestamp").
///
/// This is guaranteed to round-trip with [`timestamp_nanos`](DateTime::timestamp_nanos).
///
/// If you need to create a `DateTime` with a [`TimeZone`] different from [`Utc`], use
/// [`TimeZone::timestamp_nanos`] or [`DateTime::with_timezone`].
///
/// The UNIX epoch starts on midnight, January 1, 1970, UTC.
///
/// An `i64` with nanosecond precision can span a range of ~584 years. Because all values can
/// be represented as a `DateTime` this method never fails.
///
/// # Example
///
/// ```
/// use chrono::DateTime;
///
/// let timestamp_nanos: i64 = 1662921288_000_000_000; // Sun, 11 Sep 2022 18:34:48 UTC
/// let dt = DateTime::from_timestamp_nanos(timestamp_nanos);
/// assert_eq!(timestamp_nanos, dt.timestamp_nanos_opt().unwrap());
///
/// // Negative timestamps (before the UNIX epoch) are supported as well.
/// let timestamp_nanos: i64 = -2208936075_000_000_000; // Mon, 1 Jan 1900 14:38:45 UTC
/// let dt = DateTime::from_timestamp_nanos(timestamp_nanos);
/// assert_eq!(timestamp_nanos, dt.timestamp_nanos_opt().unwrap());
/// ```
#[inline]
#[must_use]
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")
}

/// The Unix Epoch, 1970-01-01 00:00:00 UTC.
Expand Down Expand Up @@ -1787,6 +1881,16 @@
}
}

/// Number of days between Januari 1, 1970 and December 31, 1 BCE which we define to be day 0.
/// 4 full leap year cycles until December 31, 1600 4 * 146097 = 584388
/// 1 day until January 1, 1601 1
/// 369 years until Januari 1, 1970 369 * 365 = 134685
/// of which floor(369 / 4) are leap years floor(369 / 4) = 92
/// except for 1700, 1800 and 1900 -3 +
/// --------
/// 719163
const UNIX_EPOCH_DAY: i64 = 719_163;
djc marked this conversation as resolved.
Show resolved Hide resolved

#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))]
fn test_encodable_json<FUtc, FFixed, E>(to_string_utc: FUtc, to_string_fixed: FFixed)
where
Expand Down