diff --git a/src/timestamp.rs b/src/timestamp.rs index d344870..28be089 100644 --- a/src/timestamp.rs +++ b/src/timestamp.rs @@ -15,6 +15,297 @@ use crate::{ zoned::Zoned, }; +/// An instant in time represented as the number of nanoseconds since the Unix +/// epoch. +/// +/// To obtain civil or "local" datetime units like year, month, day or hour, +/// a timestamp needs to be combined with a [`TimeZone`] to create a +/// [`Zoned`]. +/// +/// The integer count of nanoseconds since the Unix epoch is signed, where +/// the Unix epoch is `1970-01-01 00:00:00Z`. A positive timestamp indicates +/// a point in time after the Unix epoch. A negative timestamp indicates a +/// point in time before the Unix epoch. +/// +/// # Parsing and printing +/// +/// The `Timestamp` type provides convenient trait implementations of +/// [`std::str::FromStr`] and [`std::fmt::Display`]: +/// +/// ``` +/// use jiff::Timestamp; +/// +/// let ts: Timestamp = "2024-06-19 15:22:45-04".parse()?; +/// assert_eq!(ts.to_string(), "2024-06-19T19:22:45Z"); +/// +/// # Ok::<(), Box>(()) +/// ``` +/// +/// A `Timestamp` can also be parsed from something that _contains_ a +/// timestamp, but with perhaps other data (such as a time zone): +/// +/// ``` +/// use jiff::Timestamp; +/// +/// let ts: Timestamp = "2024-06-19T15:22:45-04[America/New_York]".parse()?; +/// assert_eq!(ts.to_string(), "2024-06-19T19:22:45Z"); +/// +/// # Ok::<(), Box>(()) +/// ``` +/// +/// For more information on the specific format supported, see the +/// [`fmt::temporal`](crate::fmt::temporal) module documentation. +/// +/// # Default value +/// +/// For convenience, this type implements the `Default` trait. Its default +/// value corresponds to `1970-01-01T00:00:00.000000000`. That is, it is the +/// Unix epoch. One can also access this value via the `Timestamp::UNIX_EPOCH` +/// constant. +/// +/// # Leap seconds +/// +/// Jiff does not support leap seconds. Jiff behaves as if they don't exist. +/// The only exception is that if one parses a timestamp with a second +/// component of `60`, then it is automatically constrained to `59`: +/// +/// ``` +/// use jiff::Timestamp; +/// +/// let ts: Timestamp = "2016-12-31 23:59:60Z".parse()?; +/// assert_eq!(ts.to_string(), "2016-12-31T23:59:59Z"); +/// +/// # Ok::<(), Box>(()) +/// ``` +/// +/// # Comparisons +/// +/// The `Timestamp` type provides both `Eq` and `Ord` trait implementations +/// to facilitate easy comparisons. When a timestamp `ts1` occurs before a +/// timestamp `ts2`, then `dt1 < dt2`. For example: +/// +/// ``` +/// use jiff::Timestamp; +/// +/// let ts1 = Timestamp::from_second(123_456_789)?; +/// let ts2 = Timestamp::from_second(123_456_790)?; +/// assert!(ts1 < ts2); +/// +/// # Ok::<(), Box>(()) +/// ``` +/// +/// # Arithmetic +/// +/// This type provides routines for adding and subtracting spans of time, as +/// well as computing the span of time between two `Timestamp` values. +/// +/// For adding or subtracting spans of time, one can use any of the following +/// routines: +/// +/// * [`Timestamp::checked_add`] or [`Timestamp::checked_sub`] for checked +/// arithmetic. +/// * [`Timestamp::saturating_add`] or [`Timestamp::saturating_sub`] for +/// saturating arithmetic. +/// +/// Additionally, checked arithmetic is available via the `Add` and `Sub` +/// trait implementations. When the result overflows, a panic occurs. +/// +/// ``` +/// use jiff::{Timestamp, ToSpan}; +/// +/// let ts1: Timestamp = "2024-02-25T15:45Z".parse()?; +/// let ts2 = ts1 - 24.hours(); +/// assert_eq!(ts2.to_string(), "2024-02-24T15:45:00Z"); +/// +/// # Ok::<(), Box>(()) +/// ``` +/// +/// One can compute the span of time between two timestamps using either +/// [`Timestamp::until`] or [`Timestamp::since`]. It's also possible to +/// subtract two `Timestamp` values directly via a `Sub` trait implementation: +/// +/// ``` +/// use jiff::{Timestamp, ToSpan}; +/// +/// let ts1: Timestamp = "2024-05-03 23:30:00.123Z".parse()?; +/// let ts2: Timestamp = "2024-02-25 07Z".parse()?; +/// // The default is to return spans with units no bigger than seconds. +/// assert_eq!(ts1 - ts2, 5934600.seconds().milliseconds(123)); +/// +/// # Ok::<(), Box>(()) +/// ``` +/// +/// The `until` and `since` APIs are polymorphic and allow re-balancing and +/// rounding the span returned. For example, the default largest unit is +/// seconds (as exemplified above), but we can ask for bigger units (up to +/// hours): +/// +/// ``` +/// use jiff::{Timestamp, ToSpan, Unit}; +/// +/// let ts1: Timestamp = "2024-05-03 23:30:00.123Z".parse()?; +/// let ts2: Timestamp = "2024-02-25 07Z".parse()?; +/// assert_eq!( +/// // If you want to deal in units bigger than hours, then you'll have to +/// // convert your timestamp to a [`Zoned`] first. +/// ts1.since((Unit::Hour, ts2))?, +/// 1648.hours().minutes(30).milliseconds(123), +/// ); +/// +/// # Ok::<(), Box>(()) +/// ``` +/// +/// You can also round the span returned: +/// +/// ``` +/// use jiff::{RoundMode, Timestamp, TimestampDifference, ToSpan, Unit}; +/// +/// let ts1: Timestamp = "2024-05-03 23:30:59.123Z".parse()?; +/// let ts2: Timestamp = "2024-05-02 07Z".parse()?; +/// assert_eq!( +/// ts1.since( +/// TimestampDifference::new(ts2) +/// .smallest(Unit::Minute) +/// .largest(Unit::Hour), +/// )?, +/// 40.hours().minutes(30), +/// ); +/// // `TimestampDifference` uses truncation as a rounding mode by default, +/// // but you can set the rounding mode to break ties away from zero: +/// assert_eq!( +/// ts1.since( +/// TimestampDifference::new(ts2) +/// .smallest(Unit::Minute) +/// .largest(Unit::Hour) +/// .mode(RoundMode::HalfExpand), +/// )?, +/// // Rounds up to 31 minutes. +/// 40.hours().minutes(31), +/// ); +/// +/// # Ok::<(), Box>(()) +/// ``` +/// +/// # Rounding timestamps +/// +/// A `Timestamp` can be rounded based on a [`TimestampRound`] configuration of +/// smallest units, rounding increment and rounding mode. Here's an example +/// showing how to round to the nearest third hour: +/// +/// ``` +/// use jiff::{Timestamp, TimestampRound, Unit}; +/// +/// let ts: Timestamp = "2024-06-19 16:27:29.999999999Z".parse()?; +/// assert_eq!( +/// ts.round(TimestampRound::new().smallest(Unit::Hour).increment(3))?, +/// "2024-06-19 15Z".parse::()?, +/// ); +/// // Or alternatively, make use of the `From<(Unit, i64)> for TimestampRound` +/// // trait implementation: +/// assert_eq!( +/// ts.round((Unit::Hour, 3))?.to_string(), +/// "2024-06-19T15:00:00Z", +/// ); +/// +/// # Ok::<(), Box>(()) +/// ``` +/// +/// See [`Timestamp::round`] for more details. +/// +/// # An instant in time +/// +/// Unlike a [`civil::DateTime`](crate::civil::DateTime), a `Timestamp` +/// _always_ corresponds, unambiguously, to a precise instant in time (to +/// nanosecond precision). This means that attaching a time zone to a timestamp +/// is always unambiguous because there's never any question as to which +/// instant it refers to. This is true even for gaps in civil time. +/// +/// For example, in `America/New_York`, clocks were moved ahead one hour +/// at local time `2024-03-10 02:00:00`. That is, the 2 o'clock hour never +/// appeared on clocks in the `America/New_York` region. Since parsing a +/// timestamp always requires an offset, the time it refers to is unambiguous. +/// We can see this by writing a local time, `02:30`, that never existed but +/// with two different offsets: +/// +/// ``` +/// use jiff::Timestamp; +/// +/// // All we're doing here is attaching an offset to a civil datetime. +/// // There is no time zone information here, and thus there is no +/// // accounting for ambiguity due to daylight saving time transitions. +/// let before_hour_jump: Timestamp = "2024-03-10 02:30-04".parse()?; +/// let after_hour_jump: Timestamp = "2024-03-10 02:30-05".parse()?; +/// // This shows the instant in time in UTC. +/// assert_eq!(before_hour_jump.to_string(), "2024-03-10T06:30:00Z"); +/// assert_eq!(after_hour_jump.to_string(), "2024-03-10T07:30:00Z"); +/// +/// // Now let's attach each instant to an `America/New_York` time zone. +/// let zdt_before = before_hour_jump.to_zoned("America/New_York")?; +/// let zdt_after = after_hour_jump.to_zoned("America/New_York")?; +/// // And now we can see that even though the original instant refers to +/// // the 2 o'clock hour, since that hour never existed on the clocks in +/// // `America/New_York`, an instant with a time zone correctly adjusts. +/// assert_eq!( +/// zdt_before.to_string(), +/// "2024-03-10T01:30:00-05:00[America/New_York]", +/// ); +/// assert_eq!( +/// zdt_after.to_string(), +/// "2024-03-10T03:30:00-04:00[America/New_York]", +/// ); +/// +/// # Ok::<(), Box>(()) +/// ``` +/// +/// In the example above, there is never a step that is incorrect or has an +/// alternative answer. Every step is unambiguous because we never involve +/// any [`civil`](crate::civil) datetimes. +/// +/// But note that if the datetime string you're parsing from lacks an offset, +/// then it *could* be ambiguous even if a time zone is specified. In this +/// case, parsing will always fail: +/// +/// ``` +/// use jiff::Timestamp; +/// +/// let result = "2024-06-30 08:30[America/New_York]".parse::(); +/// assert_eq!( +/// result.unwrap_err().to_string(), +/// "failed to find offset component in \ +/// \"2024-06-30 08:30[America/New_York]\", \ +/// which is required for parsing a timestamp", +/// ); +/// ``` +/// +/// # Converting a civil datetime to a timestamp +/// +/// Sometimes you want to convert the "time on the clock" to a precise instant +/// in time. One way to do this was demonstrated in the previous section, but +/// it only works if you know your current time zone offset: +/// +/// ``` +/// use jiff::Timestamp; +/// +/// let ts: Timestamp = "2024-06-30 08:36-04".parse()?; +/// assert_eq!(ts.to_string(), "2024-06-30T12:36:00Z"); +/// +/// # Ok::<(), Box>(()) +/// ``` +/// +/// The above happened to be the precise instant in time I wrote the example. +/// Since I happened to know the offset, this worked okay. But what if I +/// didn't? We could instead construct a civil datetime and attach a time zone +/// to it. This will create a [`Zoned`] value, from which we can access the +/// timestamp: +/// +/// ``` +/// use jiff::civil::date; +/// +/// let local = date(2024, 6, 30).at(8, 36, 0, 0).to_zoned("America/New_York")?; +/// assert_eq!(local.to_timestamp().to_string(), "2024-06-30T12:36:00Z"); +/// +/// # Ok::<(), Box>(()) +/// ``` #[derive(Clone, Copy)] pub struct Timestamp { second: UnixSeconds,