Skip to content

Commit

Permalink
progress
Browse files Browse the repository at this point in the history
  • Loading branch information
BurntSushi committed Jun 30, 2024
1 parent d944a8e commit b1caa74
Showing 1 changed file with 291 additions and 0 deletions.
291 changes: 291 additions & 0 deletions src/timestamp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn std::error::Error>>(())
/// ```
///
/// 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<dyn std::error::Error>>(())
/// ```
///
/// 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<dyn std::error::Error>>(())
/// ```
///
/// # 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<dyn std::error::Error>>(())
/// ```
///
/// # 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<dyn std::error::Error>>(())
/// ```
///
/// 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<dyn std::error::Error>>(())
/// ```
///
/// 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<dyn std::error::Error>>(())
/// ```
///
/// 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<dyn std::error::Error>>(())
/// ```
///
/// # 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::<Timestamp>()?,
/// );
/// // 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<dyn std::error::Error>>(())
/// ```
///
/// 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<dyn std::error::Error>>(())
/// ```
///
/// 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::<Timestamp>();
/// 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<dyn std::error::Error>>(())
/// ```
///
/// 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<dyn std::error::Error>>(())
/// ```
#[derive(Clone, Copy)]
pub struct Timestamp {
second: UnixSeconds,
Expand Down

0 comments on commit b1caa74

Please sign in to comment.