jiff/src/span.rs
Andrew Gallant c587fddbb7 fmt: add table of different formats supported by Jiff
I think someone asked for this when `jiff 0.1` was released, and I
thought it was a good idea then. But I hadn't gotten around to it. With
the number of formats Jiff supports having expanded a bit since then, I
think now is a good time to get this documented.
2025-02-10 21:24:55 -05:00

7260 lines
253 KiB
Rust

use core::{cmp::Ordering, time::Duration as UnsignedDuration};
use crate::{
civil::{Date, DateTime, Time},
duration::{Duration, SDuration},
error::{err, Error, ErrorContext},
fmt::{friendly, temporal},
tz::TimeZone,
util::{
borrow::DumbCow,
escape,
rangeint::{ri64, ri8, RFrom, RInto, TryRFrom, TryRInto},
round::increment,
t::{self, Constant, NoUnits, NoUnits128, Sign, C},
},
RoundMode, SignedDuration, Timestamp, Zoned,
};
/// A macro helper, only used in tests, for comparing spans for equality.
#[cfg(test)]
macro_rules! span_eq {
($span1:expr, $span2:expr $(,)?) => {{
assert_eq!($span1.fieldwise(), $span2.fieldwise());
}};
($span1:expr, $span2:expr, $($tt:tt)*) => {{
assert_eq!($span1.fieldwise(), $span2.fieldwise(), $($tt)*);
}};
}
#[cfg(test)]
pub(crate) use span_eq;
/// A span of time represented via a mixture of calendar and clock units.
///
/// A span represents a duration of time in units of years, months, weeks,
/// days, hours, minutes, seconds, milliseconds, microseconds and nanoseconds.
/// Spans are used to as inputs to routines like
/// [`Zoned::checked_add`] and [`Date::saturating_sub`],
/// and are also outputs from routines like
/// [`Timestamp::since`] and [`DateTime::until`].
///
/// # Range of spans
///
/// Except for nanoseconds, each unit can represent the full span of time
/// expressible via any combination of datetime supported by Jiff. For example:
///
/// ```
/// use jiff::{civil::{DateTime, DateTimeDifference}, ToSpan, Unit};
///
/// let options = DateTimeDifference::new(DateTime::MAX).largest(Unit::Year);
/// assert_eq!(DateTime::MIN.until(options)?.get_years(), 19_998);
///
/// let options = options.largest(Unit::Day);
/// assert_eq!(DateTime::MIN.until(options)?.get_days(), 7_304_483);
///
/// let options = options.largest(Unit::Microsecond);
/// assert_eq!(
/// DateTime::MIN.until(options)?.get_microseconds(),
/// 631_107_417_599_999_999i64,
/// );
///
/// let options = options.largest(Unit::Nanosecond);
/// // Span is too big, overflow!
/// assert!(DateTime::MIN.until(options).is_err());
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Building spans
///
/// A default or empty span corresponds to a duration of zero time:
///
/// ```
/// use jiff::Span;
///
/// assert!(Span::new().is_zero());
/// assert!(Span::default().is_zero());
/// ```
///
/// Spans are `Copy` types that have mutator methods on them for creating new
/// spans:
///
/// ```
/// use jiff::Span;
///
/// let span = Span::new().days(5).hours(8).minutes(1);
/// assert_eq!(span.to_string(), "P5DT8H1M");
/// ```
///
/// But Jiff provides a [`ToSpan`] trait that defines extension methods on
/// primitive signed integers to make span creation terser:
///
/// ```
/// use jiff::ToSpan;
///
/// let span = 5.days().hours(8).minutes(1);
/// assert_eq!(span.to_string(), "P5DT8H1M");
/// // singular units on integers can be used too:
/// let span = 1.day().hours(8).minutes(1);
/// assert_eq!(span.to_string(), "P1DT8H1M");
/// ```
///
/// # Negative spans
///
/// A span may be negative. All of these are equivalent:
///
/// ```
/// use jiff::{Span, ToSpan};
///
/// let span = -Span::new().days(5);
/// assert_eq!(span.to_string(), "-P5D");
///
/// let span = Span::new().days(5).negate();
/// assert_eq!(span.to_string(), "-P5D");
///
/// let span = Span::new().days(-5);
/// assert_eq!(span.to_string(), "-P5D");
///
/// let span = -Span::new().days(-5).negate();
/// assert_eq!(span.to_string(), "-P5D");
///
/// let span = -5.days();
/// assert_eq!(span.to_string(), "-P5D");
///
/// let span = (-5).days();
/// assert_eq!(span.to_string(), "-P5D");
///
/// let span = -(5.days());
/// assert_eq!(span.to_string(), "-P5D");
/// ```
///
/// The sign of a span applies to the entire span. When a span is negative,
/// then all of its units are negative:
///
/// ```
/// use jiff::ToSpan;
///
/// let span = -5.days().hours(10).minutes(1);
/// assert_eq!(span.get_days(), -5);
/// assert_eq!(span.get_hours(), -10);
/// assert_eq!(span.get_minutes(), -1);
/// ```
///
/// And if any of a span's units are negative, then the entire span is regarded
/// as negative:
///
/// ```
/// use jiff::ToSpan;
///
/// // It's the same thing.
/// let span = (-5).days().hours(-10).minutes(-1);
/// assert_eq!(span.get_days(), -5);
/// assert_eq!(span.get_hours(), -10);
/// assert_eq!(span.get_minutes(), -1);
///
/// // Still the same. All negative.
/// let span = 5.days().hours(-10).minutes(1);
/// assert_eq!(span.get_days(), -5);
/// assert_eq!(span.get_hours(), -10);
/// assert_eq!(span.get_minutes(), -1);
///
/// // But this is not! The negation in front applies
/// // to the entire span, which was already negative
/// // by virtue of at least one of its units being
/// // negative. So the negation operator in front turns
/// // the span positive.
/// let span = -5.days().hours(-10).minutes(-1);
/// assert_eq!(span.get_days(), 5);
/// assert_eq!(span.get_hours(), 10);
/// assert_eq!(span.get_minutes(), 1);
/// ```
///
/// You can also ask for the absolute value of a span:
///
/// ```
/// use jiff::Span;
///
/// let span = Span::new().days(5).hours(10).minutes(1).negate().abs();
/// assert_eq!(span.get_days(), 5);
/// assert_eq!(span.get_hours(), 10);
/// assert_eq!(span.get_minutes(), 1);
/// ```
///
/// # Parsing and printing
///
/// The `Span` type provides convenient trait implementations of
/// [`std::str::FromStr`] and [`std::fmt::Display`]:
///
/// ```
/// use jiff::{Span, ToSpan};
///
/// let span: Span = "P2m10dT2h30m".parse()?;
/// // By default, capital unit designator labels are used.
/// // This can be changed with `jiff::fmt::temporal::SpanPrinter::lowercase`.
/// assert_eq!(span.to_string(), "P2M10DT2H30M");
///
/// // Or use the "friendly" format by invoking the `Display` alternate:
/// assert_eq!(format!("{span:#}"), "2mo 10d 2h 30m");
///
/// // Parsing automatically supports both the ISO 8601 and "friendly"
/// // formats. Note that we use `Span::fieldwise` to create a `Span` that
/// // compares based on each field. To compare based on total duration, use
/// // `Span::compare` or `Span::total`.
/// let span: Span = "2mo 10d 2h 30m".parse()?;
/// assert_eq!(span, 2.months().days(10).hours(2).minutes(30).fieldwise());
/// let span: Span = "2 months, 10 days, 2 hours, 30 minutes".parse()?;
/// assert_eq!(span, 2.months().days(10).hours(2).minutes(30).fieldwise());
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// The format supported is a variation (nearly a subset) of the duration
/// format specified in [ISO 8601] _and_ a Jiff-specific "friendly" format.
/// Here are more examples:
///
/// ```
/// use jiff::{Span, ToSpan};
///
/// let spans = [
/// // ISO 8601
/// ("P40D", 40.days()),
/// ("P1y1d", 1.year().days(1)),
/// ("P3dT4h59m", 3.days().hours(4).minutes(59)),
/// ("PT2H30M", 2.hours().minutes(30)),
/// ("P1m", 1.month()),
/// ("P1w", 1.week()),
/// ("P1w4d", 1.week().days(4)),
/// ("PT1m", 1.minute()),
/// ("PT0.0021s", 2.milliseconds().microseconds(100)),
/// ("PT0s", 0.seconds()),
/// ("P0d", 0.seconds()),
/// (
/// "P1y1m1dT1h1m1.1s",
/// 1.year().months(1).days(1).hours(1).minutes(1).seconds(1).milliseconds(100),
/// ),
/// // Jiff's "friendly" format
/// ("40d", 40.days()),
/// ("40 days", 40.days()),
/// ("1y1d", 1.year().days(1)),
/// ("1yr 1d", 1.year().days(1)),
/// ("3d4h59m", 3.days().hours(4).minutes(59)),
/// ("3 days, 4 hours, 59 minutes", 3.days().hours(4).minutes(59)),
/// ("3d 4h 59m", 3.days().hours(4).minutes(59)),
/// ("2h30m", 2.hours().minutes(30)),
/// ("2h 30m", 2.hours().minutes(30)),
/// ("1mo", 1.month()),
/// ("1w", 1.week()),
/// ("1 week", 1.week()),
/// ("1w4d", 1.week().days(4)),
/// ("1 wk 4 days", 1.week().days(4)),
/// ("1m", 1.minute()),
/// ("0.0021s", 2.milliseconds().microseconds(100)),
/// ("0s", 0.seconds()),
/// ("0d", 0.seconds()),
/// ("0 days", 0.seconds()),
/// (
/// "1y1mo1d1h1m1.1s",
/// 1.year().months(1).days(1).hours(1).minutes(1).seconds(1).milliseconds(100),
/// ),
/// (
/// "1yr 1mo 1day 1hr 1min 1.1sec",
/// 1.year().months(1).days(1).hours(1).minutes(1).seconds(1).milliseconds(100),
/// ),
/// (
/// "1 year, 1 month, 1 day, 1 hour, 1 minute 1.1 seconds",
/// 1.year().months(1).days(1).hours(1).minutes(1).seconds(1).milliseconds(100),
/// ),
/// (
/// "1 year, 1 month, 1 day, 01:01:01.1",
/// 1.year().months(1).days(1).hours(1).minutes(1).seconds(1).milliseconds(100),
/// ),
/// ];
/// for (string, span) in spans {
/// let parsed: Span = string.parse()?;
/// assert_eq!(
/// span.fieldwise(),
/// parsed.fieldwise(),
/// "result of parsing {string:?}",
/// );
/// }
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// For more details, see the [`fmt::temporal`](temporal) and
/// [`fmt::friendly`](friendly) modules.
///
/// [ISO 8601]: https://www.iso.org/iso-8601-date-and-time-format.html
///
/// # Comparisons
///
/// A `Span` does not implement the `PartialEq` or `Eq` traits. These traits
/// were implemented in an earlier version of Jiff, but they made it too
/// easy to introduce bugs. For example, `120.minutes()` and `2.hours()`
/// always correspond to the same total duration, but they have different
/// representations in memory and so didn't compare equivalent.
///
/// The reason why the `PartialEq` and `Eq` trait implementations do not do
/// comparisons with total duration is because it is fundamentally impossible
/// to do such comparisons without a reference date in all cases.
///
/// However, it is undeniably occasionally useful to do comparisons based
/// on the component fields, so long as such use cases can tolerate two
/// different spans comparing unequal even when their total durations are
/// equivalent. For example, many of the tests in Jiff (including the tests in
/// the documentation) work by comparing a `Span` to an expected result. This
/// is a good demonstration of when fieldwise comparisons are appropriate.
///
/// To do fieldwise comparisons with a span, use the [`Span::fieldwise`]
/// method. This method creates a [`SpanFieldwise`], which is just a `Span`
/// that implements `PartialEq` and `Eq` in a fieldwise manner. In other words,
/// it's a speed bump to ensure this is the kind of comparison you actually
/// want. For example:
///
/// ```
/// use jiff::ToSpan;
///
/// assert_ne!(1.hour().fieldwise(), 60.minutes().fieldwise());
/// // These also work since you only need one fieldwise span to do a compare:
/// assert_ne!(1.hour(), 60.minutes().fieldwise());
/// assert_ne!(1.hour().fieldwise(), 60.minutes());
/// ```
///
/// This is because doing true comparisons requires arithmetic and a relative
/// datetime in the general case, and which can fail due to overflow. This
/// operation is provided via [`Span::compare`]:
///
/// ```
/// use jiff::{civil::date, ToSpan};
///
/// // This doesn't need a reference date since it's only using time units.
/// assert_eq!(1.hour().compare(60.minutes())?, std::cmp::Ordering::Equal);
/// // But if you have calendar units, then you need a
/// // reference date at minimum:
/// assert!(1.month().compare(30.days()).is_err());
/// assert_eq!(
/// 1.month().compare((30.days(), date(2025, 6, 1)))?,
/// std::cmp::Ordering::Equal,
/// );
/// // A month can be a differing number of days!
/// assert_eq!(
/// 1.month().compare((30.days(), date(2025, 7, 1)))?,
/// std::cmp::Ordering::Greater,
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Arithmetic
///
/// Spans can be added or subtracted via [`Span::checked_add`] and
/// [`Span::checked_sub`]:
///
/// ```
/// use jiff::{Span, ToSpan};
///
/// let span1 = 2.hours().minutes(20);
/// let span2: Span = "PT89400s".parse()?;
/// assert_eq!(span1.checked_add(span2)?, 27.hours().minutes(10).fieldwise());
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// When your spans involve calendar units, a relative datetime must be
/// provided. (Because, for example, 1 month from March 1 is 31 days, but
/// 1 month from April 1 is 30 days.)
///
/// ```
/// use jiff::{civil::date, Span, ToSpan};
///
/// let span1 = 2.years().months(6).days(20);
/// let span2 = 400.days();
/// assert_eq!(
/// span1.checked_add((span2, date(2023, 1, 1)))?,
/// 3.years().months(7).days(24).fieldwise(),
/// );
/// // The span changes when a leap year isn't included!
/// assert_eq!(
/// span1.checked_add((span2, date(2025, 1, 1)))?,
/// 3.years().months(7).days(23).fieldwise(),
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Rounding and balancing
///
/// Unlike datetimes, multiple distinct `Span` values can actually correspond
/// to the same duration of time. For example, all of the following correspond
/// to the same duration:
///
/// * 2 hours, 30 minutes
/// * 150 minutes
/// * 1 hour, 90 minutes
///
/// The first is said to be balanced. That is, its biggest non-zero unit cannot
/// be expressed in an integer number of units bigger than hours. But the
/// second is unbalanced because 150 minutes can be split up into hours and
/// minutes. We call this sort of span a "top-heavy" unbalanced span. The third
/// span is also unbalanced, but it's "bottom-heavy" and rarely used. Jiff
/// will generally only produce spans of the first two types. In particular,
/// most `Span` producing APIs accept a "largest" [`Unit`] parameter, and the
/// result can be said to be a span "balanced up to the largest unit provided."
///
/// Balanced and unbalanced spans can be switched between as needed via
/// the [`Span::round`] API by providing a rounding configuration with
/// [`SpanRound::largest`]` set:
///
/// ```
/// use jiff::{SpanRound, ToSpan, Unit};
///
/// let span = 2.hours().minutes(30);
/// let unbalanced = span.round(SpanRound::new().largest(Unit::Minute))?;
/// assert_eq!(unbalanced, 150.minutes().fieldwise());
/// let balanced = unbalanced.round(SpanRound::new().largest(Unit::Hour))?;
/// assert_eq!(balanced, 2.hours().minutes(30).fieldwise());
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// Balancing can also be done as part of computing spans from two datetimes:
///
/// ```
/// use jiff::{civil::date, ToSpan, Unit};
///
/// let zdt1 = date(2024, 7, 7).at(15, 23, 0, 0).in_tz("America/New_York")?;
/// let zdt2 = date(2024, 11, 5).at(8, 0, 0, 0).in_tz("America/New_York")?;
///
/// // To make arithmetic reversible, the default largest unit for spans of
/// // time computed from zoned datetimes is hours:
/// assert_eq!(zdt1.until(&zdt2)?, 2_897.hour().minutes(37).fieldwise());
/// // But we can ask for the span to be balanced up to years:
/// assert_eq!(
/// zdt1.until((Unit::Year, &zdt2))?,
/// 3.months().days(28).hours(16).minutes(37).fieldwise(),
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// While the [`Span::round`] API does balancing, it also, of course, does
/// rounding as well. Rounding occurs when the smallest unit is set to
/// something bigger than [`Unit::Nanosecond`]:
///
/// ```
/// use jiff::{ToSpan, Unit};
///
/// let span = 2.hours().minutes(30);
/// assert_eq!(span.round(Unit::Hour)?, 3.hours().fieldwise());
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// When rounding spans with calendar units (years, months or weeks), then a
/// relative datetime is required:
///
/// ```
/// use jiff::{civil::date, SpanRound, ToSpan, Unit};
///
/// let span = 10.years().months(11);
/// let options = SpanRound::new()
/// .smallest(Unit::Year)
/// .relative(date(2024, 1, 1));
/// assert_eq!(span.round(options)?, 11.years().fieldwise());
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Days are not always 24 hours!
///
/// That is, a `Span` is made up of uniform and non-uniform units.
///
/// A uniform unit is a unit whose elapsed duration is always the same.
/// A non-uniform unit is a unit whose elapsed duration is not always the same.
/// There are two things that can impact the length of a non-uniform unit:
/// the calendar date and the time zone.
///
/// Years and months are always considered non-uniform units. For example,
/// 1 month from `2024-04-01` is 30 days, while 1 month from `2024-05-01` is
/// 31 days. Similarly for years because of leap years.
///
/// Hours, minutes, seconds, milliseconds, microseconds and nanoseconds are
/// always considered uniform units.
///
/// Days are only considered non-uniform when in the presence of a zone aware
/// datetime. A day can be more or less than 24 hours, and it can be balanced
/// up and down, but only when a relative zoned datetime is given. This
/// typically happens because of DST (daylight saving time), but can also occur
/// because of other time zone transitions too.
///
/// ```
/// use jiff::{civil::date, SpanRound, ToSpan, Unit};
///
/// // 2024-03-10 in New York was 23 hours long,
/// // because of a jump to DST at 2am.
/// let zdt = date(2024, 3, 9).at(21, 0, 0, 0).in_tz("America/New_York")?;
/// // Goes from days to hours:
/// assert_eq!(
/// 1.day().round(SpanRound::new().largest(Unit::Hour).relative(&zdt))?,
/// 23.hours().fieldwise(),
/// );
/// // Goes from hours to days:
/// assert_eq!(
/// 23.hours().round(SpanRound::new().largest(Unit::Day).relative(&zdt))?,
/// 1.day().fieldwise(),
/// );
/// // 24 hours is more than 1 day starting at this time:
/// assert_eq!(
/// 24.hours().round(SpanRound::new().largest(Unit::Day).relative(&zdt))?,
/// 1.day().hours(1).fieldwise(),
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// And similarly, days can be longer than 24 hours:
///
/// ```
/// use jiff::{civil::date, SpanRound, ToSpan, Unit};
///
/// // 2024-11-03 in New York was 25 hours long,
/// // because of a repetition of the 1 o'clock AM hour.
/// let zdt = date(2024, 11, 2).at(21, 0, 0, 0).in_tz("America/New_York")?;
/// // Goes from days to hours:
/// assert_eq!(
/// 1.day().round(SpanRound::new().largest(Unit::Hour).relative(&zdt))?,
/// 25.hours().fieldwise(),
/// );
/// // Goes from hours to days:
/// assert_eq!(
/// 25.hours().round(SpanRound::new().largest(Unit::Day).relative(&zdt))?,
/// 1.day().fieldwise(),
/// );
/// // 24 hours is less than 1 day starting at this time,
/// // so it stays in units of hours even though we ask
/// // for days (because 24 isn't enough hours to make
/// // 1 day):
/// assert_eq!(
/// 24.hours().round(SpanRound::new().largest(Unit::Day).relative(&zdt))?,
/// 24.hours().fieldwise(),
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// The APIs on `Span` will otherwise treat days as non-uniform unless a
/// relative civil date is given, or there is an explicit opt-in to invariant
/// 24-hour days. For example:
///
/// ```
/// use jiff::{civil, SpanRelativeTo, ToSpan, Unit};
///
/// let span = 1.day();
///
/// // An error because days aren't always 24 hours:
/// assert_eq!(
/// span.total(Unit::Hour).unwrap_err().to_string(),
/// "using unit 'day' in a span or configuration requires that either \
/// a relative reference time be given or \
/// `SpanRelativeTo::days_are_24_hours()` is used to indicate \
/// invariant 24-hour days, but neither were provided",
/// );
/// // Opt into invariant 24 hour days without a relative date:
/// let marker = SpanRelativeTo::days_are_24_hours();
/// let hours = span.total((Unit::Hour, marker))?;
/// // Or use a relative civil date, and all days are 24 hours:
/// let date = civil::date(2020, 1, 1);
/// let hours = span.total((Unit::Hour, date))?;
/// assert_eq!(hours, 24.0);
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// In Jiff, all weeks are 7 days. And generally speaking, weeks only appear in
/// a `Span` if they were explicitly put there by the caller or if they were
/// explicitly requested by the caller in an API. For example:
///
/// ```
/// use jiff::{civil::date, ToSpan, Unit};
///
/// let dt1 = date(2024, 1, 1).at(0, 0, 0, 0);
/// let dt2 = date(2024, 7, 16).at(0, 0, 0, 0);
/// // Default units go up to days.
/// assert_eq!(dt1.until(dt2)?, 197.days().fieldwise());
/// // No weeks, even though we requested up to year.
/// assert_eq!(dt1.until((Unit::Year, dt2))?, 6.months().days(15).fieldwise());
/// // We get weeks only when we ask for them.
/// assert_eq!(dt1.until((Unit::Week, dt2))?, 28.weeks().days(1).fieldwise());
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Integration with [`std::time::Duration`] and [`SignedDuration`]
///
/// While Jiff primarily uses a `Span` for doing arithmetic on datetimes,
/// one can convert between a `Span` and a [`std::time::Duration`] or a
/// [`SignedDuration`]. The main difference between them is that a `Span`
/// always keeps tracks of its individual units, and a `Span` can represent
/// non-uniform units like months. In contrast, `Duration` and `SignedDuration`
/// are always an exact elapsed amount of time. They don't distinguish between
/// `120 seconds` and `2 minutes`. And they can't represent the concept of
/// "months" because a month doesn't have a single fixed amount of time.
///
/// However, an exact duration is still useful in certain contexts. Beyond
/// that, it serves as an interoperability point due to the presence of an
/// unsigned exact duration type in the standard library. Because of that,
/// Jiff provides `TryFrom` trait implementations for converting to and from a
/// `std::time::Duration` (and, of course, a `SignedDuration`). For example, to
/// convert from a `std::time::Duration` to a `Span`:
///
/// ```
/// use std::time::Duration;
///
/// use jiff::{Span, ToSpan};
///
/// let duration = Duration::new(86_400, 123_456_789);
/// let span = Span::try_from(duration)?;
/// // A duration-to-span conversion always results in a span with
/// // non-zero units no bigger than seconds.
/// assert_eq!(
/// span.fieldwise(),
/// 86_400.seconds().milliseconds(123).microseconds(456).nanoseconds(789),
/// );
///
/// // Note that the conversion is fallible! For example:
/// assert!(Span::try_from(Duration::from_secs(u64::MAX)).is_err());
/// // At present, a Jiff `Span` can only represent a range of time equal to
/// // the range of time expressible via minimum and maximum Jiff timestamps.
/// // Which is roughly -9999-01-01 to 9999-12-31, or ~20,000 years.
/// assert!(Span::try_from(Duration::from_secs(999_999_999_999)).is_err());
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// And to convert from a `Span` to a `std::time::Duration`:
///
/// ```
/// use std::time::Duration;
///
/// use jiff::{Span, ToSpan};
///
/// let span = 86_400.seconds()
/// .milliseconds(123)
/// .microseconds(456)
/// .nanoseconds(789);
/// let duration = Duration::try_from(span)?;
/// assert_eq!(duration, Duration::new(86_400, 123_456_789));
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// Note that an error will occur when converting a `Span` to a
/// `std::time::Duration` using the `TryFrom` trait implementation with units
/// bigger than hours:
///
/// ```
/// use std::time::Duration;
///
/// use jiff::ToSpan;
///
/// let span = 2.days().hours(10);
/// assert_eq!(
/// Duration::try_from(span).unwrap_err().to_string(),
/// "failed to convert span to duration without relative datetime \
/// (must use `Span::to_duration` instead): using unit 'day' in a \
/// span or configuration requires that either a relative reference \
/// time be given or `SpanRelativeTo::days_are_24_hours()` is used \
/// to indicate invariant 24-hour days, but neither were provided",
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// Similar code can be written for `SignedDuration` as well.
///
/// If you need to convert such spans, then as the error suggests, you'll need
/// to use [`Span::to_duration`] with a relative date.
///
/// And note that since a `Span` is signed and a `std::time::Duration` is unsigned,
/// converting a negative `Span` to `std::time::Duration` will always fail. One can use
/// [`Span::signum`] to get the sign of the span and [`Span::abs`] to make the
/// span positive before converting it to a `Duration`:
///
/// ```
/// use std::time::Duration;
///
/// use jiff::{Span, ToSpan};
///
/// let span = -86_400.seconds().nanoseconds(1);
/// let (sign, duration) = (span.signum(), Duration::try_from(span.abs())?);
/// assert_eq!((sign, duration), (-1, Duration::new(86_400, 1)));
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// Or, consider using Jiff's own [`SignedDuration`] instead:
///
/// ```
/// # // See: https://github.com/rust-lang/rust/pull/121364
/// # #![allow(unknown_lints, ambiguous_negative_literals)]
/// use jiff::{SignedDuration, Span, ToSpan};
///
/// let span = -86_400.seconds().nanoseconds(1);
/// let duration = SignedDuration::try_from(span)?;
/// assert_eq!(duration, SignedDuration::new(-86_400, -1));
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[derive(Clone, Copy)]
pub struct Span {
sign: Sign,
years: t::SpanYears,
months: t::SpanMonths,
weeks: t::SpanWeeks,
days: t::SpanDays,
hours: t::SpanHours,
minutes: t::SpanMinutes,
seconds: t::SpanSeconds,
milliseconds: t::SpanMilliseconds,
microseconds: t::SpanMicroseconds,
nanoseconds: t::SpanNanoseconds,
}
/// Infallible routines for setting units on a `Span`.
///
/// These are useful when the units are determined by the programmer or when
/// they have been validated elsewhere. In general, use these routines when
/// constructing an invalid `Span` should be considered a bug in the program.
impl Span {
/// Creates a new span representing a zero duration. That is, a duration
/// in which no time has passed.
pub fn new() -> Span {
Span::default()
}
/// Set the number of years on this span. The value may be negative.
///
/// The fallible version of this method is [`Span::try_years`].
///
/// # Panics
///
/// This panics when the number of years is too small or too big.
/// The minimum value is `-19,998`.
/// The maximum value is `19,998`.
#[inline]
pub fn years<I: Into<i64>>(self, years: I) -> Span {
self.try_years(years).expect("value for years is out of bounds")
}
/// Set the number of months on this span. The value may be negative.
///
/// The fallible version of this method is [`Span::try_months`].
///
/// # Panics
///
/// This panics when the number of months is too small or too big.
/// The minimum value is `-239,976`.
/// The maximum value is `239,976`.
#[inline]
pub fn months<I: Into<i64>>(self, months: I) -> Span {
self.try_months(months).expect("value for months is out of bounds")
}
/// Set the number of weeks on this span. The value may be negative.
///
/// The fallible version of this method is [`Span::try_weeks`].
///
/// # Panics
///
/// This panics when the number of weeks is too small or too big.
/// The minimum value is `-1,043,497`.
/// The maximum value is `1_043_497`.
#[inline]
pub fn weeks<I: Into<i64>>(self, weeks: I) -> Span {
self.try_weeks(weeks).expect("value for weeks is out of bounds")
}
/// Set the number of days on this span. The value may be negative.
///
/// The fallible version of this method is [`Span::try_days`].
///
/// # Panics
///
/// This panics when the number of days is too small or too big.
/// The minimum value is `-7,304,484`.
/// The maximum value is `7,304,484`.
#[inline]
pub fn days<I: Into<i64>>(self, days: I) -> Span {
self.try_days(days).expect("value for days is out of bounds")
}
/// Set the number of hours on this span. The value may be negative.
///
/// The fallible version of this method is [`Span::try_hours`].
///
/// # Panics
///
/// This panics when the number of hours is too small or too big.
/// The minimum value is `-175,307,616`.
/// The maximum value is `175,307,616`.
#[inline]
pub fn hours<I: Into<i64>>(self, hours: I) -> Span {
self.try_hours(hours).expect("value for hours is out of bounds")
}
/// Set the number of minutes on this span. The value may be negative.
///
/// The fallible version of this method is [`Span::try_minutes`].
///
/// # Panics
///
/// This panics when the number of minutes is too small or too big.
/// The minimum value is `-10,518,456,960`.
/// The maximum value is `10,518,456,960`.
#[inline]
pub fn minutes<I: Into<i64>>(self, minutes: I) -> Span {
self.try_minutes(minutes).expect("value for minutes is out of bounds")
}
/// Set the number of seconds on this span. The value may be negative.
///
/// The fallible version of this method is [`Span::try_seconds`].
///
/// # Panics
///
/// This panics when the number of seconds is too small or too big.
/// The minimum value is `-631,107,417,600`.
/// The maximum value is `631,107,417,600`.
#[inline]
pub fn seconds<I: Into<i64>>(self, seconds: I) -> Span {
self.try_seconds(seconds).expect("value for seconds is out of bounds")
}
/// Set the number of milliseconds on this span. The value may be negative.
///
/// The fallible version of this method is [`Span::try_milliseconds`].
///
/// # Panics
///
/// This panics when the number of milliseconds is too small or too big.
/// The minimum value is `-631,107,417,600,000`.
/// The maximum value is `631,107,417,600,000`.
#[inline]
pub fn milliseconds<I: Into<i64>>(self, milliseconds: I) -> Span {
self.try_milliseconds(milliseconds)
.expect("value for milliseconds is out of bounds")
}
/// Set the number of microseconds on this span. The value may be negative.
///
/// The fallible version of this method is [`Span::try_microseconds`].
///
/// # Panics
///
/// This panics when the number of microseconds is too small or too big.
/// The minimum value is `-631,107,417,600,000,000`.
/// The maximum value is `631,107,417,600,000,000`.
#[inline]
pub fn microseconds<I: Into<i64>>(self, microseconds: I) -> Span {
self.try_microseconds(microseconds)
.expect("value for microseconds is out of bounds")
}
/// Set the number of nanoseconds on this span. The value may be negative.
///
/// Note that unlike all other units, a 64-bit integer number of
/// nanoseconds is not big enough to represent all possible spans between
/// all possible datetimes supported by Jiff. This means, for example, that
/// computing a span between two datetimes that are far enough apart _and_
/// requesting a largest unit of [`Unit::Nanosecond`], might return an
/// error due to lack of precision.
///
/// The fallible version of this method is [`Span::try_nanoseconds`].
///
/// # Panics
///
/// This panics when the number of nanoseconds is too small or too big.
/// The minimum value is `-9,223,372,036,854,775,807`.
/// The maximum value is `9,223,372,036,854,775,807`.
#[inline]
pub fn nanoseconds<I: Into<i64>>(self, nanoseconds: I) -> Span {
self.try_nanoseconds(nanoseconds)
.expect("value for nanoseconds is out of bounds")
}
}
/// Fallible methods for setting units on a `Span`.
///
/// These methods are useful when the span is made up of user provided values
/// that may not be in range.
impl Span {
/// Set the number of years on this span. The value may be negative.
///
/// The panicking version of this method is [`Span::years`].
///
/// # Errors
///
/// This returns an error when the number of years is too small or too big.
/// The minimum value is `-19,998`.
/// The maximum value is `19,998`.
#[inline]
pub fn try_years<I: Into<i64>>(self, years: I) -> Result<Span, Error> {
let years = t::SpanYears::try_new("years", years)?;
Ok(self.years_ranged(years))
}
/// Set the number of months on this span. The value may be negative.
///
/// The panicking version of this method is [`Span::months`].
///
/// # Errors
///
/// This returns an error when the number of months is too small or too big.
/// The minimum value is `-239,976`.
/// The maximum value is `239,976`.
#[inline]
pub fn try_months<I: Into<i64>>(self, months: I) -> Result<Span, Error> {
type Range = ri64<{ t::SpanMonths::MIN }, { t::SpanMonths::MAX }>;
let months = Range::try_new("months", months)?;
Ok(self.months_ranged(months))
}
/// Set the number of weeks on this span. The value may be negative.
///
/// The panicking version of this method is [`Span::weeks`].
///
/// # Errors
///
/// This returns an error when the number of weeks is too small or too big.
/// The minimum value is `-1,043,497`.
/// The maximum value is `1_043_497`.
#[inline]
pub fn try_weeks<I: Into<i64>>(self, weeks: I) -> Result<Span, Error> {
type Range = ri64<{ t::SpanWeeks::MIN }, { t::SpanWeeks::MAX }>;
let weeks = Range::try_new("weeks", weeks)?;
Ok(self.weeks_ranged(weeks))
}
/// Set the number of days on this span. The value may be negative.
///
/// The panicking version of this method is [`Span::days`].
///
/// # Errors
///
/// This returns an error when the number of days is too small or too big.
/// The minimum value is `-7,304,484`.
/// The maximum value is `7,304,484`.
#[inline]
pub fn try_days<I: Into<i64>>(self, days: I) -> Result<Span, Error> {
type Range = ri64<{ t::SpanDays::MIN }, { t::SpanDays::MAX }>;
let days = Range::try_new("days", days)?;
Ok(self.days_ranged(days))
}
/// Set the number of hours on this span. The value may be negative.
///
/// The panicking version of this method is [`Span::hours`].
///
/// # Errors
///
/// This returns an error when the number of hours is too small or too big.
/// The minimum value is `-175,307,616`.
/// The maximum value is `175,307,616`.
#[inline]
pub fn try_hours<I: Into<i64>>(self, hours: I) -> Result<Span, Error> {
type Range = ri64<{ t::SpanHours::MIN }, { t::SpanHours::MAX }>;
let hours = Range::try_new("hours", hours)?;
Ok(self.hours_ranged(hours))
}
/// Set the number of minutes on this span. The value may be negative.
///
/// The panicking version of this method is [`Span::minutes`].
///
/// # Errors
///
/// This returns an error when the number of minutes is too small or too big.
/// The minimum value is `-10,518,456,960`.
/// The maximum value is `10,518,456,960`.
#[inline]
pub fn try_minutes<I: Into<i64>>(self, minutes: I) -> Result<Span, Error> {
type Range = ri64<{ t::SpanMinutes::MIN }, { t::SpanMinutes::MAX }>;
let minutes = Range::try_new("minutes", minutes.into())?;
Ok(self.minutes_ranged(minutes))
}
/// Set the number of seconds on this span. The value may be negative.
///
/// The panicking version of this method is [`Span::seconds`].
///
/// # Errors
///
/// This returns an error when the number of seconds is too small or too big.
/// The minimum value is `-631,107,417,600`.
/// The maximum value is `631,107,417,600`.
#[inline]
pub fn try_seconds<I: Into<i64>>(self, seconds: I) -> Result<Span, Error> {
type Range = ri64<{ t::SpanSeconds::MIN }, { t::SpanSeconds::MAX }>;
let seconds = Range::try_new("seconds", seconds.into())?;
Ok(self.seconds_ranged(seconds))
}
/// Set the number of milliseconds on this span. The value may be negative.
///
/// The panicking version of this method is [`Span::milliseconds`].
///
/// # Errors
///
/// This returns an error when the number of milliseconds is too small or
/// too big.
/// The minimum value is `-631,107,417,600,000`.
/// The maximum value is `631,107,417,600,000`.
#[inline]
pub fn try_milliseconds<I: Into<i64>>(
self,
milliseconds: I,
) -> Result<Span, Error> {
type Range =
ri64<{ t::SpanMilliseconds::MIN }, { t::SpanMilliseconds::MAX }>;
let milliseconds =
Range::try_new("milliseconds", milliseconds.into())?;
Ok(self.milliseconds_ranged(milliseconds))
}
/// Set the number of microseconds on this span. The value may be negative.
///
/// The panicking version of this method is [`Span::microseconds`].
///
/// # Errors
///
/// This returns an error when the number of microseconds is too small or
/// too big.
/// The minimum value is `-631,107,417,600,000,000`.
/// The maximum value is `631,107,417,600,000,000`.
#[inline]
pub fn try_microseconds<I: Into<i64>>(
self,
microseconds: I,
) -> Result<Span, Error> {
type Range =
ri64<{ t::SpanMicroseconds::MIN }, { t::SpanMicroseconds::MAX }>;
let microseconds =
Range::try_new("microseconds", microseconds.into())?;
Ok(self.microseconds_ranged(microseconds))
}
/// Set the number of nanoseconds on this span. The value may be negative.
///
/// Note that unlike all other units, a 64-bit integer number of
/// nanoseconds is not big enough to represent all possible spans between
/// all possible datetimes supported by Jiff. This means, for example, that
/// computing a span between two datetimes that are far enough apart _and_
/// requesting a largest unit of [`Unit::Nanosecond`], might return an
/// error due to lack of precision.
///
/// The panicking version of this method is [`Span::nanoseconds`].
///
/// # Errors
///
/// This returns an error when the number of nanoseconds is too small or
/// too big.
/// The minimum value is `-9,223,372,036,854,775,807`.
/// The maximum value is `9,223,372,036,854,775,807`.
#[inline]
pub fn try_nanoseconds<I: Into<i64>>(
self,
nanoseconds: I,
) -> Result<Span, Error> {
type Range =
ri64<{ t::SpanNanoseconds::MIN }, { t::SpanNanoseconds::MAX }>;
let nanoseconds = Range::try_new("nanoseconds", nanoseconds.into())?;
Ok(self.nanoseconds_ranged(nanoseconds))
}
}
/// Routines for accessing the individual units in a `Span`.
impl Span {
/// Returns the number of year units in this span.
///
/// Note that this is not the same as the total number of years in the
/// span. To get that, you'll need to use either [`Span::round`] or
/// [`Span::total`].
///
/// # Example
///
/// ```
/// use jiff::{civil::date, ToSpan, Unit};
///
/// let span = 3.years().months(24);
/// assert_eq!(3, span.get_years());
/// assert_eq!(5.0, span.total((Unit::Year, date(2024, 1, 1)))?);
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[inline]
pub fn get_years(&self) -> i16 {
self.get_years_ranged().get()
}
/// Returns the number of month units in this span.
///
/// Note that this is not the same as the total number of months in the
/// span. To get that, you'll need to use either [`Span::round`] or
/// [`Span::total`].
///
/// # Example
///
/// ```
/// use jiff::{civil::date, ToSpan, Unit};
///
/// let span = 7.months().days(59);
/// assert_eq!(7, span.get_months());
/// assert_eq!(9.0, span.total((Unit::Month, date(2022, 6, 1)))?);
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[inline]
pub fn get_months(&self) -> i32 {
self.get_months_ranged().get()
}
/// Returns the number of week units in this span.
///
/// Note that this is not the same as the total number of weeks in the
/// span. To get that, you'll need to use either [`Span::round`] or
/// [`Span::total`].
///
/// # Example
///
/// ```
/// use jiff::{civil::date, ToSpan, Unit};
///
/// let span = 3.weeks().days(14);
/// assert_eq!(3, span.get_weeks());
/// assert_eq!(5.0, span.total((Unit::Week, date(2024, 1, 1)))?);
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[inline]
pub fn get_weeks(&self) -> i32 {
self.get_weeks_ranged().get()
}
/// Returns the number of day units in this span.
///
/// Note that this is not the same as the total number of days in the
/// span. To get that, you'll need to use either [`Span::round`] or
/// [`Span::total`].
///
/// # Example
///
/// ```
/// use jiff::{ToSpan, Unit, Zoned};
///
/// let span = 3.days().hours(47);
/// assert_eq!(3, span.get_days());
///
/// let zdt: Zoned = "2024-03-07[America/New_York]".parse()?;
/// assert_eq!(5.0, span.total((Unit::Day, &zdt))?);
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[inline]
pub fn get_days(&self) -> i32 {
self.get_days_ranged().get()
}
/// Returns the number of hour units in this span.
///
/// Note that this is not the same as the total number of hours in the
/// span. To get that, you'll need to use either [`Span::round`] or
/// [`Span::total`].
///
/// # Example
///
/// ```
/// use jiff::{ToSpan, Unit};
///
/// let span = 3.hours().minutes(120);
/// assert_eq!(3, span.get_hours());
/// assert_eq!(5.0, span.total(Unit::Hour)?);
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[inline]
pub fn get_hours(&self) -> i32 {
self.get_hours_ranged().get()
}
/// Returns the number of minute units in this span.
///
/// Note that this is not the same as the total number of minutes in the
/// span. To get that, you'll need to use either [`Span::round`] or
/// [`Span::total`].
///
/// # Example
///
/// ```
/// use jiff::{ToSpan, Unit};
///
/// let span = 3.minutes().seconds(120);
/// assert_eq!(3, span.get_minutes());
/// assert_eq!(5.0, span.total(Unit::Minute)?);
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[inline]
pub fn get_minutes(&self) -> i64 {
self.get_minutes_ranged().get()
}
/// Returns the number of second units in this span.
///
/// Note that this is not the same as the total number of seconds in the
/// span. To get that, you'll need to use either [`Span::round`] or
/// [`Span::total`].
///
/// # Example
///
/// ```
/// use jiff::{ToSpan, Unit};
///
/// let span = 3.seconds().milliseconds(2_000);
/// assert_eq!(3, span.get_seconds());
/// assert_eq!(5.0, span.total(Unit::Second)?);
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[inline]
pub fn get_seconds(&self) -> i64 {
self.get_seconds_ranged().get()
}
/// Returns the number of millisecond units in this span.
///
/// Note that this is not the same as the total number of milliseconds in
/// the span. To get that, you'll need to use either [`Span::round`] or
/// [`Span::total`].
///
/// # Example
///
/// ```
/// use jiff::{ToSpan, Unit};
///
/// let span = 3.milliseconds().microseconds(2_000);
/// assert_eq!(3, span.get_milliseconds());
/// assert_eq!(5.0, span.total(Unit::Millisecond)?);
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[inline]
pub fn get_milliseconds(&self) -> i64 {
self.get_milliseconds_ranged().get()
}
/// Returns the number of microsecond units in this span.
///
/// Note that this is not the same as the total number of microseconds in
/// the span. To get that, you'll need to use either [`Span::round`] or
/// [`Span::total`].
///
/// # Example
///
/// ```
/// use jiff::{ToSpan, Unit};
///
/// let span = 3.microseconds().nanoseconds(2_000);
/// assert_eq!(3, span.get_microseconds());
/// assert_eq!(5.0, span.total(Unit::Microsecond)?);
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[inline]
pub fn get_microseconds(&self) -> i64 {
self.get_microseconds_ranged().get()
}
/// Returns the number of nanosecond units in this span.
///
/// Note that this is not the same as the total number of nanoseconds in
/// the span. To get that, you'll need to use either [`Span::round`] or
/// [`Span::total`].
///
/// # Example
///
/// ```
/// use jiff::{ToSpan, Unit};
///
/// let span = 3.microseconds().nanoseconds(2_000);
/// assert_eq!(2_000, span.get_nanoseconds());
/// assert_eq!(5_000.0, span.total(Unit::Nanosecond)?);
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[inline]
pub fn get_nanoseconds(&self) -> i64 {
self.get_nanoseconds_ranged().get()
}
}
/// Routines for manipulating, comparing and inspecting `Span` values.
impl Span {
/// Returns a new span that is the absolute value of this span.
///
/// If this span is zero or positive, then this is a no-op.
///
/// # Example
///
/// ```
/// use jiff::ToSpan;
///
/// let span = -100.seconds();
/// assert_eq!(span.to_string(), "-PT100S");
/// let span = span.abs();
/// assert_eq!(span.to_string(), "PT100S");
/// ```
#[inline]
pub fn abs(self) -> Span {
if self.is_zero() {
return self;
}
Span { sign: ri8::N::<1>(), ..self }
}
/// Returns a new span that negates this span.
///
/// If this span is zero, then this is a no-op. If this span is negative,
/// then the returned span is positive. If this span is positive, then
/// the returned span is negative.
///
/// # Example
///
/// ```
/// use jiff::ToSpan;
///
/// let span = 100.days();
/// assert_eq!(span.to_string(), "P100D");
/// let span = span.negate();
/// assert_eq!(span.to_string(), "-P100D");
/// ```
///
/// # Example: available via the negation operator
///
/// This routine can also be used via `-`:
///
/// ```
/// use jiff::ToSpan;
///
/// let span = 100.days();
/// assert_eq!(span.to_string(), "P100D");
/// let span = -span;
/// assert_eq!(span.to_string(), "-P100D");
/// ```
#[inline]
pub fn negate(self) -> Span {
Span { sign: -self.sign, ..self }
}
/// Returns the "sign number" or "signum" of this span.
///
/// The number returned is `-1` when this span is negative,
/// `0` when this span is zero and `1` when this span is positive.
#[inline]
pub fn signum(self) -> i8 {
self.sign.signum().get()
}
/// Returns true if and only if this span is positive.
///
/// This returns false when the span is zero or negative.
///
/// # Example
///
/// ```
/// use jiff::ToSpan;
///
/// assert!(!2.months().is_negative());
/// assert!((-2.months()).is_negative());
/// ```
#[inline]
pub fn is_positive(self) -> bool {
self.get_sign_ranged() > 0
}
/// Returns true if and only if this span is negative.
///
/// This returns false when the span is zero or positive.
///
/// # Example
///
/// ```
/// use jiff::ToSpan;
///
/// assert!(!2.months().is_negative());
/// assert!((-2.months()).is_negative());
/// ```
#[inline]
pub fn is_negative(self) -> bool {
self.get_sign_ranged() < 0
}
/// Returns true if and only if every field in this span is set to `0`.
///
/// # Example
///
/// ```
/// use jiff::{Span, ToSpan};
///
/// assert!(Span::new().is_zero());
/// assert!(Span::default().is_zero());
/// assert!(0.seconds().is_zero());
/// assert!(!0.seconds().seconds(1).is_zero());
/// assert!(0.seconds().seconds(1).seconds(0).is_zero());
/// ```
#[inline]
pub fn is_zero(self) -> bool {
self.sign == 0
}
/// Returns this `Span` as a value with a type that implements the
/// `Hash`, `Eq` and `PartialEq` traits in a fieldwise fashion.
///
/// A `SpanFieldwise` is meant to make it easy to compare two spans in a
/// "dumb" way based purely on its unit values. This is distinct from
/// something like [`Span::compare`] that performs a comparison on the
/// actual elapsed time of two spans.
///
/// It is generally discouraged to use `SpanFieldwise` since spans that
/// represent an equivalent elapsed amount of time may compare unequal.
/// However, in some cases, it is useful to be able to assert precise
/// field values. For example, Jiff itself makes heavy use of fieldwise
/// comparisons for tests.
///
/// # Example: the difference between `SpanFieldwise` and `Span::compare`
///
/// In short, `SpanFieldwise` considers `2 hours` and `120 minutes` to be
/// distinct values, but `Span::compare` considers them to be equivalent:
///
/// ```
/// use std::cmp::Ordering;
/// use jiff::ToSpan;
///
/// assert_ne!(120.minutes().fieldwise(), 2.hours().fieldwise());
/// assert_eq!(120.minutes().compare(2.hours())?, Ordering::Equal);
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[inline]
pub fn fieldwise(self) -> SpanFieldwise {
SpanFieldwise(self)
}
/// Multiplies each field in this span by a given integer.
///
/// If this would cause any individual field in this span to overflow, then
/// this returns an error.
///
/// # Example
///
/// ```
/// use jiff::ToSpan;
///
/// let span = 4.days().seconds(8);
/// assert_eq!(span.checked_mul(2)?, 8.days().seconds(16).fieldwise());
/// assert_eq!(span.checked_mul(-3)?, -12.days().seconds(24).fieldwise());
/// // Notice that no re-balancing is done. It's "just" multiplication.
/// assert_eq!(span.checked_mul(10)?, 40.days().seconds(80).fieldwise());
///
/// let span = 10_000.years();
/// // too big!
/// assert!(span.checked_mul(3).is_err());
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Example: available via the multiplication operator
///
/// This method can be used via the `*` operator. Note though that a panic
/// happens on overflow.
///
/// ```
/// use jiff::ToSpan;
///
/// let span = 4.days().seconds(8);
/// assert_eq!(span * 2, 8.days().seconds(16).fieldwise());
/// assert_eq!(2 * span, 8.days().seconds(16).fieldwise());
/// assert_eq!(span * -3, -12.days().seconds(24).fieldwise());
/// assert_eq!(-3 * span, -12.days().seconds(24).fieldwise());
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[inline]
pub fn checked_mul(mut self, rhs: i64) -> Result<Span, Error> {
if rhs == 0 {
return Ok(Span::default());
} else if rhs == 1 {
return Ok(self);
}
self.sign *= t::Sign::try_new("span factor", rhs.signum())
.expect("signum fits in ri8");
// This is all somewhat odd, but since each of our span fields uses
// a different primitive representation and range of allowed values,
// we only seek to perform multiplications when they will actually
// do something. Otherwise, we risk multiplying the mins/maxs of a
// ranged integer and causing a spurious panic. Basically, the idea
// here is the allowable values for our multiple depend on what we're
// actually going to multiply with it. If our span has non-zero years,
// then our multiple can't exceed the bounds of `SpanYears`, otherwise
// it is guaranteed to overflow.
if self.years != 0 {
let rhs = t::SpanYears::try_new("years multiple", rhs)?;
self.years = self.years.try_checked_mul("years", rhs.abs())?;
}
if self.months != 0 {
let rhs = t::SpanMonths::try_new("months multiple", rhs)?;
self.months = self.months.try_checked_mul("months", rhs.abs())?;
}
if self.weeks != 0 {
let rhs = t::SpanWeeks::try_new("weeks multiple", rhs)?;
self.weeks = self.weeks.try_checked_mul("weeks", rhs.abs())?;
}
if self.days != 0 {
let rhs = t::SpanDays::try_new("days multiple", rhs)?;
self.days = self.days.try_checked_mul("days", rhs.abs())?;
}
if self.hours != 0 {
let rhs = t::SpanHours::try_new("hours multiple", rhs)?;
self.hours = self.hours.try_checked_mul("hours", rhs.abs())?;
}
if self.minutes != 0 {
let rhs = t::SpanMinutes::try_new("minutes multiple", rhs)?;
self.minutes =
self.minutes.try_checked_mul("minutes", rhs.abs())?;
}
if self.seconds != 0 {
let rhs = t::SpanSeconds::try_new("seconds multiple", rhs)?;
self.seconds =
self.seconds.try_checked_mul("seconds", rhs.abs())?;
}
if self.milliseconds != 0 {
let rhs =
t::SpanMilliseconds::try_new("milliseconds multiple", rhs)?;
self.milliseconds = self
.milliseconds
.try_checked_mul("milliseconds", rhs.abs())?;
}
if self.microseconds != 0 {
let rhs =
t::SpanMicroseconds::try_new("microseconds multiple", rhs)?;
self.microseconds = self
.microseconds
.try_checked_mul("microseconds", rhs.abs())?;
}
if self.nanoseconds != 0 {
let rhs =
t::SpanNanoseconds::try_new("nanoseconds multiple", rhs)?;
self.nanoseconds =
self.nanoseconds.try_checked_mul("nanoseconds", rhs.abs())?;
}
Ok(self)
}
/// Adds a span to this one and returns the sum as a new span.
///
/// When adding a span with units greater than hours, callers must provide
/// a relative datetime to anchor the spans.
///
/// Arithmetic proceeds as specified in [RFC 5545]. Bigger units are
/// added together before smaller units.
///
/// This routine accepts anything that implements `Into<SpanArithmetic>`.
/// There are some trait implementations that make using this routine
/// ergonomic:
///
/// * `From<Span> for SpanArithmetic` adds the given span to this one.
/// * `From<(Span, civil::Date)> for SpanArithmetic` adds the given
/// span to this one relative to the given date. There are also `From`
/// implementations for `civil::DateTime` and `Zoned`.
///
/// This also works with different duration types, such as
/// [`SignedDuration`] and [`std::time::Duration`], via additional trait
/// implementations:
///
/// * `From<SignedDuration> for SpanArithmetic` adds the given duration to
/// this one.
/// * `From<(SignedDuration, civil::Date)> for SpanArithmetic` adds the
/// given duration to this one relative to the given date. There are also
/// `From` implementations for `civil::DateTime` and `Zoned`.
///
/// And similarly for `std::time::Duration`.
///
/// Adding a negative span is equivalent to subtracting its absolute value.
///
/// The largest non-zero unit in the span returned is at most the largest
/// non-zero unit among the two spans being added. For an absolute
/// duration, its "largest" unit is considered to be nanoseconds.
///
/// The sum returned is automatically re-balanced so that the span is not
/// "bottom heavy."
///
/// [RFC 5545]: https://datatracker.ietf.org/doc/html/rfc5545
///
/// # Errors
///
/// This returns an error when adding the two spans would overflow any
/// individual field of a span. This will also return an error if either
/// of the spans have non-zero units of days or greater and no relative
/// reference time is provided.
///
/// Callers may use [`SpanArithmetic::days_are_24_hours`] as a special
/// marker instead of providing a relative civil date to indicate that
/// all days should be 24 hours long. This also results in treating all
/// weeks as seven 24 hour days (168 hours).
///
/// # Example
///
/// ```
/// use jiff::ToSpan;
///
/// assert_eq!(
/// 1.hour().checked_add(30.minutes())?,
/// 1.hour().minutes(30).fieldwise(),
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Example: re-balancing
///
/// This example shows how units are automatically rebalanced into bigger
/// units when appropriate.
///
/// ```
/// use jiff::ToSpan;
///
/// let span1 = 2.hours().minutes(59);
/// let span2 = 2.minutes();
/// assert_eq!(span1.checked_add(span2)?, 3.hours().minutes(1).fieldwise());
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Example: days are not assumed to be 24 hours by default
///
/// When dealing with units involving days or weeks, one must either
/// provide a relative datetime (shown in the following examples) or opt
/// into invariant 24 hour days:
///
/// ```
/// use jiff::{SpanRelativeTo, ToSpan};
///
/// let span1 = 2.days().hours(23);
/// let span2 = 2.hours();
/// assert_eq!(
/// span1.checked_add((span2, SpanRelativeTo::days_are_24_hours()))?,
/// 3.days().hours(1).fieldwise(),
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Example: adding spans with calendar units
///
/// If you try to add two spans with calendar units without specifying a
/// relative datetime, you'll get an error:
///
/// ```
/// use jiff::ToSpan;
///
/// let span1 = 1.month().days(15);
/// let span2 = 15.days();
/// assert!(span1.checked_add(span2).is_err());
/// ```
///
/// A relative datetime is needed because calendar spans may correspond to
/// different actual durations depending on where the span begins:
///
/// ```
/// use jiff::{civil::date, ToSpan};
///
/// let span1 = 1.month().days(15);
/// let span2 = 15.days();
/// // 1 month from March 1 is 31 days...
/// assert_eq!(
/// span1.checked_add((span2, date(2008, 3, 1)))?,
/// 2.months().fieldwise(),
/// );
/// // ... but 1 month from April 1 is 30 days!
/// assert_eq!(
/// span1.checked_add((span2, date(2008, 4, 1)))?,
/// 1.month().days(30).fieldwise(),
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Example: error on overflow
///
/// Adding two spans can overflow, and this will result in an error:
///
/// ```
/// use jiff::ToSpan;
///
/// assert!(19_998.years().checked_add(1.year()).is_err());
/// ```
///
/// # Example: adding an absolute duration to a span
///
/// This shows how one isn't limited to just adding two spans together.
/// One can also add absolute durations to a span.
///
/// ```
/// use std::time::Duration;
///
/// use jiff::{SignedDuration, ToSpan};
///
/// assert_eq!(
/// 1.hour().checked_add(SignedDuration::from_mins(30))?,
/// 1.hour().minutes(30).fieldwise(),
/// );
/// assert_eq!(
/// 1.hour().checked_add(Duration::from_secs(30 * 60))?,
/// 1.hour().minutes(30).fieldwise(),
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// Note that even when adding an absolute duration, if the span contains
/// non-uniform units, you still need to provide a relative datetime:
///
/// ```
/// use jiff::{civil::date, SignedDuration, ToSpan};
///
/// // Might be 1 month or less than 1 month!
/// let dur = SignedDuration::from_hours(30 * 24);
/// // No relative datetime provided even when the span
/// // contains non-uniform units results in an error.
/// assert!(1.month().checked_add(dur).is_err());
/// // In this case, 30 days is one month (April).
/// assert_eq!(
/// 1.month().checked_add((dur, date(2024, 3, 1)))?,
/// 2.months().fieldwise(),
/// );
/// // In this case, 30 days is less than one month (May).
/// assert_eq!(
/// 1.month().checked_add((dur, date(2024, 4, 1)))?,
/// 1.month().days(30).fieldwise(),
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[inline]
pub fn checked_add<'a, A: Into<SpanArithmetic<'a>>>(
&self,
options: A,
) -> Result<Span, Error> {
let options: SpanArithmetic<'_> = options.into();
options.checked_add(*self)
}
#[inline]
fn checked_add_span<'a>(
&self,
relative: Option<SpanRelativeTo<'a>>,
span: &Span,
) -> Result<Span, Error> {
let (span1, span2) = (*self, *span);
let unit = span1.largest_unit().max(span2.largest_unit());
let start = match relative {
Some(r) => match r.to_relative(unit)? {
None => return span1.checked_add_invariant(unit, &span2),
Some(r) => r,
},
None => {
requires_relative_date_err(unit)?;
return span1.checked_add_invariant(unit, &span2);
}
};
let mid = start.checked_add(span1)?;
let end = mid.checked_add(span2)?;
start.until(unit, &end)
}
#[inline]
fn checked_add_duration<'a>(
&self,
relative: Option<SpanRelativeTo<'a>>,
duration: SignedDuration,
) -> Result<Span, Error> {
let (span1, dur2) = (*self, duration);
let unit = span1.largest_unit();
let start = match relative {
Some(r) => match r.to_relative(unit)? {
None => {
return span1.checked_add_invariant_duration(unit, dur2)
}
Some(r) => r,
},
None => {
requires_relative_date_err(unit)?;
return span1.checked_add_invariant_duration(unit, dur2);
}
};
let mid = start.checked_add(span1)?;
let end = mid.checked_add_duration(dur2)?;
start.until(unit, &end)
}
/// Like `checked_add`, but only applies for invariant units. That is,
/// when *both* spans whose non-zero units are all hours or smaller
/// (or weeks or smaller with the "days are 24 hours" marker).
#[inline]
fn checked_add_invariant(
&self,
unit: Unit,
span: &Span,
) -> Result<Span, Error> {
assert!(unit <= Unit::Week);
let nanos1 = self.to_invariant_nanoseconds();
let nanos2 = span.to_invariant_nanoseconds();
let sum = nanos1 + nanos2;
Span::from_invariant_nanoseconds(unit, sum)
}
/// Like `checked_add_invariant`, but adds an absolute duration.
#[inline]
fn checked_add_invariant_duration(
&self,
unit: Unit,
duration: SignedDuration,
) -> Result<Span, Error> {
assert!(unit <= Unit::Week);
let nanos1 = self.to_invariant_nanoseconds();
let nanos2 = t::NoUnits96::new_unchecked(duration.as_nanos());
let sum = nanos1 + nanos2;
Span::from_invariant_nanoseconds(unit, sum)
}
/// This routine is identical to [`Span::checked_add`] with the given
/// duration negated.
///
/// # Errors
///
/// This has the same error conditions as [`Span::checked_add`].
///
/// # Example
///
/// ```
/// use std::time::Duration;
///
/// use jiff::{SignedDuration, ToSpan};
///
/// assert_eq!(
/// 1.hour().checked_sub(30.minutes())?,
/// 30.minutes().fieldwise(),
/// );
/// assert_eq!(
/// 1.hour().checked_sub(SignedDuration::from_mins(30))?,
/// 30.minutes().fieldwise(),
/// );
/// assert_eq!(
/// 1.hour().checked_sub(Duration::from_secs(30 * 60))?,
/// 30.minutes().fieldwise(),
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[inline]
pub fn checked_sub<'a, A: Into<SpanArithmetic<'a>>>(
&self,
options: A,
) -> Result<Span, Error> {
let mut options: SpanArithmetic<'_> = options.into();
options.duration = options.duration.checked_neg()?;
options.checked_add(*self)
}
/// Compares two spans in terms of how long they are. Negative spans are
/// considered shorter than the zero span.
///
/// Two spans compare equal when they correspond to the same duration
/// of time, even if their individual fields are different. This is in
/// contrast to the `Eq` trait implementation of `Span`, which performs
/// exact field-wise comparisons. This split exists because the comparison
/// provided by this routine is "heavy" in that it may need to do
/// datetime arithmetic to return an answer. In contrast, the `Eq` trait
/// implementation is "cheap."
///
/// This routine accepts anything that implements `Into<SpanCompare>`.
/// There are some trait implementations that make using this routine
/// ergonomic:
///
/// * `From<Span> for SpanCompare` compares the given span to this one.
/// * `From<(Span, civil::Date)> for SpanArithmetic` compares the given
/// span to this one relative to the given date. There are also `From`
/// implementations for `civil::DateTime` and `Zoned`.
///
/// # Errors
///
/// If either of the spans being compared have a non-zero calendar unit
/// (units bigger than hours), then this routine requires a relative
/// datetime. If one is not provided, then an error is returned.
///
/// An error can also occur when adding either span to the relative
/// datetime given results in overflow.
///
/// Callers may use [`SpanArithmetic::days_are_24_hours`] as a special
/// marker instead of providing a relative civil date to indicate that
/// all days should be 24 hours long. This also results in treating all
/// weeks as seven 24 hour days (168 hours).
///
/// # Example
///
/// ```
/// use jiff::ToSpan;
///
/// let span1 = 3.hours();
/// let span2 = 180.minutes();
/// assert_eq!(span1.compare(span2)?, std::cmp::Ordering::Equal);
/// // But notice that the two spans are not equal via `Eq`:
/// assert_ne!(span1.fieldwise(), span2.fieldwise());
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Example: negative spans are less than zero
///
/// ```
/// use jiff::ToSpan;
///
/// let span1 = -1.second();
/// let span2 = 0.seconds();
/// assert_eq!(span1.compare(span2)?, std::cmp::Ordering::Less);
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Example: comparisons take DST into account
///
/// When a relative datetime is time zone aware, then DST is taken into
/// account when comparing spans:
///
/// ```
/// use jiff::{civil, ToSpan, Zoned};
///
/// let span1 = 79.hours().minutes(10);
/// let span2 = 3.days().hours(7).seconds(630);
/// let span3 = 3.days().hours(6).minutes(50);
///
/// let relative: Zoned = "2020-11-01T00-07[America/Los_Angeles]".parse()?;
/// let mut spans = [span1, span2, span3];
/// spans.sort_by(|s1, s2| s1.compare((s2, &relative)).unwrap());
/// assert_eq!(
/// spans.map(|sp| sp.fieldwise()),
/// [span1.fieldwise(), span3.fieldwise(), span2.fieldwise()],
/// );
///
/// // Compare with the result of sorting without taking DST into account.
/// // We can that by providing a relative civil date:
/// let relative = civil::date(2020, 11, 1);
/// spans.sort_by(|s1, s2| s1.compare((s2, relative)).unwrap());
/// assert_eq!(
/// spans.map(|sp| sp.fieldwise()),
/// [span3.fieldwise(), span1.fieldwise(), span2.fieldwise()],
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// See the examples for [`Span::total`] if you want to sort spans without
/// an `unwrap()` call.
#[inline]
pub fn compare<'a, C: Into<SpanCompare<'a>>>(
&self,
options: C,
) -> Result<Ordering, Error> {
let options: SpanCompare<'_> = options.into();
options.compare(*self)
}
/// Returns a floating point number representing the total number of a
/// specific unit (as given) in this span. If the span is not evenly
/// divisible by the requested units, then the number returned may have a
/// fractional component.
///
/// This routine accepts anything that implements `Into<SpanTotal>`. There
/// are some trait implementations that make using this routine ergonomic:
///
/// * `From<Unit> for SpanTotal` computes a total for the given unit in
/// this span.
/// * `From<(Unit, civil::Date)> for SpanTotal` computes a total for the
/// given unit in this span, relative to the given date. There are also
/// `From` implementations for `civil::DateTime` and `Zoned`.
///
/// # Errors
///
/// If this span has any non-zero calendar unit (units bigger than hours),
/// then this routine requires a relative datetime. If one is not provided,
/// then an error is returned.
///
/// An error can also occur when adding the span to the relative
/// datetime given results in overflow.
///
/// Callers may use [`SpanArithmetic::days_are_24_hours`] as a special
/// marker instead of providing a relative civil date to indicate that
/// all days should be 24 hours long. This also results in treating all
/// weeks as seven 24 hour days (168 hours).
///
/// # Example
///
/// This example shows how to find the number of seconds in a particular
/// span:
///
/// ```
/// use jiff::{ToSpan, Unit};
///
/// let span = 3.hours().minutes(10);
/// assert_eq!(span.total(Unit::Second)?, 11_400.0);
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Example: 24 hour days
///
/// This shows how to find the total number of 24 hour days in
/// `123,456,789` seconds.
///
/// ```
/// use jiff::{SpanTotal, ToSpan, Unit};
///
/// let span = 123_456_789.seconds();
/// assert_eq!(
/// span.total(SpanTotal::from(Unit::Day).days_are_24_hours())?,
/// 1428.8980208333332,
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Example: DST is taken into account
///
/// The month of March 2024 in `America/New_York` had 31 days, but one of
/// those days was 23 hours long due a transition into daylight saving
/// time:
///
/// ```
/// use jiff::{civil::date, ToSpan, Unit};
///
/// let span = 744.hours();
/// let relative = date(2024, 3, 1).in_tz("America/New_York")?;
/// // Because of the short day, 744 hours is actually a little *more* than
/// // 1 month starting from 2024-03-01.
/// assert_eq!(span.total((Unit::Month, &relative))?, 1.0013888888888889);
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// Now compare what happens when the relative datetime is civil and not
/// time zone aware:
///
/// ```
/// use jiff::{civil::date, ToSpan, Unit};
///
/// let span = 744.hours();
/// let relative = date(2024, 3, 1);
/// assert_eq!(span.total((Unit::Month, relative))?, 1.0);
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Example: infallible sorting
///
/// The sorting example in [`Span::compare`] has to use `unwrap()` in
/// its `sort_by(..)` call because `Span::compare` may fail and there
/// is no "fallible" sorting routine in Rust's standard library (as of
/// 2024-07-07). While the ways in which `Span::compare` can fail for
/// a valid configuration are limited to overflow for "extreme" values, it
/// is possible to sort spans infallibly by computing floating point
/// representations for each span up-front:
///
/// ```
/// use jiff::{civil::Date, ToSpan, Unit, Zoned};
///
/// let span1 = 79.hours().minutes(10);
/// let span2 = 3.days().hours(7).seconds(630);
/// let span3 = 3.days().hours(6).minutes(50);
///
/// let relative: Zoned = "2020-11-01T00-07[America/Los_Angeles]".parse()?;
/// let mut spans = [
/// (span1, span1.total((Unit::Day, &relative))?),
/// (span2, span2.total((Unit::Day, &relative))?),
/// (span3, span3.total((Unit::Day, &relative))?),
/// ];
/// spans.sort_by(|&(_, total1), &(_, total2)| total1.total_cmp(&total2));
/// assert_eq!(
/// spans.map(|(sp, _)| sp.fieldwise()),
/// [span1.fieldwise(), span3.fieldwise(), span2.fieldwise()],
/// );
///
/// // Compare with the result of sorting without taking DST into account.
/// // We do that here by providing a relative civil date.
/// let relative: Date = "2020-11-01".parse()?;
/// let mut spans = [
/// (span1, span1.total((Unit::Day, relative))?),
/// (span2, span2.total((Unit::Day, relative))?),
/// (span3, span3.total((Unit::Day, relative))?),
/// ];
/// spans.sort_by(|&(_, total1), &(_, total2)| total1.total_cmp(&total2));
/// assert_eq!(
/// spans.map(|(sp, _)| sp.fieldwise()),
/// [span3.fieldwise(), span1.fieldwise(), span2.fieldwise()],
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[inline]
pub fn total<'a, T: Into<SpanTotal<'a>>>(
&self,
options: T,
) -> Result<f64, Error> {
let options: SpanTotal<'_> = options.into();
options.total(*self)
}
/// Returns a new span that is balanced and rounded.
///
/// Rounding a span has a number of parameters, all of which are optional.
/// When no parameters are given, then no rounding or balancing is done,
/// and the span as given is returned. That is, it's a no-op.
///
/// The parameters are, in brief:
///
/// * [`SpanRound::largest`] sets the largest [`Unit`] that is allowed to
/// be non-zero in the span returned. When _only_ the largest unit is set,
/// rounding itself doesn't occur and instead the span is merely balanced.
/// * [`SpanRound::smallest`] sets the smallest [`Unit`] that is allowed to
/// be non-zero in the span returned. By default, it is set to
/// [`Unit::Nanosecond`], i.e., no rounding occurs. When the smallest unit
/// is set to something bigger than nanoseconds, then the non-zero units
/// in the span smaller than the smallest unit are used to determine how
/// the span should be rounded. For example, rounding `1 hour 59 minutes`
/// to the nearest hour using the default rounding mode would produce
/// `2 hours`.
/// * [`SpanRound::mode`] determines how to handle the remainder when
/// rounding. The default is [`RoundMode::HalfExpand`], which corresponds
/// to how you were taught to round in school. Alternative modes, like
/// [`RoundMode::Trunc`], exist too. For example, a truncating rounding of
/// `1 hour 59 minutes` to the nearest hour would produce `1 hour`.
/// * [`SpanRound::increment`] sets the rounding granularity to use for
/// the configured smallest unit. For example, if the smallest unit is
/// minutes and the increment is 5, then the span returned will always have
/// its minute units set to a multiple of `5`.
/// * [`SpanRound::relative`] sets the datetime from which to interpret the
/// span. This is required when rounding spans with calendar units (years,
/// months or weeks). When a relative datetime is time zone aware, then
/// rounding accounts for the fact that not all days are 24 hours long.
/// When a relative datetime is omitted or is civil (not time zone aware),
/// then days are always 24 hours long.
///
/// # Constructing a [`SpanRound`]
///
/// This routine accepts anything that implements `Into<SpanRound>`. There
/// are a few key trait implementations that make this convenient:
///
/// * `From<Unit> for SpanRound` will construct a rounding configuration
/// where the smallest unit is set to the one given.
/// * `From<(Unit, i64)> for SpanRound` will construct a rounding
/// configuration where the smallest unit and the rounding increment are
/// set to the ones given.
///
/// To set other options (like the largest unit, the rounding mode and the
/// relative datetime), one must explicitly create a `SpanRound` and pass
/// it to this routine.
///
/// # Errors
///
/// In general, there are two main ways for rounding to fail: an improper
/// configuration like trying to round a span with calendar units but
/// without a relative datetime, or when overflow occurs. Overflow can
/// occur when the span, added to the relative datetime if given, would
/// exceed the minimum or maximum datetime values. Overflow can also occur
/// if the span is too big to fit into the requested unit configuration.
/// For example, a span like `19_998.years()` cannot be represented with a
/// 64-bit integer number of nanoseconds.
///
/// Callers may use [`SpanArithmetic::days_are_24_hours`] as a special
/// marker instead of providing a relative civil date to indicate that
/// all days should be 24 hours long. This also results in treating all
/// weeks as seven 24 hour days (168 hours).
///
/// # Example: balancing
///
/// This example demonstrates balancing, not rounding. And in particular,
/// this example shows how to balance a span as much as possible (i.e.,
/// with units of hours or smaller) without needing to specify a relative
/// datetime:
///
/// ```
/// use jiff::{SpanRound, ToSpan, Unit};
///
/// let span = 123_456_789_123_456_789i64.nanoseconds();
/// assert_eq!(
/// span.round(SpanRound::new().largest(Unit::Hour))?.fieldwise(),
/// 34_293.hours().minutes(33).seconds(9)
/// .milliseconds(123).microseconds(456).nanoseconds(789),
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// Or you can opt into invariant 24-hour days (and 7-day weeks) without a
/// relative date with [`SpanRound::days_are_24_hours`]:
///
/// ```
/// use jiff::{SpanRound, ToSpan, Unit};
///
/// let span = 123_456_789_123_456_789i64.nanoseconds();
/// assert_eq!(
/// span.round(
/// SpanRound::new().largest(Unit::Day).days_are_24_hours(),
/// )?.fieldwise(),
/// 1_428.days()
/// .hours(21).minutes(33).seconds(9)
/// .milliseconds(123).microseconds(456).nanoseconds(789),
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Example: balancing and rounding
///
/// This example is like the one before it, but where we round to the
/// nearest second:
///
/// ```
/// use jiff::{SpanRound, ToSpan, Unit};
///
/// let span = 123_456_789_123_456_789i64.nanoseconds();
/// assert_eq!(
/// span.round(SpanRound::new().largest(Unit::Hour).smallest(Unit::Second))?,
/// 34_293.hours().minutes(33).seconds(9).fieldwise(),
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// Or, just rounding to the nearest hour can make use of the
/// `From<Unit> for SpanRound` trait implementation:
///
/// ```
/// use jiff::{ToSpan, Unit};
///
/// let span = 123_456_789_123_456_789i64.nanoseconds();
/// assert_eq!(span.round(Unit::Hour)?, 34_294.hours().fieldwise());
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Example: balancing with a relative datetime
///
/// Even with calendar units, so long as a relative datetime is provided,
/// it's easy to turn days into bigger units:
///
/// ```
/// use jiff::{civil::date, SpanRound, ToSpan, Unit};
///
/// let span = 1_000.days();
/// let relative = date(2000, 1, 1);
/// let options = SpanRound::new().largest(Unit::Year).relative(relative);
/// assert_eq!(span.round(options)?, 2.years().months(8).days(26).fieldwise());
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Example: round to the nearest half-hour
///
/// ```
/// use jiff::{Span, ToSpan, Unit};
///
/// let span: Span = "PT23h50m3.123s".parse()?;
/// assert_eq!(span.round((Unit::Minute, 30))?, 24.hours().fieldwise());
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Example: yearly quarters in a span
///
/// This example shows how to find how many full 3 month quarters are in a
/// particular span of time.
///
/// ```
/// use jiff::{civil::date, RoundMode, SpanRound, ToSpan, Unit};
///
/// let span1 = 10.months().days(15);
/// let round = SpanRound::new()
/// .smallest(Unit::Month)
/// .increment(3)
/// .mode(RoundMode::Trunc)
/// // A relative datetime must be provided when
/// // rounding involves calendar units.
/// .relative(date(2024, 1, 1));
/// let span2 = span1.round(round)?;
/// assert_eq!(span2.get_months() / 3, 3);
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[inline]
pub fn round<'a, R: Into<SpanRound<'a>>>(
self,
options: R,
) -> Result<Span, Error> {
let options: SpanRound<'a> = options.into();
options.round(self)
}
/// Converts a `Span` to a [`SignedDuration`] relative to the date given.
///
/// In most cases, it is unlikely that you'll need to use this routine to
/// convert a `Span` to a `SignedDuration`. Namely, by default:
///
/// * [`Zoned::until`] guarantees that the biggest non-zero unit is hours.
/// * [`Timestamp::until`] guarantees that the biggest non-zero unit is
/// seconds.
/// * [`DateTime::until`] guarantees that the biggest non-zero unit is
/// days.
/// * [`Date::until`] guarantees that the biggest non-zero unit is days.
/// * [`Time::until`] guarantees that the biggest non-zero unit is hours.
///
/// In the above, only [`DateTime::until`] and [`Date::until`] return
/// calendar units by default. In which case, one may pass
/// [`SpanRelativeTo::days_are_24_hours`] or an actual relative date to
/// resolve the length of a day.
///
/// Of course, any of the above can be changed by asking, for example,
/// `Zoned::until` to return units up to years.
///
/// # Errors
///
/// This returns an error if adding this span to the date given results in
/// overflow. This can also return an error if one uses
/// [`SpanRelativeTo::days_are_24_hours`] with a `Span` that has non-zero
/// units greater than weeks.
///
/// # Example: converting a span with calendar units to a `SignedDuration`
///
/// This compares the number of seconds in a non-leap year with a leap
/// year:
///
/// ```
/// use jiff::{civil::date, SignedDuration, ToSpan};
///
/// let span = 1.year();
///
/// let duration = span.to_duration(date(2024, 1, 1))?;
/// assert_eq!(duration, SignedDuration::from_secs(31_622_400));
/// let duration = span.to_duration(date(2023, 1, 1))?;
/// assert_eq!(duration, SignedDuration::from_secs(31_536_000));
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Example: converting a span without a relative datetime
///
/// If for some reason it doesn't make sense to include a
/// relative datetime, you can use this routine to convert a
/// `Span` with units up to weeks to a `SignedDuration` via the
/// [`SpanRelativeTo::days_are_24_hours`] marker:
///
/// ```
/// use jiff::{civil::date, SignedDuration, SpanRelativeTo, ToSpan};
///
/// let span = 1.week().days(1);
///
/// let duration = span.to_duration(SpanRelativeTo::days_are_24_hours())?;
/// assert_eq!(duration, SignedDuration::from_hours(192));
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[inline]
pub fn to_duration<'a, R: Into<SpanRelativeTo<'a>>>(
&self,
relative: R,
) -> Result<SignedDuration, Error> {
let max_unit = self.largest_unit();
let relative: SpanRelativeTo<'a> = relative.into();
let Some(result) = relative.to_relative(max_unit).transpose() else {
return Ok(self.to_duration_invariant());
};
let relspan = result
.and_then(|r| r.into_relative_span(Unit::Second, *self))
.with_context(|| {
err!(
"could not compute normalized relative span \
from datetime {relative} and span {self}",
relative = relative.kind,
)
})?;
debug_assert!(relspan.span.largest_unit() <= Unit::Second);
Ok(relspan.span.to_duration_invariant())
}
/// Converts an entirely invariant span to a `SignedDuration`.
///
/// Callers must ensure that this span has no units greater than weeks.
/// If it does have non-zero units of days or weeks, then every day is
/// considered 24 hours and every week 7 days. Generally speaking, callers
/// should also ensure that if this span does have non-zero day/week units,
/// then callers have either provided a civil relative date or the special
/// `SpanRelativeTo::days_are_24_hours()` marker.
#[inline]
pub(crate) fn to_duration_invariant(&self) -> SignedDuration {
// This guarantees, at compile time, that a maximal invariant Span
// (that is, all units are days or lower and all units are set to their
// maximum values) will still balance out to a number of seconds that
// fits into a `i64`. This in turn implies that a `SignedDuration` can
// represent all possible invariant positive spans.
const _FITS_IN_U64: () = {
debug_assert!(
i64::MAX as i128
> ((t::SpanWeeks::MAX
* t::SECONDS_PER_CIVIL_WEEK.bound())
+ (t::SpanDays::MAX
* t::SECONDS_PER_CIVIL_DAY.bound())
+ (t::SpanHours::MAX * t::SECONDS_PER_HOUR.bound())
+ (t::SpanMinutes::MAX
* t::SECONDS_PER_MINUTE.bound())
+ t::SpanSeconds::MAX
+ (t::SpanMilliseconds::MAX
/ t::MILLIS_PER_SECOND.bound())
+ (t::SpanMicroseconds::MAX
/ t::MICROS_PER_SECOND.bound())
+ (t::SpanNanoseconds::MAX
/ t::NANOS_PER_SECOND.bound())),
);
()
};
let nanos = self.to_invariant_nanoseconds();
debug_assert!(
self.largest_unit() <= Unit::Week,
"units must be weeks or lower"
);
let seconds = nanos / t::NANOS_PER_SECOND;
let seconds = i64::from(seconds);
let subsec_nanos = nanos % t::NANOS_PER_SECOND;
// OK because % 1_000_000_000 above guarantees that the result fits
// in a i32.
let subsec_nanos = i32::try_from(subsec_nanos).unwrap();
// SignedDuration::new can panic if |subsec_nanos| >= 1_000_000_000
// and seconds == {i64::MIN,i64::MAX}. But this can never happen
// because we guaranteed by construction above that |subsec_nanos| <
// 1_000_000_000.
SignedDuration::new(seconds, subsec_nanos)
}
}
/// Crate internal APIs that operate on ranged integer types.
impl Span {
#[inline]
pub(crate) fn years_ranged(self, years: impl RInto<t::SpanYears>) -> Span {
let years = years.rinto();
let mut span = Span { years: years.abs(), ..self };
span.sign = self.resign(years, &span);
span
}
#[inline]
pub(crate) fn months_ranged(
self,
months: impl RInto<t::SpanMonths>,
) -> Span {
let months = months.rinto();
let mut span = Span { months: months.abs(), ..self };
span.sign = self.resign(months, &span);
span
}
#[inline]
pub(crate) fn weeks_ranged(self, weeks: impl RInto<t::SpanWeeks>) -> Span {
let weeks = weeks.rinto();
let mut span = Span { weeks: weeks.abs(), ..self };
span.sign = self.resign(weeks, &span);
span
}
#[inline]
pub(crate) fn days_ranged(self, days: impl RInto<t::SpanDays>) -> Span {
let days = days.rinto();
let mut span = Span { days: days.abs(), ..self };
span.sign = self.resign(days, &span);
span
}
#[inline]
pub(crate) fn hours_ranged(self, hours: impl RInto<t::SpanHours>) -> Span {
let hours = hours.rinto();
let mut span = Span { hours: hours.abs(), ..self };
span.sign = self.resign(hours, &span);
span
}
#[inline]
pub(crate) fn minutes_ranged(
self,
minutes: impl RInto<t::SpanMinutes>,
) -> Span {
let minutes = minutes.rinto();
let mut span = Span { minutes: minutes.abs(), ..self };
span.sign = self.resign(minutes, &span);
span
}
#[inline]
pub(crate) fn seconds_ranged(
self,
seconds: impl RInto<t::SpanSeconds>,
) -> Span {
let seconds = seconds.rinto();
let mut span = Span { seconds: seconds.abs(), ..self };
span.sign = self.resign(seconds, &span);
span
}
#[inline]
fn milliseconds_ranged(
self,
milliseconds: impl RInto<t::SpanMilliseconds>,
) -> Span {
let milliseconds = milliseconds.rinto();
let mut span = Span { milliseconds: milliseconds.abs(), ..self };
span.sign = self.resign(milliseconds, &span);
span
}
#[inline]
fn microseconds_ranged(
self,
microseconds: impl RInto<t::SpanMicroseconds>,
) -> Span {
let microseconds = microseconds.rinto();
let mut span = Span { microseconds: microseconds.abs(), ..self };
span.sign = self.resign(microseconds, &span);
span
}
#[inline]
pub(crate) fn nanoseconds_ranged(
self,
nanoseconds: impl RInto<t::SpanNanoseconds>,
) -> Span {
let nanoseconds = nanoseconds.rinto();
let mut span = Span { nanoseconds: nanoseconds.abs(), ..self };
span.sign = self.resign(nanoseconds, &span);
span
}
#[inline]
fn try_years_ranged(
self,
years: impl TryRInto<t::SpanYears>,
) -> Result<Span, Error> {
let years = years.try_rinto("years")?;
Ok(self.years_ranged(years))
}
#[inline]
fn try_months_ranged(
self,
months: impl TryRInto<t::SpanMonths>,
) -> Result<Span, Error> {
let months = months.try_rinto("months")?;
Ok(self.months_ranged(months))
}
#[inline]
fn try_weeks_ranged(
self,
weeks: impl TryRInto<t::SpanWeeks>,
) -> Result<Span, Error> {
let weeks = weeks.try_rinto("weeks")?;
Ok(self.weeks_ranged(weeks))
}
#[inline]
fn try_days_ranged(
self,
days: impl TryRInto<t::SpanDays>,
) -> Result<Span, Error> {
let days = days.try_rinto("days")?;
Ok(self.days_ranged(days))
}
#[inline]
pub(crate) fn try_hours_ranged(
self,
hours: impl TryRInto<t::SpanHours>,
) -> Result<Span, Error> {
let hours = hours.try_rinto("hours")?;
Ok(self.hours_ranged(hours))
}
#[inline]
pub(crate) fn try_minutes_ranged(
self,
minutes: impl TryRInto<t::SpanMinutes>,
) -> Result<Span, Error> {
let minutes = minutes.try_rinto("minutes")?;
Ok(self.minutes_ranged(minutes))
}
#[inline]
pub(crate) fn try_seconds_ranged(
self,
seconds: impl TryRInto<t::SpanSeconds>,
) -> Result<Span, Error> {
let seconds = seconds.try_rinto("seconds")?;
Ok(self.seconds_ranged(seconds))
}
#[inline]
pub(crate) fn try_milliseconds_ranged(
self,
milliseconds: impl TryRInto<t::SpanMilliseconds>,
) -> Result<Span, Error> {
let milliseconds = milliseconds.try_rinto("milliseconds")?;
Ok(self.milliseconds_ranged(milliseconds))
}
#[inline]
pub(crate) fn try_microseconds_ranged(
self,
microseconds: impl TryRInto<t::SpanMicroseconds>,
) -> Result<Span, Error> {
let microseconds = microseconds.try_rinto("microseconds")?;
Ok(self.microseconds_ranged(microseconds))
}
#[inline]
pub(crate) fn try_nanoseconds_ranged(
self,
nanoseconds: impl TryRInto<t::SpanNanoseconds>,
) -> Result<Span, Error> {
let nanoseconds = nanoseconds.try_rinto("nanoseconds")?;
Ok(self.nanoseconds_ranged(nanoseconds))
}
#[inline]
pub(crate) fn try_units_ranged(
self,
unit: Unit,
value: impl RInto<NoUnits>,
) -> Result<Span, Error> {
let value = value.rinto();
match unit {
Unit::Year => self.try_years_ranged(value),
Unit::Month => self.try_months_ranged(value),
Unit::Week => self.try_weeks_ranged(value),
Unit::Day => self.try_days_ranged(value),
Unit::Hour => self.try_hours_ranged(value),
Unit::Minute => self.try_minutes_ranged(value),
Unit::Second => self.try_seconds_ranged(value),
Unit::Millisecond => self.try_milliseconds_ranged(value),
Unit::Microsecond => self.try_microseconds_ranged(value),
Unit::Nanosecond => self.try_nanoseconds_ranged(value),
}
}
#[inline]
pub(crate) fn get_years_ranged(&self) -> t::SpanYears {
self.years * self.sign
}
#[inline]
pub(crate) fn get_months_ranged(&self) -> t::SpanMonths {
self.months * self.sign
}
#[inline]
pub(crate) fn get_weeks_ranged(&self) -> t::SpanWeeks {
self.weeks * self.sign
}
#[inline]
pub(crate) fn get_days_ranged(&self) -> t::SpanDays {
self.days * self.sign
}
#[inline]
pub(crate) fn get_hours_ranged(&self) -> t::SpanHours {
self.hours * self.sign
}
#[inline]
pub(crate) fn get_minutes_ranged(&self) -> t::SpanMinutes {
self.minutes * self.sign
}
#[inline]
pub(crate) fn get_seconds_ranged(&self) -> t::SpanSeconds {
self.seconds * self.sign
}
#[inline]
pub(crate) fn get_milliseconds_ranged(&self) -> t::SpanMilliseconds {
self.milliseconds * self.sign
}
#[inline]
pub(crate) fn get_microseconds_ranged(&self) -> t::SpanMicroseconds {
self.microseconds * self.sign
}
#[inline]
pub(crate) fn get_nanoseconds_ranged(&self) -> t::SpanNanoseconds {
self.nanoseconds * self.sign
}
#[inline]
fn get_sign_ranged(&self) -> ri8<-1, 1> {
self.sign
}
#[inline]
fn get_units_ranged(&self, unit: Unit) -> NoUnits {
match unit {
Unit::Year => self.get_years_ranged().rinto(),
Unit::Month => self.get_months_ranged().rinto(),
Unit::Week => self.get_weeks_ranged().rinto(),
Unit::Day => self.get_days_ranged().rinto(),
Unit::Hour => self.get_hours_ranged().rinto(),
Unit::Minute => self.get_minutes_ranged().rinto(),
Unit::Second => self.get_seconds_ranged().rinto(),
Unit::Millisecond => self.get_milliseconds_ranged().rinto(),
Unit::Microsecond => self.get_microseconds_ranged().rinto(),
Unit::Nanosecond => self.get_nanoseconds_ranged().rinto(),
}
}
}
/// Crate internal helper routines.
impl Span {
/// Converts the given number of nanoseconds to a `Span` whose units do not
/// exceed `largest`.
///
/// Note that `largest` is capped at `Unit::Week`. Note though that if
/// any unit greater than `Unit::Week` is given, then it is treated as
/// `Unit::Day`. The only way to get weeks in the `Span` returned is to
/// specifically request `Unit::Week`.
///
/// And also note that days in this context are civil days. That is, they
/// are always 24 hours long. Callers needing to deal with variable length
/// days should do so outside of this routine and should not provide a
/// `largest` unit bigger than `Unit::Hour`.
pub(crate) fn from_invariant_nanoseconds(
largest: Unit,
nanos: impl RInto<NoUnits128>,
) -> Result<Span, Error> {
let nanos = nanos.rinto();
let mut span = Span::new();
match largest {
Unit::Week => {
let micros = nanos.div_ceil(t::NANOS_PER_MICRO);
span = span.try_nanoseconds_ranged(
nanos.rem_ceil(t::NANOS_PER_MICRO),
)?;
let millis = micros.div_ceil(t::MICROS_PER_MILLI);
span = span.try_microseconds_ranged(
micros.rem_ceil(t::MICROS_PER_MILLI),
)?;
let secs = millis.div_ceil(t::MILLIS_PER_SECOND);
span = span.try_milliseconds_ranged(
millis.rem_ceil(t::MILLIS_PER_SECOND),
)?;
let mins = secs.div_ceil(t::SECONDS_PER_MINUTE);
span = span.try_seconds_ranged(
secs.rem_ceil(t::SECONDS_PER_MINUTE),
)?;
let hours = mins.div_ceil(t::MINUTES_PER_HOUR);
span = span
.try_minutes_ranged(mins.rem_ceil(t::MINUTES_PER_HOUR))?;
let days = hours.div_ceil(t::HOURS_PER_CIVIL_DAY);
span = span.try_hours_ranged(
hours.rem_ceil(t::HOURS_PER_CIVIL_DAY),
)?;
let weeks = days.div_ceil(t::DAYS_PER_CIVIL_WEEK);
span = span
.try_days_ranged(days.rem_ceil(t::DAYS_PER_CIVIL_WEEK))?;
span = span.try_weeks_ranged(weeks)?;
Ok(span)
}
Unit::Year | Unit::Month | Unit::Day => {
// Unit::Year | Unit::Month | Unit::Week | Unit::Day => {
let micros = nanos.div_ceil(t::NANOS_PER_MICRO);
span = span.try_nanoseconds_ranged(
nanos.rem_ceil(t::NANOS_PER_MICRO),
)?;
let millis = micros.div_ceil(t::MICROS_PER_MILLI);
span = span.try_microseconds_ranged(
micros.rem_ceil(t::MICROS_PER_MILLI),
)?;
let secs = millis.div_ceil(t::MILLIS_PER_SECOND);
span = span.try_milliseconds_ranged(
millis.rem_ceil(t::MILLIS_PER_SECOND),
)?;
let mins = secs.div_ceil(t::SECONDS_PER_MINUTE);
span = span.try_seconds_ranged(
secs.rem_ceil(t::SECONDS_PER_MINUTE),
)?;
let hours = mins.div_ceil(t::MINUTES_PER_HOUR);
span = span
.try_minutes_ranged(mins.rem_ceil(t::MINUTES_PER_HOUR))?;
let days = hours.div_ceil(t::HOURS_PER_CIVIL_DAY);
span = span.try_hours_ranged(
hours.rem_ceil(t::HOURS_PER_CIVIL_DAY),
)?;
span = span.try_days_ranged(days)?;
Ok(span)
}
Unit::Hour => {
let micros = nanos.div_ceil(t::NANOS_PER_MICRO);
span = span.try_nanoseconds_ranged(
nanos.rem_ceil(t::NANOS_PER_MICRO),
)?;
let millis = micros.div_ceil(t::MICROS_PER_MILLI);
span = span.try_microseconds_ranged(
micros.rem_ceil(t::MICROS_PER_MILLI),
)?;
let secs = millis.div_ceil(t::MILLIS_PER_SECOND);
span = span.try_milliseconds_ranged(
millis.rem_ceil(t::MILLIS_PER_SECOND),
)?;
let mins = secs.div_ceil(t::SECONDS_PER_MINUTE);
span = span.try_seconds_ranged(
secs.rem_ceil(t::SECONDS_PER_MINUTE),
)?;
let hours = mins.div_ceil(t::MINUTES_PER_HOUR);
span = span
.try_minutes_ranged(mins.rem_ceil(t::MINUTES_PER_HOUR))?;
span = span.try_hours_ranged(hours)?;
Ok(span)
}
Unit::Minute => {
let micros = nanos.div_ceil(t::NANOS_PER_MICRO);
span = span.try_nanoseconds_ranged(
nanos.rem_ceil(t::NANOS_PER_MICRO),
)?;
let millis = micros.div_ceil(t::MICROS_PER_MILLI);
span = span.try_microseconds_ranged(
micros.rem_ceil(t::MICROS_PER_MILLI),
)?;
let secs = millis.div_ceil(t::MILLIS_PER_SECOND);
span = span.try_milliseconds_ranged(
millis.rem_ceil(t::MILLIS_PER_SECOND),
)?;
let mins = secs.div_ceil(t::SECONDS_PER_MINUTE);
span =
span.try_seconds(secs.rem_ceil(t::SECONDS_PER_MINUTE))?;
span = span.try_minutes_ranged(mins)?;
Ok(span)
}
Unit::Second => {
let micros = nanos.div_ceil(t::NANOS_PER_MICRO);
span = span.try_nanoseconds_ranged(
nanos.rem_ceil(t::NANOS_PER_MICRO),
)?;
let millis = micros.div_ceil(t::MICROS_PER_MILLI);
span = span.try_microseconds_ranged(
micros.rem_ceil(t::MICROS_PER_MILLI),
)?;
let secs = millis.div_ceil(t::MILLIS_PER_SECOND);
span = span.try_milliseconds_ranged(
millis.rem_ceil(t::MILLIS_PER_SECOND),
)?;
span = span.try_seconds_ranged(secs)?;
Ok(span)
}
Unit::Millisecond => {
let micros = nanos.div_ceil(t::NANOS_PER_MICRO);
span = span.try_nanoseconds_ranged(
nanos.rem_ceil(t::NANOS_PER_MICRO),
)?;
let millis = micros.div_ceil(t::MICROS_PER_MILLI);
span = span.try_microseconds_ranged(
micros.rem_ceil(t::MICROS_PER_MILLI),
)?;
span = span.try_milliseconds_ranged(millis)?;
Ok(span)
}
Unit::Microsecond => {
let micros = nanos.div_ceil(t::NANOS_PER_MICRO);
span = span.try_nanoseconds_ranged(
nanos.rem_ceil(t::NANOS_PER_MICRO),
)?;
span = span.try_microseconds_ranged(micros)?;
Ok(span)
}
Unit::Nanosecond => {
span = span.try_nanoseconds_ranged(nanos)?;
Ok(span)
}
}
}
/// Converts the non-variable units of this `Span` to a total number of
/// nanoseconds.
///
/// This includes days and weeks, even though they can be of irregular
/// length during time zone transitions. If this applies, then callers
/// should set the days and weeks to `0` before calling this routine.
///
/// All units above weeks are always ignored.
#[inline]
pub(crate) fn to_invariant_nanoseconds(&self) -> NoUnits128 {
let mut nanos = NoUnits128::rfrom(self.get_nanoseconds_ranged());
nanos += NoUnits128::rfrom(self.get_microseconds_ranged())
* t::NANOS_PER_MICRO;
nanos += NoUnits128::rfrom(self.get_milliseconds_ranged())
* t::NANOS_PER_MILLI;
nanos +=
NoUnits128::rfrom(self.get_seconds_ranged()) * t::NANOS_PER_SECOND;
nanos +=
NoUnits128::rfrom(self.get_minutes_ranged()) * t::NANOS_PER_MINUTE;
nanos +=
NoUnits128::rfrom(self.get_hours_ranged()) * t::NANOS_PER_HOUR;
nanos +=
NoUnits128::rfrom(self.get_days_ranged()) * t::NANOS_PER_CIVIL_DAY;
nanos += NoUnits128::rfrom(self.get_weeks_ranged())
* t::NANOS_PER_CIVIL_WEEK;
nanos
}
/// Converts the non-variable units of this `Span` to a total number of
/// seconds if there is no fractional second component. Otherwise,
/// `None` is returned.
///
/// This is useful for short-circuiting in arithmetic operations when
/// it's faster to only deal with seconds. And in particular, acknowledges
/// that nanosecond precision durations are somewhat rare.
///
/// This includes days and weeks, even though they can be of irregular
/// length during time zone transitions. If this applies, then callers
/// should set the days and weeks to `0` before calling this routine.
///
/// All units above weeks are always ignored.
#[inline]
pub(crate) fn to_invariant_seconds(&self) -> Option<NoUnits> {
if self.has_fractional_seconds() {
return None;
}
let mut seconds = NoUnits::rfrom(self.get_seconds_ranged());
seconds +=
NoUnits::rfrom(self.get_minutes_ranged()) * t::SECONDS_PER_MINUTE;
seconds +=
NoUnits::rfrom(self.get_hours_ranged()) * t::SECONDS_PER_HOUR;
seconds +=
NoUnits::rfrom(self.get_days_ranged()) * t::SECONDS_PER_CIVIL_DAY;
seconds += NoUnits::rfrom(self.get_weeks_ranged())
* t::SECONDS_PER_CIVIL_WEEK;
Some(seconds)
}
/// Rebalances the invariant units (days or lower) on this span so that
/// the largest possible non-zero unit is the one given.
///
/// Units above day are ignored and dropped.
///
/// If the given unit is greater than days, then it is treated as-if it
/// were days.
///
/// # Errors
///
/// This can return an error in the case of lop-sided units. For example,
/// if this span has maximal values for all units, then rebalancing is
/// not possible because the number of days after balancing would exceed
/// the limit.
#[cfg(test)] // currently only used in zic parser?
#[inline]
pub(crate) fn rebalance(self, unit: Unit) -> Result<Span, Error> {
Span::from_invariant_nanoseconds(unit, self.to_invariant_nanoseconds())
}
/// Returns true if and only if this span has at least one non-zero
/// fractional second unit.
#[inline]
pub(crate) fn has_fractional_seconds(&self) -> bool {
self.milliseconds != 0
|| self.microseconds != 0
|| self.nanoseconds != 0
}
/// Returns an equivalent span, but with all non-calendar (units below
/// days) set to zero.
#[inline(always)]
pub(crate) fn only_calendar(self) -> Span {
let mut span = self;
span.hours = t::SpanHours::N::<0>();
span.minutes = t::SpanMinutes::N::<0>();
span.seconds = t::SpanSeconds::N::<0>();
span.milliseconds = t::SpanMilliseconds::N::<0>();
span.microseconds = t::SpanMicroseconds::N::<0>();
span.nanoseconds = t::SpanNanoseconds::N::<0>();
if span.sign != 0
&& span.years == 0
&& span.months == 0
&& span.weeks == 0
&& span.days == 0
{
span.sign = t::Sign::N::<0>();
}
span
}
/// Returns an equivalent span, but with all calendar (units above
/// hours) set to zero.
#[inline(always)]
pub(crate) fn only_time(self) -> Span {
let mut span = self;
span.years = t::SpanYears::N::<0>();
span.months = t::SpanMonths::N::<0>();
span.weeks = t::SpanWeeks::N::<0>();
span.days = t::SpanDays::N::<0>();
if span.sign != 0
&& span.hours == 0
&& span.minutes == 0
&& span.seconds == 0
&& span.milliseconds == 0
&& span.microseconds == 0
&& span.nanoseconds == 0
{
span.sign = t::Sign::N::<0>();
}
span
}
/// Returns an equivalent span, but with all units greater than or equal to
/// the one given set to zero.
#[inline(always)]
pub(crate) fn only_lower(self, unit: Unit) -> Span {
let mut span = self;
// Unit::Nanosecond is the minimum, so nothing can be smaller than it.
if unit <= Unit::Microsecond {
span = span.microseconds_ranged(C(0));
}
if unit <= Unit::Millisecond {
span = span.milliseconds_ranged(C(0));
}
if unit <= Unit::Second {
span = span.seconds_ranged(C(0));
}
if unit <= Unit::Minute {
span = span.minutes_ranged(C(0));
}
if unit <= Unit::Hour {
span = span.hours_ranged(C(0));
}
if unit <= Unit::Day {
span = span.days_ranged(C(0));
}
if unit <= Unit::Week {
span = span.weeks_ranged(C(0));
}
if unit <= Unit::Month {
span = span.months_ranged(C(0));
}
if unit <= Unit::Year {
span = span.years_ranged(C(0));
}
span
}
/// Returns an equivalent span, but with all units less than the one given
/// set to zero.
#[inline(always)]
pub(crate) fn without_lower(self, unit: Unit) -> Span {
let mut span = self;
if unit > Unit::Nanosecond {
span = span.nanoseconds_ranged(C(0));
}
if unit > Unit::Microsecond {
span = span.microseconds_ranged(C(0));
}
if unit > Unit::Millisecond {
span = span.milliseconds_ranged(C(0));
}
if unit > Unit::Second {
span = span.seconds_ranged(C(0));
}
if unit > Unit::Minute {
span = span.minutes_ranged(C(0));
}
if unit > Unit::Hour {
span = span.hours_ranged(C(0));
}
if unit > Unit::Day {
span = span.days_ranged(C(0));
}
if unit > Unit::Week {
span = span.weeks_ranged(C(0));
}
if unit > Unit::Month {
span = span.months_ranged(C(0));
}
// Unit::Year is the max, so nothing can be bigger than it.
span
}
/// Returns an error corresponding to the smallest non-time non-zero unit.
///
/// If all non-time units are zero, then this returns `None`.
#[inline(always)]
pub(crate) fn smallest_non_time_non_zero_unit_error(
&self,
) -> Option<Error> {
let non_time_unit = self.largest_calendar_unit()?;
Some(err!(
"operation can only be performed with units of hours \
or smaller, but found non-zero {unit} units \
(operations on `Timestamp`, `tz::Offset` and `civil::Time` \
don't support calendar units in a `Span`)",
unit = non_time_unit.singular(),
))
}
/// Returns the largest non-zero calendar unit, or `None` if there are no
/// non-zero calendar units.
#[inline]
pub(crate) fn largest_calendar_unit(&self) -> Option<Unit> {
if self.days != 0 {
Some(Unit::Day)
} else if self.weeks != 0 {
Some(Unit::Week)
} else if self.months != 0 {
Some(Unit::Month)
} else if self.years != 0 {
Some(Unit::Year)
} else {
None
}
}
/// Returns the largest non-zero unit in this span.
///
/// If all components of this span are zero, then `Unit::Nanosecond` is
/// returned.
#[inline]
pub(crate) fn largest_unit(&self) -> Unit {
if self.years != 0 {
Unit::Year
} else if self.months != 0 {
Unit::Month
} else if self.weeks != 0 {
Unit::Week
} else if self.days != 0 {
Unit::Day
} else if self.hours != 0 {
Unit::Hour
} else if self.minutes != 0 {
Unit::Minute
} else if self.seconds != 0 {
Unit::Second
} else if self.milliseconds != 0 {
Unit::Millisecond
} else if self.microseconds != 0 {
Unit::Microsecond
} else {
Unit::Nanosecond
}
}
/// Returns a string containing the value of all non-zero fields.
///
/// This is useful for debugging. Normally, this would be the "alternate"
/// debug impl (perhaps), but that's what insta uses and I preferred having
/// the friendly format used there since it is much more terse.
#[cfg(feature = "alloc")]
#[allow(dead_code)]
fn debug(&self) -> alloc::string::String {
use core::fmt::Write;
let mut buf = alloc::string::String::new();
write!(buf, "Span {{ sign: {:?}", self.sign).unwrap();
if self.years != 0 {
write!(buf, ", years: {:?}", self.years).unwrap();
}
if self.months != 0 {
write!(buf, ", months: {:?}", self.months).unwrap();
}
if self.weeks != 0 {
write!(buf, ", weeks: {:?}", self.weeks).unwrap();
}
if self.days != 0 {
write!(buf, ", days: {:?}", self.days).unwrap();
}
if self.hours != 0 {
write!(buf, ", hours: {:?}", self.hours).unwrap();
}
if self.minutes != 0 {
write!(buf, ", minutes: {:?}", self.minutes).unwrap();
}
if self.seconds != 0 {
write!(buf, ", seconds: {:?}", self.seconds).unwrap();
}
if self.milliseconds != 0 {
write!(buf, ", milliseconds: {:?}", self.milliseconds).unwrap();
}
if self.microseconds != 0 {
write!(buf, ", microseconds: {:?}", self.microseconds).unwrap();
}
if self.nanoseconds != 0 {
write!(buf, ", nanoseconds: {:?}", self.nanoseconds).unwrap();
}
write!(buf, " }}").unwrap();
buf
}
/// Given some new units to set on this span and the span updates with the
/// new units, this determines the what the sign of `new` should be.
#[inline]
fn resign(&self, units: impl RInto<NoUnits>, new: &Span) -> Sign {
let units = units.rinto();
// Negative units anywhere always makes the entire span negative.
if units < 0 {
return Sign::N::<-1>();
}
let mut new_is_zero = new.sign == 0 && units == 0;
// When `units == 0` and it was previously non-zero, then `new.sign`
// won't be `0` and thus `new_is_zero` will be false when it should
// be true. So in this case, we need to re-check all the units to set
// the sign correctly.
if units == 0 {
new_is_zero = new.years == 0
&& new.months == 0
&& new.weeks == 0
&& new.days == 0
&& new.hours == 0
&& new.minutes == 0
&& new.seconds == 0
&& new.milliseconds == 0
&& new.microseconds == 0
&& new.nanoseconds == 0;
}
match (self.is_zero(), new_is_zero) {
(_, true) => Sign::N::<0>(),
(true, false) => units.signum().rinto(),
// If the old and new span are both non-zero, and we know our new
// units are not negative, then the sign remains unchanged.
(false, false) => new.sign,
}
}
}
impl Default for Span {
#[inline]
fn default() -> Span {
Span {
sign: ri8::N::<0>(),
years: C(0).rinto(),
months: C(0).rinto(),
weeks: C(0).rinto(),
days: C(0).rinto(),
hours: C(0).rinto(),
minutes: C(0).rinto(),
seconds: C(0).rinto(),
milliseconds: C(0).rinto(),
microseconds: C(0).rinto(),
nanoseconds: C(0).rinto(),
}
}
}
impl core::fmt::Debug for Span {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use crate::fmt::StdFmtWrite;
friendly::DEFAULT_SPAN_PRINTER
.print_span(self, StdFmtWrite(f))
.map_err(|_| core::fmt::Error)
}
}
impl core::fmt::Display for Span {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use crate::fmt::StdFmtWrite;
if f.alternate() {
friendly::DEFAULT_SPAN_PRINTER
.print_span(self, StdFmtWrite(f))
.map_err(|_| core::fmt::Error)
} else {
temporal::DEFAULT_SPAN_PRINTER
.print_span(self, StdFmtWrite(f))
.map_err(|_| core::fmt::Error)
}
}
}
impl core::str::FromStr for Span {
type Err = Error;
#[inline]
fn from_str(string: &str) -> Result<Span, Error> {
parse_iso_or_friendly(string.as_bytes())
}
}
impl core::ops::Neg for Span {
type Output = Span;
#[inline]
fn neg(self) -> Span {
self.negate()
}
}
/// This multiplies each unit in a span by an integer.
///
/// This panics on overflow. For checked arithmetic, use [`Span::checked_mul`].
impl core::ops::Mul<i64> for Span {
type Output = Span;
#[inline]
fn mul(self, rhs: i64) -> Span {
self.checked_mul(rhs)
.expect("multiplying `Span` by a scalar overflowed")
}
}
/// This multiplies each unit in a span by an integer.
///
/// This panics on overflow. For checked arithmetic, use [`Span::checked_mul`].
impl core::ops::Mul<Span> for i64 {
type Output = Span;
#[inline]
fn mul(self, rhs: Span) -> Span {
rhs.checked_mul(self)
.expect("multiplying `Span` by a scalar overflowed")
}
}
/// Converts a `Span` to a [`std::time::Duration`].
///
/// Note that this assumes that days are always 24 hours long.
///
/// # Errors
///
/// This can fail for only two reasons:
///
/// * The span is negative. This is an error because a `std::time::Duration` is
/// unsigned.)
/// * The span has any non-zero units greater than hours. This is an error
/// because it's impossible to determine the length of, e.g., a month without
/// a reference date.
///
/// This can never result in overflow because a `Duration` can represent a
/// bigger span of time than `Span` when limited to units of hours or lower.
///
/// If you need to convert a `Span` to a `Duration` that has non-zero
/// units bigger than hours, then please use [`Span::to_duration`] with a
/// corresponding relative date.
///
/// # Example: maximal span
///
/// This example shows the maximum possible span using units of hours or
/// smaller, and the corresponding `Duration` value:
///
/// ```
/// use std::time::Duration;
///
/// use jiff::Span;
///
/// let sp = Span::new()
/// .hours(175_307_616)
/// .minutes(10_518_456_960i64)
/// .seconds(631_107_417_600i64)
/// .milliseconds(631_107_417_600_000i64)
/// .microseconds(631_107_417_600_000_000i64)
/// .nanoseconds(9_223_372_036_854_775_807i64);
/// let duration = Duration::try_from(sp)?;
/// assert_eq!(duration, Duration::new(3_164_760_460_036, 854_775_807));
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Example: converting a negative span
///
/// Since a `Span` is signed and a `Duration` is unsigned, converting
/// a negative `Span` to `Duration` will always fail. One can use
/// [`Span::signum`] to get the sign of the span and [`Span::abs`] to make the
/// span positive before converting it to a `Duration`:
///
/// ```
/// use std::time::Duration;
///
/// use jiff::{Span, ToSpan};
///
/// let span = -86_400.seconds().nanoseconds(1);
/// let (sign, duration) = (span.signum(), Duration::try_from(span.abs())?);
/// assert_eq!((sign, duration), (-1, Duration::new(86_400, 1)));
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
impl TryFrom<Span> for UnsignedDuration {
type Error = Error;
#[inline]
fn try_from(sp: Span) -> Result<UnsignedDuration, Error> {
// This isn't needed, but improves error messages.
if sp.is_negative() {
return Err(err!(
"cannot convert negative span {sp:?} \
to unsigned std::time::Duration",
));
}
SignedDuration::try_from(sp).and_then(UnsignedDuration::try_from)
}
}
/// Converts a [`std::time::Duration`] to a `Span`.
///
/// The span returned from this conversion will only ever have non-zero units
/// of seconds or smaller.
///
/// # Errors
///
/// This only fails when the given `Duration` overflows the maximum number of
/// seconds representable by a `Span`.
///
/// # Example
///
/// This shows a basic conversion:
///
/// ```
/// use std::time::Duration;
///
/// use jiff::{Span, ToSpan};
///
/// let duration = Duration::new(86_400, 123_456_789);
/// let span = Span::try_from(duration)?;
/// // A duration-to-span conversion always results in a span with
/// // non-zero units no bigger than seconds.
/// assert_eq!(
/// span.fieldwise(),
/// 86_400.seconds().milliseconds(123).microseconds(456).nanoseconds(789),
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Example: rounding
///
/// This example shows how to convert a `Duration` to a `Span`, and then round
/// it up to bigger units given a relative date:
///
/// ```
/// use std::time::Duration;
///
/// use jiff::{civil::date, Span, SpanRound, ToSpan, Unit};
///
/// let duration = Duration::new(450 * 86_401, 0);
/// let span = Span::try_from(duration)?;
/// // We get back a simple span of just seconds:
/// assert_eq!(span.fieldwise(), Span::new().seconds(450 * 86_401));
/// // But we can balance it up to bigger units:
/// let options = SpanRound::new()
/// .largest(Unit::Year)
/// .relative(date(2024, 1, 1));
/// assert_eq!(
/// span.round(options)?,
/// 1.year().months(2).days(25).minutes(7).seconds(30).fieldwise(),
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
impl TryFrom<UnsignedDuration> for Span {
type Error = Error;
#[inline]
fn try_from(d: UnsignedDuration) -> Result<Span, Error> {
let seconds = i64::try_from(d.as_secs()).map_err(|_| {
err!("seconds from {d:?} overflows a 64-bit signed integer")
})?;
let nanoseconds = i64::from(d.subsec_nanos());
let milliseconds = nanoseconds / t::NANOS_PER_MILLI.value();
let microseconds = (nanoseconds % t::NANOS_PER_MILLI.value())
/ t::NANOS_PER_MICRO.value();
let nanoseconds = nanoseconds % t::NANOS_PER_MICRO.value();
let span = Span::new().try_seconds(seconds).with_context(|| {
err!("duration {d:?} overflows limits of a Jiff `Span`")
})?;
// These are all OK because `Duration::subsec_nanos` is guaranteed to
// return less than 1_000_000_000 nanoseconds. And splitting that up
// into millis, micros and nano components is guaranteed to fit into
// the limits of a `Span`.
Ok(span
.milliseconds(milliseconds)
.microseconds(microseconds)
.nanoseconds(nanoseconds))
}
}
/// Converts a `Span` to a [`SignedDuration`].
///
/// Note that this assumes that days are always 24 hours long.
///
/// # Errors
///
/// This can fail for only when the span has any non-zero units greater than
/// hours. This is an error because it's impossible to determine the length of,
/// e.g., a month without a reference date.
///
/// This can never result in overflow because a `SignedDuration` can represent
/// a bigger span of time than `Span` when limited to units of hours or lower.
///
/// If you need to convert a `Span` to a `SignedDuration` that has non-zero
/// units bigger than hours, then please use [`Span::to_duration`] with a
/// corresponding relative date.
///
/// # Example: maximal span
///
/// This example shows the maximum possible span using units of hours or
/// smaller, and the corresponding `SignedDuration` value:
///
/// ```
/// use jiff::{SignedDuration, Span};
///
/// let sp = Span::new()
/// .hours(175_307_616)
/// .minutes(10_518_456_960i64)
/// .seconds(631_107_417_600i64)
/// .milliseconds(631_107_417_600_000i64)
/// .microseconds(631_107_417_600_000_000i64)
/// .nanoseconds(9_223_372_036_854_775_807i64);
/// let duration = SignedDuration::try_from(sp)?;
/// assert_eq!(duration, SignedDuration::new(3_164_760_460_036, 854_775_807));
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
impl TryFrom<Span> for SignedDuration {
type Error = Error;
#[inline]
fn try_from(sp: Span) -> Result<SignedDuration, Error> {
requires_relative_date_err(sp.largest_unit()).context(
"failed to convert span to duration without relative datetime \
(must use `Span::to_duration` instead)",
)?;
Ok(sp.to_duration_invariant())
}
}
/// Converts a [`SignedDuration`] to a `Span`.
///
/// The span returned from this conversion will only ever have non-zero units
/// of seconds or smaller.
///
/// # Errors
///
/// This only fails when the given `SignedDuration` overflows the maximum
/// number of seconds representable by a `Span`.
///
/// # Example
///
/// This shows a basic conversion:
///
/// ```
/// use jiff::{SignedDuration, Span, ToSpan};
///
/// let duration = SignedDuration::new(86_400, 123_456_789);
/// let span = Span::try_from(duration)?;
/// // A duration-to-span conversion always results in a span with
/// // non-zero units no bigger than seconds.
/// assert_eq!(
/// span.fieldwise(),
/// 86_400.seconds().milliseconds(123).microseconds(456).nanoseconds(789),
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Example: rounding
///
/// This example shows how to convert a `SignedDuration` to a `Span`, and then
/// round it up to bigger units given a relative date:
///
/// ```
/// use jiff::{civil::date, SignedDuration, Span, SpanRound, ToSpan, Unit};
///
/// let duration = SignedDuration::new(450 * 86_401, 0);
/// let span = Span::try_from(duration)?;
/// // We get back a simple span of just seconds:
/// assert_eq!(span.fieldwise(), Span::new().seconds(450 * 86_401));
/// // But we can balance it up to bigger units:
/// let options = SpanRound::new()
/// .largest(Unit::Year)
/// .relative(date(2024, 1, 1));
/// assert_eq!(
/// span.round(options)?,
/// 1.year().months(2).days(25).minutes(7).seconds(30).fieldwise(),
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
impl TryFrom<SignedDuration> for Span {
type Error = Error;
#[inline]
fn try_from(d: SignedDuration) -> Result<Span, Error> {
let seconds = d.as_secs();
let nanoseconds = i64::from(d.subsec_nanos());
let milliseconds = nanoseconds / t::NANOS_PER_MILLI.value();
let microseconds = (nanoseconds % t::NANOS_PER_MILLI.value())
/ t::NANOS_PER_MICRO.value();
let nanoseconds = nanoseconds % t::NANOS_PER_MICRO.value();
let span = Span::new().try_seconds(seconds).with_context(|| {
err!("signed duration {d:?} overflows limits of a Jiff `Span`")
})?;
// These are all OK because `|SignedDuration::subsec_nanos|` is
// guaranteed to return less than 1_000_000_000 nanoseconds. And
// splitting that up into millis, micros and nano components is
// guaranteed to fit into the limits of a `Span`.
Ok(span
.milliseconds(milliseconds)
.microseconds(microseconds)
.nanoseconds(nanoseconds))
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Span {
#[inline]
fn serialize<S: serde::Serializer>(
&self,
serializer: S,
) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Span {
#[inline]
fn deserialize<D: serde::Deserializer<'de>>(
deserializer: D,
) -> Result<Span, D::Error> {
use serde::de;
struct SpanVisitor;
impl<'de> de::Visitor<'de> for SpanVisitor {
type Value = Span;
fn expecting(
&self,
f: &mut core::fmt::Formatter,
) -> core::fmt::Result {
f.write_str("a span duration string")
}
#[inline]
fn visit_bytes<E: de::Error>(
self,
value: &[u8],
) -> Result<Span, E> {
parse_iso_or_friendly(value).map_err(de::Error::custom)
}
#[inline]
fn visit_str<E: de::Error>(self, value: &str) -> Result<Span, E> {
self.visit_bytes(value.as_bytes())
}
}
deserializer.deserialize_str(SpanVisitor)
}
}
#[cfg(test)]
impl quickcheck::Arbitrary for Span {
fn arbitrary(g: &mut quickcheck::Gen) -> Span {
// In order to sample from the full space of possible spans, we need
// to provide a relative datetime. But if we do that, then it's
// possible the span plus the datetime overflows. So we pick one
// datetime and shrink the size of the span we can produce.
type Nanos = ri64<-631_107_417_600_000_000, 631_107_417_600_000_000>;
let nanos = Nanos::arbitrary(g).get();
let relative =
SpanRelativeTo::from(DateTime::constant(0, 1, 1, 0, 0, 0, 0));
let round =
SpanRound::new().largest(Unit::arbitrary(g)).relative(relative);
Span::new().nanoseconds(nanos).round(round).unwrap()
}
fn shrink(&self) -> alloc::boxed::Box<dyn Iterator<Item = Self>> {
alloc::boxed::Box::new(
(
(
self.get_years_ranged(),
self.get_months_ranged(),
self.get_weeks_ranged(),
self.get_days_ranged(),
),
(
self.get_hours_ranged(),
self.get_minutes_ranged(),
self.get_seconds_ranged(),
self.get_milliseconds_ranged(),
),
(
self.get_microseconds_ranged(),
self.get_nanoseconds_ranged(),
),
)
.shrink()
.filter_map(
|(
(years, months, weeks, days),
(hours, minutes, seconds, milliseconds),
(microseconds, nanoseconds),
)| {
let span = Span::new()
.years_ranged(years)
.months_ranged(months)
.weeks_ranged(weeks)
.days_ranged(days)
.hours_ranged(hours)
.minutes_ranged(minutes)
.seconds_ranged(seconds)
.milliseconds_ranged(milliseconds)
.microseconds_ranged(microseconds)
.nanoseconds_ranged(nanoseconds);
Some(span)
},
),
)
}
}
/// A wrapper for [`Span`] that implements the `Hash`, `Eq` and `PartialEq`
/// traits.
///
/// A `SpanFieldwise` is meant to make it easy to compare two spans in a "dumb"
/// way based purely on its unit values, while still providing a speed bump
/// to avoid accidentally doing this comparison on `Span` directly. This is
/// distinct from something like [`Span::compare`] that performs a comparison
/// on the actual elapsed time of two spans.
///
/// It is generally discouraged to use `SpanFieldwise` since spans that
/// represent an equivalent elapsed amount of time may compare unequal.
/// However, in some cases, it is useful to be able to assert precise field
/// values. For example, Jiff itself makes heavy use of fieldwise comparisons
/// for tests.
///
/// # Construction
///
/// While callers may use `SpanFieldwise(span)` (where `span` has type [`Span`])
/// to construct a value of this type, callers may find [`Span::fieldwise`]
/// more convenient. Namely, `Span::fieldwise` may avoid the need to explicitly
/// import `SpanFieldwise`.
///
/// # Trait implementations
///
/// In addition to implementing the `Hash`, `Eq` and `PartialEq` traits, this
/// type also provides `PartialEq` impls for comparing a `Span` with a
/// `SpanFieldwise`. This simplifies comparisons somewhat while still requiring
/// that at least one of the values has an explicit fieldwise comparison type.
///
/// # Safety
///
/// This type is guaranteed to have the same layout in memory as [`Span`].
///
/// # Example: the difference between `SpanFieldwise` and [`Span::compare`]
///
/// In short, `SpanFieldwise` considers `2 hours` and `120 minutes` to be
/// distinct values, but `Span::compare` considers them to be equivalent:
///
/// ```
/// use std::cmp::Ordering;
/// use jiff::ToSpan;
///
/// assert_ne!(120.minutes().fieldwise(), 2.hours().fieldwise());
/// assert_eq!(120.minutes().compare(2.hours())?, Ordering::Equal);
///
/// // These comparisons are allowed between a `Span` and a `SpanFieldwise`.
/// // Namely, as long as one value is "fieldwise," then the comparison is OK.
/// assert_ne!(120.minutes().fieldwise(), 2.hours());
/// assert_ne!(120.minutes(), 2.hours().fieldwise());
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[derive(Clone, Copy, Debug, Default)]
#[repr(transparent)]
pub struct SpanFieldwise(pub Span);
// Exists so that things like `-1.day().fieldwise()` works as expected.
impl core::ops::Neg for SpanFieldwise {
type Output = SpanFieldwise;
#[inline]
fn neg(self) -> SpanFieldwise {
SpanFieldwise(self.0.negate())
}
}
impl Eq for SpanFieldwise {}
impl PartialEq for SpanFieldwise {
fn eq(&self, rhs: &SpanFieldwise) -> bool {
self.0.sign == rhs.0.sign
&& self.0.years == rhs.0.years
&& self.0.months == rhs.0.months
&& self.0.weeks == rhs.0.weeks
&& self.0.days == rhs.0.days
&& self.0.hours == rhs.0.hours
&& self.0.minutes == rhs.0.minutes
&& self.0.seconds == rhs.0.seconds
&& self.0.milliseconds == rhs.0.milliseconds
&& self.0.microseconds == rhs.0.microseconds
&& self.0.nanoseconds == rhs.0.nanoseconds
}
}
impl<'a> PartialEq<SpanFieldwise> for &'a SpanFieldwise {
fn eq(&self, rhs: &SpanFieldwise) -> bool {
*self == rhs
}
}
impl PartialEq<Span> for SpanFieldwise {
fn eq(&self, rhs: &Span) -> bool {
self == rhs.fieldwise()
}
}
impl PartialEq<SpanFieldwise> for Span {
fn eq(&self, rhs: &SpanFieldwise) -> bool {
self.fieldwise() == *rhs
}
}
impl<'a> PartialEq<SpanFieldwise> for &'a Span {
fn eq(&self, rhs: &SpanFieldwise) -> bool {
self.fieldwise() == *rhs
}
}
impl core::hash::Hash for SpanFieldwise {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.0.sign.hash(state);
self.0.years.hash(state);
self.0.months.hash(state);
self.0.weeks.hash(state);
self.0.days.hash(state);
self.0.hours.hash(state);
self.0.minutes.hash(state);
self.0.seconds.hash(state);
self.0.milliseconds.hash(state);
self.0.microseconds.hash(state);
self.0.nanoseconds.hash(state);
}
}
impl From<Span> for SpanFieldwise {
fn from(span: Span) -> SpanFieldwise {
SpanFieldwise(span)
}
}
impl From<SpanFieldwise> for Span {
fn from(span: SpanFieldwise) -> Span {
span.0
}
}
/// A trait for enabling concise literals for creating [`Span`] values.
///
/// In short, this trait lets you write something like `5.seconds()` or
/// `1.day()` to create a [`Span`]. Once a `Span` has been created, you can
/// use its mutator methods to add more fields. For example,
/// `1.day().hours(10)` is equivalent to `Span::new().days(1).hours(10)`.
///
/// This trait is implemented for the following integer types: `i8`, `i16`,
/// `i32` and `i64`.
///
/// Note that this trait is provided as a convenience and should generally
/// only be used for literals in your source code. You should not use this
/// trait on numbers provided by end users. Namely, if the number provided
/// is not within Jiff's span limits, then these trait methods will panic.
/// Instead, use fallible mutator constructors like [`Span::try_days`]
/// or [`Span::try_seconds`].
///
/// # Example
///
/// ```
/// use jiff::ToSpan;
///
/// assert_eq!(5.days().to_string(), "P5D");
/// assert_eq!(5.days().hours(10).to_string(), "P5DT10H");
///
/// // Negation works and it doesn't matter where the sign goes. It can be
/// // applied to the span itself or to the integer.
/// assert_eq!((-5.days()).to_string(), "-P5D");
/// assert_eq!((-5).days().to_string(), "-P5D");
/// ```
///
/// # Example: alternative via span parsing
///
/// Another way of tersely building a `Span` value is by parsing a ISO 8601
/// duration string:
///
/// ```
/// use jiff::Span;
///
/// let span = "P5y2m15dT23h30m10s".parse::<Span>()?;
/// assert_eq!(
/// span.fieldwise(),
/// Span::new().years(5).months(2).days(15).hours(23).minutes(30).seconds(10),
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub trait ToSpan: Sized {
/// Create a new span from this integer in units of years.
///
/// # Panics
///
/// When `Span::new().years(self)` would panic.
fn years(self) -> Span;
/// Create a new span from this integer in units of months.
///
/// # Panics
///
/// When `Span::new().months(self)` would panic.
fn months(self) -> Span;
/// Create a new span from this integer in units of weeks.
///
/// # Panics
///
/// When `Span::new().weeks(self)` would panic.
fn weeks(self) -> Span;
/// Create a new span from this integer in units of days.
///
/// # Panics
///
/// When `Span::new().days(self)` would panic.
fn days(self) -> Span;
/// Create a new span from this integer in units of hours.
///
/// # Panics
///
/// When `Span::new().hours(self)` would panic.
fn hours(self) -> Span;
/// Create a new span from this integer in units of minutes.
///
/// # Panics
///
/// When `Span::new().minutes(self)` would panic.
fn minutes(self) -> Span;
/// Create a new span from this integer in units of seconds.
///
/// # Panics
///
/// When `Span::new().seconds(self)` would panic.
fn seconds(self) -> Span;
/// Create a new span from this integer in units of milliseconds.
///
/// # Panics
///
/// When `Span::new().milliseconds(self)` would panic.
fn milliseconds(self) -> Span;
/// Create a new span from this integer in units of microseconds.
///
/// # Panics
///
/// When `Span::new().microseconds(self)` would panic.
fn microseconds(self) -> Span;
/// Create a new span from this integer in units of nanoseconds.
///
/// # Panics
///
/// When `Span::new().nanoseconds(self)` would panic.
fn nanoseconds(self) -> Span;
/// Equivalent to `years()`, but reads better for singular units.
#[inline]
fn year(self) -> Span {
self.years()
}
/// Equivalent to `months()`, but reads better for singular units.
#[inline]
fn month(self) -> Span {
self.months()
}
/// Equivalent to `weeks()`, but reads better for singular units.
#[inline]
fn week(self) -> Span {
self.weeks()
}
/// Equivalent to `days()`, but reads better for singular units.
#[inline]
fn day(self) -> Span {
self.days()
}
/// Equivalent to `hours()`, but reads better for singular units.
#[inline]
fn hour(self) -> Span {
self.hours()
}
/// Equivalent to `minutes()`, but reads better for singular units.
#[inline]
fn minute(self) -> Span {
self.minutes()
}
/// Equivalent to `seconds()`, but reads better for singular units.
#[inline]
fn second(self) -> Span {
self.seconds()
}
/// Equivalent to `milliseconds()`, but reads better for singular units.
#[inline]
fn millisecond(self) -> Span {
self.milliseconds()
}
/// Equivalent to `microseconds()`, but reads better for singular units.
#[inline]
fn microsecond(self) -> Span {
self.microseconds()
}
/// Equivalent to `nanoseconds()`, but reads better for singular units.
#[inline]
fn nanosecond(self) -> Span {
self.nanoseconds()
}
}
macro_rules! impl_to_span {
($ty:ty) => {
impl ToSpan for $ty {
#[inline]
fn years(self) -> Span {
Span::new().years(self)
}
#[inline]
fn months(self) -> Span {
Span::new().months(self)
}
#[inline]
fn weeks(self) -> Span {
Span::new().weeks(self)
}
#[inline]
fn days(self) -> Span {
Span::new().days(self)
}
#[inline]
fn hours(self) -> Span {
Span::new().hours(self)
}
#[inline]
fn minutes(self) -> Span {
Span::new().minutes(self)
}
#[inline]
fn seconds(self) -> Span {
Span::new().seconds(self)
}
#[inline]
fn milliseconds(self) -> Span {
Span::new().milliseconds(self)
}
#[inline]
fn microseconds(self) -> Span {
Span::new().microseconds(self)
}
#[inline]
fn nanoseconds(self) -> Span {
Span::new().nanoseconds(self)
}
}
};
}
impl_to_span!(i8);
impl_to_span!(i16);
impl_to_span!(i32);
impl_to_span!(i64);
/// A way to refer to a single calendar or clock unit.
///
/// This type is principally used in APIs involving a [`Span`], which is a
/// duration of time. For example, routines like [`Zoned::until`] permit
/// specifying the largest unit of the span returned:
///
/// ```
/// use jiff::{Unit, Zoned};
///
/// let zdt1: Zoned = "2024-07-06 17:40-04[America/New_York]".parse()?;
/// let zdt2: Zoned = "2024-11-05 08:00-05[America/New_York]".parse()?;
/// let span = zdt1.until((Unit::Year, &zdt2))?;
/// assert_eq!(format!("{span:#}"), "3mo 29d 14h 20m");
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// But a `Unit` is also used in APIs for rounding datetimes themselves:
///
/// ```
/// use jiff::{Unit, Zoned};
///
/// let zdt: Zoned = "2024-07-06 17:44:22.158-04[America/New_York]".parse()?;
/// let nearest_minute = zdt.round(Unit::Minute)?;
/// assert_eq!(
/// nearest_minute.to_string(),
/// "2024-07-06T17:44:00-04:00[America/New_York]",
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Example: ordering
///
/// This example demonstrates that `Unit` has an ordering defined such that
/// bigger units compare greater than smaller units.
///
/// ```
/// use jiff::Unit;
///
/// assert!(Unit::Year > Unit::Nanosecond);
/// assert!(Unit::Day > Unit::Hour);
/// assert!(Unit::Hour > Unit::Minute);
/// assert!(Unit::Hour > Unit::Minute);
/// assert_eq!(Unit::Hour, Unit::Hour);
/// ```
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub enum Unit {
/// A Gregorian calendar year. It usually has 365 days for non-leap years,
/// and 366 days for leap years.
Year = 9,
/// A Gregorian calendar month. It usually has one of 28, 29, 30 or 31
/// days.
Month = 8,
/// A week is 7 days that either begins on Sunday or Monday.
Week = 7,
/// A day is usually 24 hours, but some days may have different lengths
/// due to time zone transitions.
Day = 6,
/// An hour is always 60 minutes.
Hour = 5,
/// A minute is always 60 seconds. (Jiff behaves as if leap seconds do not
/// exist.)
Minute = 4,
/// A second is always 1,000 milliseconds.
Second = 3,
/// A millisecond is always 1,000 microseconds.
Millisecond = 2,
/// A microsecond is always 1,000 nanoseconds.
Microsecond = 1,
/// A nanosecond is the smallest granularity of time supported by Jiff.
Nanosecond = 0,
}
impl Unit {
/// Returns the next biggest unit, if one exists.
pub(crate) fn next(&self) -> Option<Unit> {
match *self {
Unit::Year => None,
Unit::Month => Some(Unit::Year),
Unit::Week => Some(Unit::Month),
Unit::Day => Some(Unit::Week),
Unit::Hour => Some(Unit::Day),
Unit::Minute => Some(Unit::Hour),
Unit::Second => Some(Unit::Minute),
Unit::Millisecond => Some(Unit::Second),
Unit::Microsecond => Some(Unit::Millisecond),
Unit::Nanosecond => Some(Unit::Microsecond),
}
}
/// Returns the number of nanoseconds in this unit as a 128-bit integer.
///
/// # Panics
///
/// When this unit is always variable. That is, years or months.
pub(crate) fn nanoseconds(self) -> NoUnits128 {
match self {
Unit::Nanosecond => Constant(1),
Unit::Microsecond => t::NANOS_PER_MICRO,
Unit::Millisecond => t::NANOS_PER_MILLI,
Unit::Second => t::NANOS_PER_SECOND,
Unit::Minute => t::NANOS_PER_MINUTE,
Unit::Hour => t::NANOS_PER_HOUR,
Unit::Day => t::NANOS_PER_CIVIL_DAY,
Unit::Week => t::NANOS_PER_CIVIL_WEEK,
unit => unreachable!("{unit:?} has no definitive time interval"),
}
.rinto()
}
/// Returns true when this unit is definitively variable.
///
/// In effect, this is any unit bigger than 'day', because any such unit
/// can vary in time depending on its reference point. A 'day' can as well,
/// but we sorta special case 'day' to mean '24 hours' for cases where
/// the user is dealing with civil time.
fn is_variable(self) -> bool {
matches!(self, Unit::Year | Unit::Month | Unit::Week | Unit::Day)
}
/// A human readable singular description of this unit of time.
pub(crate) fn singular(&self) -> &'static str {
match *self {
Unit::Year => "year",
Unit::Month => "month",
Unit::Week => "week",
Unit::Day => "day",
Unit::Hour => "hour",
Unit::Minute => "minute",
Unit::Second => "second",
Unit::Millisecond => "millisecond",
Unit::Microsecond => "microsecond",
Unit::Nanosecond => "nanosecond",
}
}
/// A human readable plural description of this unit of time.
pub(crate) fn plural(&self) -> &'static str {
match *self {
Unit::Year => "years",
Unit::Month => "months",
Unit::Week => "weeks",
Unit::Day => "days",
Unit::Hour => "hours",
Unit::Minute => "minutes",
Unit::Second => "seconds",
Unit::Millisecond => "milliseconds",
Unit::Microsecond => "microseconds",
Unit::Nanosecond => "nanoseconds",
}
}
}
#[cfg(test)]
impl Unit {
fn from_usize(n: usize) -> Option<Unit> {
match n {
0 => Some(Unit::Nanosecond),
1 => Some(Unit::Microsecond),
2 => Some(Unit::Millisecond),
3 => Some(Unit::Second),
4 => Some(Unit::Minute),
5 => Some(Unit::Hour),
6 => Some(Unit::Day),
7 => Some(Unit::Week),
8 => Some(Unit::Month),
9 => Some(Unit::Year),
_ => None,
}
}
}
#[cfg(test)]
impl quickcheck::Arbitrary for Unit {
fn arbitrary(g: &mut quickcheck::Gen) -> Unit {
Unit::from_usize(usize::arbitrary(g) % 10).unwrap()
}
fn shrink(&self) -> alloc::boxed::Box<dyn Iterator<Item = Self>> {
alloc::boxed::Box::new(
(*self as usize)
.shrink()
.map(|n| Unit::from_usize(n % 10).unwrap()),
)
}
}
/// Options for [`Span::checked_add`] and [`Span::checked_sub`].
///
/// This type provides a way to ergonomically add two spans with an optional
/// relative datetime. Namely, a relative datetime is only needed when at least
/// one of the two spans being added (or subtracted) has a non-zero calendar
/// unit (years, months, weeks or days). Otherwise, an error will be returned.
///
/// Callers may use [`SpanArithmetic::days_are_24_hours`] to opt into 24-hour
/// invariant days (and 7-day weeks) without providing a relative datetime.
///
/// The main way to construct values of this type is with its `From` trait
/// implementations:
///
/// * `From<Span> for SpanArithmetic` adds (or subtracts) the given span to the
/// receiver in [`Span::checked_add`] (or [`Span::checked_sub`]).
/// * `From<(Span, civil::Date)> for SpanArithmetic` adds (or subtracts)
/// the given span to the receiver in [`Span::checked_add`] (or
/// [`Span::checked_sub`]), relative to the given date. There are also `From`
/// implementations for `civil::DateTime`, `Zoned` and [`SpanRelativeTo`].
///
/// # Example
///
/// ```
/// use jiff::ToSpan;
///
/// assert_eq!(
/// 1.hour().checked_add(30.minutes())?,
/// 1.hour().minutes(30).fieldwise(),
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[derive(Clone, Copy, Debug)]
pub struct SpanArithmetic<'a> {
duration: Duration,
relative: Option<SpanRelativeTo<'a>>,
}
impl<'a> SpanArithmetic<'a> {
/// This is a convenience function for setting the relative option on
/// this configuration to [`SpanRelativeTo::days_are_24_hours`].
///
/// # Example
///
/// When doing arithmetic on spans involving days, either a relative
/// datetime must be provided, or a special assertion opting into 24-hour
/// days is required. Otherwise, you get an error.
///
/// ```
/// use jiff::{SpanArithmetic, ToSpan};
///
/// let span1 = 2.days().hours(12);
/// let span2 = 12.hours();
/// // No relative date provided, which results in an error.
/// assert_eq!(
/// span1.checked_add(span2).unwrap_err().to_string(),
/// "using unit 'day' in a span or configuration requires that \
/// either a relative reference time be given or \
/// `SpanRelativeTo::days_are_24_hours()` is used to indicate \
/// invariant 24-hour days, but neither were provided",
/// );
/// let sum = span1.checked_add(
/// SpanArithmetic::from(span2).days_are_24_hours(),
/// )?;
/// assert_eq!(sum, 3.days().fieldwise());
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[inline]
pub fn days_are_24_hours(self) -> SpanArithmetic<'a> {
self.relative(SpanRelativeTo::days_are_24_hours())
}
}
impl<'a> SpanArithmetic<'a> {
#[inline]
fn relative<R: Into<SpanRelativeTo<'a>>>(
self,
relative: R,
) -> SpanArithmetic<'a> {
SpanArithmetic { relative: Some(relative.into()), ..self }
}
#[inline]
fn checked_add(self, span1: Span) -> Result<Span, Error> {
match self.duration.to_signed()? {
SDuration::Span(span2) => {
span1.checked_add_span(self.relative, &span2)
}
SDuration::Absolute(dur2) => {
span1.checked_add_duration(self.relative, dur2)
}
}
}
}
impl From<Span> for SpanArithmetic<'static> {
fn from(span: Span) -> SpanArithmetic<'static> {
let duration = Duration::from(span);
SpanArithmetic { duration, relative: None }
}
}
impl<'a> From<&'a Span> for SpanArithmetic<'static> {
fn from(span: &'a Span) -> SpanArithmetic<'static> {
let duration = Duration::from(*span);
SpanArithmetic { duration, relative: None }
}
}
impl From<(Span, Date)> for SpanArithmetic<'static> {
#[inline]
fn from((span, date): (Span, Date)) -> SpanArithmetic<'static> {
SpanArithmetic::from(span).relative(date)
}
}
impl From<(Span, DateTime)> for SpanArithmetic<'static> {
#[inline]
fn from((span, datetime): (Span, DateTime)) -> SpanArithmetic<'static> {
SpanArithmetic::from(span).relative(datetime)
}
}
impl<'a> From<(Span, &'a Zoned)> for SpanArithmetic<'a> {
#[inline]
fn from((span, zoned): (Span, &'a Zoned)) -> SpanArithmetic<'a> {
SpanArithmetic::from(span).relative(zoned)
}
}
impl<'a> From<(Span, SpanRelativeTo<'a>)> for SpanArithmetic<'a> {
#[inline]
fn from(
(span, relative): (Span, SpanRelativeTo<'a>),
) -> SpanArithmetic<'a> {
SpanArithmetic::from(span).relative(relative)
}
}
impl<'a> From<(&'a Span, Date)> for SpanArithmetic<'static> {
#[inline]
fn from((span, date): (&'a Span, Date)) -> SpanArithmetic<'static> {
SpanArithmetic::from(span).relative(date)
}
}
impl<'a> From<(&'a Span, DateTime)> for SpanArithmetic<'static> {
#[inline]
fn from(
(span, datetime): (&'a Span, DateTime),
) -> SpanArithmetic<'static> {
SpanArithmetic::from(span).relative(datetime)
}
}
impl<'a, 'b> From<(&'a Span, &'b Zoned)> for SpanArithmetic<'b> {
#[inline]
fn from((span, zoned): (&'a Span, &'b Zoned)) -> SpanArithmetic<'b> {
SpanArithmetic::from(span).relative(zoned)
}
}
impl<'a, 'b> From<(&'a Span, SpanRelativeTo<'b>)> for SpanArithmetic<'b> {
#[inline]
fn from(
(span, relative): (&'a Span, SpanRelativeTo<'b>),
) -> SpanArithmetic<'b> {
SpanArithmetic::from(span).relative(relative)
}
}
impl From<SignedDuration> for SpanArithmetic<'static> {
fn from(duration: SignedDuration) -> SpanArithmetic<'static> {
let duration = Duration::from(duration);
SpanArithmetic { duration, relative: None }
}
}
impl From<(SignedDuration, Date)> for SpanArithmetic<'static> {
#[inline]
fn from(
(duration, date): (SignedDuration, Date),
) -> SpanArithmetic<'static> {
SpanArithmetic::from(duration).relative(date)
}
}
impl From<(SignedDuration, DateTime)> for SpanArithmetic<'static> {
#[inline]
fn from(
(duration, datetime): (SignedDuration, DateTime),
) -> SpanArithmetic<'static> {
SpanArithmetic::from(duration).relative(datetime)
}
}
impl<'a> From<(SignedDuration, &'a Zoned)> for SpanArithmetic<'a> {
#[inline]
fn from(
(duration, zoned): (SignedDuration, &'a Zoned),
) -> SpanArithmetic<'a> {
SpanArithmetic::from(duration).relative(zoned)
}
}
impl From<UnsignedDuration> for SpanArithmetic<'static> {
fn from(duration: UnsignedDuration) -> SpanArithmetic<'static> {
let duration = Duration::from(duration);
SpanArithmetic { duration, relative: None }
}
}
impl From<(UnsignedDuration, Date)> for SpanArithmetic<'static> {
#[inline]
fn from(
(duration, date): (UnsignedDuration, Date),
) -> SpanArithmetic<'static> {
SpanArithmetic::from(duration).relative(date)
}
}
impl From<(UnsignedDuration, DateTime)> for SpanArithmetic<'static> {
#[inline]
fn from(
(duration, datetime): (UnsignedDuration, DateTime),
) -> SpanArithmetic<'static> {
SpanArithmetic::from(duration).relative(datetime)
}
}
impl<'a> From<(UnsignedDuration, &'a Zoned)> for SpanArithmetic<'a> {
#[inline]
fn from(
(duration, zoned): (UnsignedDuration, &'a Zoned),
) -> SpanArithmetic<'a> {
SpanArithmetic::from(duration).relative(zoned)
}
}
/// Options for [`Span::compare`].
///
/// This type provides a way to ergonomically compare two spans with an
/// optional relative datetime. Namely, a relative datetime is only needed when
/// at least one of the two spans being compared has a non-zero calendar unit
/// (years, months, weeks or days). Otherwise, an error will be returned.
///
/// Callers may use [`SpanCompare::days_are_24_hours`] to opt into 24-hour
/// invariant days (and 7-day weeks) without providing a relative datetime.
///
/// The main way to construct values of this type is with its `From` trait
/// implementations:
///
/// * `From<Span> for SpanCompare` compares the given span to the receiver
/// in [`Span::compare`].
/// * `From<(Span, civil::Date)> for SpanCompare` compares the given span
/// to the receiver in [`Span::compare`], relative to the given date. There
/// are also `From` implementations for `civil::DateTime`, `Zoned` and
/// [`SpanRelativeTo`].
///
/// # Example
///
/// ```
/// use jiff::ToSpan;
///
/// let span1 = 3.hours();
/// let span2 = 180.minutes();
/// assert_eq!(span1.compare(span2)?, std::cmp::Ordering::Equal);
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[derive(Clone, Copy, Debug)]
pub struct SpanCompare<'a> {
span: Span,
relative: Option<SpanRelativeTo<'a>>,
}
impl<'a> SpanCompare<'a> {
/// This is a convenience function for setting the relative option on
/// this configuration to [`SpanRelativeTo::days_are_24_hours`].
///
/// # Example
///
/// When comparing spans involving days, either a relative datetime must be
/// provided, or a special assertion opting into 24-hour days is
/// required. Otherwise, you get an error.
///
/// ```
/// use jiff::{SpanCompare, ToSpan, Unit};
///
/// let span1 = 2.days().hours(12);
/// let span2 = 60.hours();
/// // No relative date provided, which results in an error.
/// assert_eq!(
/// span1.compare(span2).unwrap_err().to_string(),
/// "using unit 'day' in a span or configuration requires that \
/// either a relative reference time be given or \
/// `SpanRelativeTo::days_are_24_hours()` is used to indicate \
/// invariant 24-hour days, but neither were provided",
/// );
/// let ordering = span1.compare(
/// SpanCompare::from(span2).days_are_24_hours(),
/// )?;
/// assert_eq!(ordering, std::cmp::Ordering::Equal);
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[inline]
pub fn days_are_24_hours(self) -> SpanCompare<'a> {
self.relative(SpanRelativeTo::days_are_24_hours())
}
}
impl<'a> SpanCompare<'a> {
#[inline]
fn new(span: Span) -> SpanCompare<'static> {
SpanCompare { span, relative: None }
}
#[inline]
fn relative<R: Into<SpanRelativeTo<'a>>>(
self,
relative: R,
) -> SpanCompare<'a> {
SpanCompare { relative: Some(relative.into()), ..self }
}
fn compare(self, span: Span) -> Result<Ordering, Error> {
let (span1, span2) = (span, self.span);
let unit = span1.largest_unit().max(span2.largest_unit());
let start = match self.relative {
Some(r) => match r.to_relative(unit)? {
Some(r) => r,
None => {
let nanos1 = span1.to_invariant_nanoseconds();
let nanos2 = span2.to_invariant_nanoseconds();
return Ok(nanos1.cmp(&nanos2));
}
},
None => {
requires_relative_date_err(unit)?;
let nanos1 = span1.to_invariant_nanoseconds();
let nanos2 = span2.to_invariant_nanoseconds();
return Ok(nanos1.cmp(&nanos2));
}
};
let end1 = start.checked_add(span1)?.to_nanosecond();
let end2 = start.checked_add(span2)?.to_nanosecond();
Ok(end1.cmp(&end2))
}
}
impl From<Span> for SpanCompare<'static> {
fn from(span: Span) -> SpanCompare<'static> {
SpanCompare::new(span)
}
}
impl<'a> From<&'a Span> for SpanCompare<'static> {
fn from(span: &'a Span) -> SpanCompare<'static> {
SpanCompare::new(*span)
}
}
impl From<(Span, Date)> for SpanCompare<'static> {
#[inline]
fn from((span, date): (Span, Date)) -> SpanCompare<'static> {
SpanCompare::from(span).relative(date)
}
}
impl From<(Span, DateTime)> for SpanCompare<'static> {
#[inline]
fn from((span, datetime): (Span, DateTime)) -> SpanCompare<'static> {
SpanCompare::from(span).relative(datetime)
}
}
impl<'a> From<(Span, &'a Zoned)> for SpanCompare<'a> {
#[inline]
fn from((span, zoned): (Span, &'a Zoned)) -> SpanCompare<'a> {
SpanCompare::from(span).relative(zoned)
}
}
impl<'a> From<(Span, SpanRelativeTo<'a>)> for SpanCompare<'a> {
#[inline]
fn from((span, relative): (Span, SpanRelativeTo<'a>)) -> SpanCompare<'a> {
SpanCompare::from(span).relative(relative)
}
}
impl<'a> From<(&'a Span, Date)> for SpanCompare<'static> {
#[inline]
fn from((span, date): (&'a Span, Date)) -> SpanCompare<'static> {
SpanCompare::from(span).relative(date)
}
}
impl<'a> From<(&'a Span, DateTime)> for SpanCompare<'static> {
#[inline]
fn from((span, datetime): (&'a Span, DateTime)) -> SpanCompare<'static> {
SpanCompare::from(span).relative(datetime)
}
}
impl<'a, 'b> From<(&'a Span, &'b Zoned)> for SpanCompare<'b> {
#[inline]
fn from((span, zoned): (&'a Span, &'b Zoned)) -> SpanCompare<'b> {
SpanCompare::from(span).relative(zoned)
}
}
impl<'a, 'b> From<(&'a Span, SpanRelativeTo<'b>)> for SpanCompare<'b> {
#[inline]
fn from(
(span, relative): (&'a Span, SpanRelativeTo<'b>),
) -> SpanCompare<'b> {
SpanCompare::from(span).relative(relative)
}
}
/// Options for [`Span::total`].
///
/// This type provides a way to ergonomically determine the number of a
/// particular unit in a span, with a potentially fractional component, with
/// an optional relative datetime. Namely, a relative datetime is only needed
/// when the span has a non-zero calendar unit (years, months, weeks or days).
/// Otherwise, an error will be returned.
///
/// Callers may use [`SpanTotal::days_are_24_hours`] to opt into 24-hour
/// invariant days (and 7-day weeks) without providing a relative datetime.
///
/// The main way to construct values of this type is with its `From` trait
/// implementations:
///
/// * `From<Unit> for SpanTotal` computes a total for the given unit in the
/// receiver span for [`Span::total`].
/// * `From<(Unit, civil::Date)> for SpanTotal` computes a total for the given
/// unit in the receiver span for [`Span::total`], relative to the given date.
/// There are also `From` implementations for `civil::DateTime`, `Zoned` and
/// [`SpanRelativeTo`].
///
/// # Example
///
/// This example shows how to find the number of seconds in a particular span:
///
/// ```
/// use jiff::{ToSpan, Unit};
///
/// let span = 3.hours().minutes(10);
/// assert_eq!(span.total(Unit::Second)?, 11_400.0);
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Example: 24 hour days
///
/// This shows how to find the total number of 24 hour days in `123,456,789`
/// seconds.
///
/// ```
/// use jiff::{SpanTotal, ToSpan, Unit};
///
/// let span = 123_456_789.seconds();
/// assert_eq!(
/// span.total(SpanTotal::from(Unit::Day).days_are_24_hours())?,
/// 1428.8980208333332,
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Example: DST is taken into account
///
/// The month of March 2024 in `America/New_York` had 31 days, but one of those
/// days was 23 hours long due a transition into daylight saving time:
///
/// ```
/// use jiff::{civil::date, ToSpan, Unit};
///
/// let span = 744.hours();
/// let relative = date(2024, 3, 1).in_tz("America/New_York")?;
/// // Because of the short day, 744 hours is actually a little *more* than
/// // 1 month starting from 2024-03-01.
/// assert_eq!(span.total((Unit::Month, &relative))?, 1.0013888888888889);
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// Now compare what happens when the relative datetime is civil and not
/// time zone aware:
///
/// ```
/// use jiff::{civil::date, ToSpan, Unit};
///
/// let span = 744.hours();
/// let relative = date(2024, 3, 1);
/// assert_eq!(span.total((Unit::Month, relative))?, 1.0);
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[derive(Clone, Copy, Debug)]
pub struct SpanTotal<'a> {
unit: Unit,
relative: Option<SpanRelativeTo<'a>>,
}
impl<'a> SpanTotal<'a> {
/// This is a convenience function for setting the relative option on
/// this configuration to [`SpanRelativeTo::days_are_24_hours`].
///
/// # Example
///
/// When computing the total duration for spans involving days, either a
/// relative datetime must be provided, or a special assertion opting into
/// 24-hour days is required. Otherwise, you get an error.
///
/// ```
/// use jiff::{civil::date, SpanTotal, ToSpan, Unit};
///
/// let span = 2.days().hours(12);
///
/// // No relative date provided, which results in an error.
/// assert_eq!(
/// span.total(Unit::Hour).unwrap_err().to_string(),
/// "using unit 'day' in a span or configuration requires that either \
/// a relative reference time be given or \
/// `SpanRelativeTo::days_are_24_hours()` is used to indicate \
/// invariant 24-hour days, but neither were provided",
/// );
///
/// // If we can assume all days are 24 hours, then we can assert it:
/// let total = span.total(
/// SpanTotal::from(Unit::Hour).days_are_24_hours(),
/// )?;
/// assert_eq!(total, 60.0);
///
/// // Or provide a relative datetime, which is preferred if possible:
/// let total = span.total((Unit::Hour, date(2025, 1, 26)))?;
/// assert_eq!(total, 60.0);
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[inline]
pub fn days_are_24_hours(self) -> SpanTotal<'a> {
self.relative(SpanRelativeTo::days_are_24_hours())
}
}
impl<'a> SpanTotal<'a> {
#[inline]
fn new(unit: Unit) -> SpanTotal<'static> {
SpanTotal { unit, relative: None }
}
#[inline]
fn relative<R: Into<SpanRelativeTo<'a>>>(
self,
relative: R,
) -> SpanTotal<'a> {
SpanTotal { relative: Some(relative.into()), ..self }
}
fn total(self, span: Span) -> Result<f64, Error> {
let max_unit = self.unit.max(span.largest_unit());
let relative = match self.relative {
Some(r) => match r.to_relative(max_unit)? {
Some(r) => r,
None => {
return Ok(self.total_invariant(span));
}
},
None => {
requires_relative_date_err(max_unit)?;
return Ok(self.total_invariant(span));
}
};
let relspan = relative.into_relative_span(self.unit, span)?;
if !self.unit.is_variable() {
return Ok(self.total_invariant(relspan.span));
}
assert!(self.unit >= Unit::Day);
let sign = relspan.span.get_sign_ranged();
let (relative_start, relative_end) = match relspan.kind {
RelativeSpanKind::Civil { start, end } => {
let start = Relative::Civil(start);
let end = Relative::Civil(end);
(start, end)
}
RelativeSpanKind::Zoned { start, end } => {
let start = Relative::Zoned(start);
let end = Relative::Zoned(end);
(start, end)
}
};
let (relative0, relative1) = clamp_relative_span(
&relative_start,
relspan.span.without_lower(self.unit),
self.unit,
sign.rinto(),
)?;
let denom = (relative1 - relative0).get() as f64;
let numer = (relative_end.to_nanosecond() - relative0).get() as f64;
let unit_val = relspan.span.get_units_ranged(self.unit).get() as f64;
Ok(unit_val + (numer / denom) * (sign.get() as f64))
}
#[inline]
fn total_invariant(&self, span: Span) -> f64 {
assert!(self.unit <= Unit::Week);
let nanos = span.to_invariant_nanoseconds();
(nanos.get() as f64) / (self.unit.nanoseconds().get() as f64)
}
}
impl From<Unit> for SpanTotal<'static> {
#[inline]
fn from(unit: Unit) -> SpanTotal<'static> {
SpanTotal::new(unit)
}
}
impl From<(Unit, Date)> for SpanTotal<'static> {
#[inline]
fn from((unit, date): (Unit, Date)) -> SpanTotal<'static> {
SpanTotal::from(unit).relative(date)
}
}
impl From<(Unit, DateTime)> for SpanTotal<'static> {
#[inline]
fn from((unit, datetime): (Unit, DateTime)) -> SpanTotal<'static> {
SpanTotal::from(unit).relative(datetime)
}
}
impl<'a> From<(Unit, &'a Zoned)> for SpanTotal<'a> {
#[inline]
fn from((unit, zoned): (Unit, &'a Zoned)) -> SpanTotal<'a> {
SpanTotal::from(unit).relative(zoned)
}
}
impl<'a> From<(Unit, SpanRelativeTo<'a>)> for SpanTotal<'a> {
#[inline]
fn from((unit, relative): (Unit, SpanRelativeTo<'a>)) -> SpanTotal<'a> {
SpanTotal::from(unit).relative(relative)
}
}
/// Options for [`Span::round`].
///
/// This type provides a way to configure the rounding of a span. This
/// includes setting the smallest unit (i.e., the unit to round), the
/// largest unit, the rounding increment, the rounding mode (e.g., "ceil" or
/// "truncate") and the datetime that the span is relative to.
///
/// `Span::round` accepts anything that implements `Into<SpanRound>`. There are
/// a few key trait implementations that make this convenient:
///
/// * `From<Unit> for SpanRound` will construct a rounding configuration where
/// the smallest unit is set to the one given.
/// * `From<(Unit, i64)> for SpanRound` will construct a rounding configuration
/// where the smallest unit and the rounding increment are set to the ones
/// given.
///
/// In order to set other options (like the largest unit, the rounding mode
/// and the relative datetime), one must explicitly create a `SpanRound` and
/// pass it to `Span::round`.
///
/// # Example
///
/// This example shows how to find how many full 3 month quarters are in a
/// particular span of time.
///
/// ```
/// use jiff::{civil::date, RoundMode, SpanRound, ToSpan, Unit};
///
/// let span1 = 10.months().days(15);
/// let round = SpanRound::new()
/// .smallest(Unit::Month)
/// .increment(3)
/// .mode(RoundMode::Trunc)
/// // A relative datetime must be provided when
/// // rounding involves calendar units.
/// .relative(date(2024, 1, 1));
/// let span2 = span1.round(round)?;
/// assert_eq!(span2.get_months() / 3, 3);
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[derive(Clone, Copy, Debug)]
pub struct SpanRound<'a> {
largest: Option<Unit>,
smallest: Unit,
mode: RoundMode,
increment: i64,
relative: Option<SpanRelativeTo<'a>>,
}
impl<'a> SpanRound<'a> {
/// Create a new default configuration for rounding a span via
/// [`Span::round`].
///
/// The default configuration does no rounding.
#[inline]
pub fn new() -> SpanRound<'static> {
SpanRound {
largest: None,
smallest: Unit::Nanosecond,
mode: RoundMode::HalfExpand,
increment: 1,
relative: None,
}
}
/// Set the smallest units allowed in the span returned. These are the
/// units that the span is rounded to.
///
/// # Errors
///
/// The smallest units must be no greater than the largest units. If this
/// is violated, then rounding a span with this configuration will result
/// in an error.
///
/// If a smallest unit bigger than days is selected without a relative
/// datetime reference point, then an error is returned when using this
/// configuration with [`Span::round`].
///
/// # Example
///
/// A basic example that rounds to the nearest minute:
///
/// ```
/// use jiff::{ToSpan, Unit};
///
/// let span = 15.minutes().seconds(46);
/// assert_eq!(span.round(Unit::Minute)?, 16.minutes().fieldwise());
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[inline]
pub fn smallest(self, unit: Unit) -> SpanRound<'a> {
SpanRound { smallest: unit, ..self }
}
/// Set the largest units allowed in the span returned.
///
/// When a largest unit is not specified, then it defaults to the largest
/// non-zero unit that is at least as big as the configured smallest
/// unit. For example, given a span of `2 months 17 hours`, the default
/// largest unit would be `Unit::Month`. The default implies that a span's
/// units do not get "bigger" than what was given.
///
/// Once a largest unit is set, there is no way to change this rounding
/// configuration back to using the "automatic" default. Instead, callers
/// must create a new configuration.
///
/// If a largest unit is set and no other options are set, then the
/// rounding operation can be said to be a "re-balancing." That is, the
/// span won't lose precision, but the way in which it is expressed may
/// change.
///
/// # Errors
///
/// The largest units, when set, must be at least as big as the smallest
/// units (which defaults to [`Unit::Nanosecond`]). If this is violated,
/// then rounding a span with this configuration will result in an error.
///
/// If a largest unit bigger than days is selected without a relative
/// datetime reference point, then an error is returned when using this
/// configuration with [`Span::round`].
///
/// # Example: re-balancing
///
/// This shows how a span can be re-balanced without losing precision:
///
/// ```
/// use jiff::{SpanRound, ToSpan, Unit};
///
/// let span = 86_401_123_456_789i64.nanoseconds();
/// assert_eq!(
/// span.round(SpanRound::new().largest(Unit::Hour))?.fieldwise(),
/// 24.hours().seconds(1).milliseconds(123).microseconds(456).nanoseconds(789),
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// If you need to use a largest unit bigger than hours, then you must
/// provide a relative datetime as a reference point (otherwise an error
/// will occur):
///
/// ```
/// use jiff::{civil::date, SpanRound, ToSpan, Unit};
///
/// let span = 3_968_000.seconds();
/// let round = SpanRound::new()
/// .largest(Unit::Day)
/// .relative(date(2024, 7, 1));
/// assert_eq!(
/// span.round(round)?,
/// 45.days().hours(22).minutes(13).seconds(20).fieldwise(),
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// As a special case for days, one can instead opt into invariant 24-hour
/// days (and 7-day weeks) without providing an explicit relative date:
///
/// ```
/// use jiff::{SpanRound, ToSpan, Unit};
///
/// let span = 86_401_123_456_789i64.nanoseconds();
/// assert_eq!(
/// span.round(
/// SpanRound::new().largest(Unit::Day).days_are_24_hours(),
/// )?.fieldwise(),
/// 1.day().seconds(1).milliseconds(123).microseconds(456).nanoseconds(789),
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Example: re-balancing while taking DST into account
///
/// When given a zone aware relative datetime, rounding will even take
/// DST into account:
///
/// ```
/// use jiff::{SpanRound, ToSpan, Unit, Zoned};
///
/// let span = 2756.hours();
/// let zdt = "2020-01-01T00:00+01:00[Europe/Rome]".parse::<Zoned>()?;
/// let round = SpanRound::new().largest(Unit::Year).relative(&zdt);
/// assert_eq!(
/// span.round(round)?,
/// 3.months().days(23).hours(21).fieldwise(),
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// Now compare with the same operation, but on a civil datetime (which is
/// not aware of time zone):
///
/// ```
/// use jiff::{civil::DateTime, SpanRound, ToSpan, Unit};
///
/// let span = 2756.hours();
/// let dt = "2020-01-01T00:00".parse::<DateTime>()?;
/// let round = SpanRound::new().largest(Unit::Year).relative(dt);
/// assert_eq!(
/// span.round(round)?,
/// 3.months().days(23).hours(20).fieldwise(),
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// The result is 1 hour shorter. This is because, in the zone
/// aware re-balancing, it accounts for the transition into DST at
/// `2020-03-29T01:00Z`, which skips an hour. This makes the span one hour
/// longer because one of the days in the span is actually only 23 hours
/// long instead of 24 hours.
#[inline]
pub fn largest(self, unit: Unit) -> SpanRound<'a> {
SpanRound { largest: Some(unit), ..self }
}
/// Set the rounding mode.
///
/// This defaults to [`RoundMode::HalfExpand`], which makes rounding work
/// like how you were taught in school.
///
/// # Example
///
/// A basic example that rounds to the nearest minute, but changing its
/// rounding mode to truncation:
///
/// ```
/// use jiff::{RoundMode, SpanRound, ToSpan, Unit};
///
/// let span = 15.minutes().seconds(46);
/// assert_eq!(
/// span.round(SpanRound::new()
/// .smallest(Unit::Minute)
/// .mode(RoundMode::Trunc),
/// )?,
/// // The default round mode does rounding like
/// // how you probably learned in school, and would
/// // result in rounding up to 16 minutes. But we
/// // change it to truncation here, which makes it
/// // round down.
/// 15.minutes().fieldwise(),
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[inline]
pub fn mode(self, mode: RoundMode) -> SpanRound<'a> {
SpanRound { mode, ..self }
}
/// Set the rounding increment for the smallest unit.
///
/// The default value is `1`. Other values permit rounding the smallest
/// unit to the nearest integer increment specified. For example, if the
/// smallest unit is set to [`Unit::Minute`], then a rounding increment of
/// `30` would result in rounding in increments of a half hour. That is,
/// the only minute value that could result would be `0` or `30`.
///
/// # Errors
///
/// When the smallest unit is less than days, the rounding increment must
/// divide evenly into the next highest unit after the smallest unit
/// configured (and must not be equivalent to it). For example, if the
/// smallest unit is [`Unit::Nanosecond`], then *some* of the valid values
/// for the rounding increment are `1`, `2`, `4`, `5`, `100` and `500`.
/// Namely, any integer that divides evenly into `1,000` nanoseconds since
/// there are `1,000` nanoseconds in the next highest unit (microseconds).
///
/// The error will occur when computing the span, and not when setting
/// the increment here.
///
/// # Example
///
/// This shows how to round a span to the nearest 5 minute increment:
///
/// ```
/// use jiff::{ToSpan, Unit};
///
/// let span = 4.hours().minutes(2).seconds(30);
/// assert_eq!(
/// span.round((Unit::Minute, 5))?,
/// 4.hours().minutes(5).fieldwise(),
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[inline]
pub fn increment(self, increment: i64) -> SpanRound<'a> {
SpanRound { increment, ..self }
}
/// Set the relative datetime to use when rounding a span.
///
/// A relative datetime is only required when calendar units (units greater
/// than days) are involved. This includes having calendar units in the
/// original span, or calendar units in the configured smallest or largest
/// unit. A relative datetime is required when calendar units are used
/// because the duration of a particular calendar unit (like 1 month or 1
/// year) is variable and depends on the date. For example, 1 month from
/// 2024-01-01 is 31 days, but 1 month from 2024-02-01 is 29 days.
///
/// A relative datetime is provided by anything that implements
/// `Into<SpanRelativeTo>`. There are a few convenience trait
/// implementations provided:
///
/// * `From<&Zoned> for SpanRelativeTo` uses a zone aware datetime to do
/// rounding. In this case, rounding will take time zone transitions into
/// account. In particular, when using a zoned relative datetime, not all
/// days are necessarily 24 hours.
/// * `From<civil::DateTime> for SpanRelativeTo` uses a civil datetime. In
/// this case, all days will be considered 24 hours long.
/// * `From<civil::Date> for SpanRelativeTo` uses a civil date. In this
/// case, all days will be considered 24 hours long.
///
/// Note that one can impose 24-hour days without providing a reference
/// date via [`SpanRelativeTo::days_are_24_hours`].
///
/// # Errors
///
/// If rounding involves a calendar unit (units bigger than hours) and no
/// relative datetime is provided, then this configuration will lead to
/// an error when used with [`Span::round`].
///
/// # Example
///
/// This example shows very precisely how a DST transition can impact
/// rounding and re-balancing. For example, consider the day `2024-11-03`
/// in `America/New_York`. On this day, the 1 o'clock hour was repeated,
/// making the day 24 hours long. This will be taken into account when
/// rounding if a zoned datetime is provided as a reference point:
///
/// ```
/// use jiff::{SpanRound, ToSpan, Unit, Zoned};
///
/// let zdt = "2024-11-03T00-04[America/New_York]".parse::<Zoned>()?;
/// let round = SpanRound::new().largest(Unit::Hour).relative(&zdt);
/// assert_eq!(1.day().round(round)?, 25.hours().fieldwise());
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// And similarly for `2024-03-10`, where the 2 o'clock hour was skipped
/// entirely:
///
/// ```
/// use jiff::{SpanRound, ToSpan, Unit, Zoned};
///
/// let zdt = "2024-03-10T00-05[America/New_York]".parse::<Zoned>()?;
/// let round = SpanRound::new().largest(Unit::Hour).relative(&zdt);
/// assert_eq!(1.day().round(round)?, 23.hours().fieldwise());
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[inline]
pub fn relative<R: Into<SpanRelativeTo<'a>>>(
self,
relative: R,
) -> SpanRound<'a> {
SpanRound { relative: Some(relative.into()), ..self }
}
/// This is a convenience function for setting the relative option on
/// this configuration to [`SpanRelativeTo::days_are_24_hours`].
///
/// # Example
///
/// When rounding spans involving days, either a relative datetime must be
/// provided, or a special assertion opting into 24-hour days is
/// required. Otherwise, you get an error.
///
/// ```
/// use jiff::{SpanRound, ToSpan, Unit};
///
/// let span = 2.days().hours(12);
/// // No relative date provided, which results in an error.
/// assert_eq!(
/// span.round(Unit::Day).unwrap_err().to_string(),
/// "error with `smallest` rounding option: using unit 'day' in a \
/// span or configuration requires that either a relative reference \
/// time be given or `SpanRelativeTo::days_are_24_hours()` is used \
/// to indicate invariant 24-hour days, but neither were provided",
/// );
/// let rounded = span.round(
/// SpanRound::new().smallest(Unit::Day).days_are_24_hours(),
/// )?;
/// assert_eq!(rounded, 3.days().fieldwise());
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[inline]
pub fn days_are_24_hours(self) -> SpanRound<'a> {
self.relative(SpanRelativeTo::days_are_24_hours())
}
/// Returns the configured smallest unit on this round configuration.
#[inline]
pub(crate) fn get_smallest(&self) -> Unit {
self.smallest
}
/// Returns the configured largest unit on this round configuration.
#[inline]
pub(crate) fn get_largest(&self) -> Option<Unit> {
self.largest
}
/// Returns true only when rounding a span *may* change it. When it
/// returns false, and if the span is already balanced according to
/// the largest unit in this round configuration, then it is guaranteed
/// that rounding is a no-op.
///
/// This is useful to avoid rounding calls after doing span arithmetic
/// on datetime types. This works because the "largest" unit is used to
/// construct a balanced span for the difference between two datetimes.
/// So we already know the span has been balanced. If this weren't the
/// case, then the largest unit being different from the one in the span
/// could result in rounding making a change. (And indeed, in the general
/// case of span rounding below, we do a more involved check for this.)
#[inline]
pub(crate) fn rounding_may_change_span_ignore_largest(&self) -> bool {
self.smallest > Unit::Nanosecond || self.increment > 1
}
/// Does the actual span rounding.
fn round(&self, span: Span) -> Result<Span, Error> {
let existing_largest = span.largest_unit();
let mode = self.mode;
let smallest = self.smallest;
let largest =
self.largest.unwrap_or_else(|| smallest.max(existing_largest));
let max = existing_largest.max(largest);
let increment = increment::for_span(smallest, self.increment)?;
if largest < smallest {
return Err(err!(
"largest unit ('{largest}') cannot be smaller than \
smallest unit ('{smallest}')",
largest = largest.singular(),
smallest = smallest.singular(),
));
}
let relative = match self.relative {
Some(ref r) => {
match r.to_relative(max)? {
Some(r) => r,
None => {
// If our reference point is civil time, then its units
// are invariant as long as we are using day-or-lower
// everywhere. That is, the length of the duration is
// independent of the reference point. In which case,
// rounding is a simple matter of converting the span
// to a number of nanoseconds and then rounding that.
return Ok(round_span_invariant(
span, smallest, largest, increment, mode,
)?);
}
}
}
None => {
// This is only okay if none of our units are above 'day'.
// That is, a reference point is only necessary when there is
// no reasonable invariant interpretation of the span. And this
// is only true when everything is less than 'day'.
requires_relative_date_err(smallest)
.context("error with `smallest` rounding option")?;
if let Some(largest) = self.largest {
requires_relative_date_err(largest)
.context("error with `largest` rounding option")?;
}
requires_relative_date_err(existing_largest).context(
"error with largest unit in span to be rounded",
)?;
assert!(max <= Unit::Week);
return Ok(round_span_invariant(
span, smallest, largest, increment, mode,
)?);
}
};
relative.round(span, smallest, largest, increment, mode)
}
}
impl Default for SpanRound<'static> {
fn default() -> SpanRound<'static> {
SpanRound::new()
}
}
impl From<Unit> for SpanRound<'static> {
fn from(unit: Unit) -> SpanRound<'static> {
SpanRound::default().smallest(unit)
}
}
impl From<(Unit, i64)> for SpanRound<'static> {
fn from((unit, increment): (Unit, i64)) -> SpanRound<'static> {
SpanRound::default().smallest(unit).increment(increment)
}
}
/// A relative datetime for use with [`Span`] APIs.
///
/// A relative datetime can be one of the following: [`civil::Date`](Date),
/// [`civil::DateTime`](DateTime) or [`Zoned`]. It can be constructed from any
/// of the preceding types via `From` trait implementations.
///
/// A relative datetime is used to indicate how the calendar units of a `Span`
/// should be interpreted. For example, the span "1 month" does not have a
/// fixed meaning. One month from `2024-03-01` is 31 days, but one month from
/// `2024-04-01` is 30 days. Similar for years.
///
/// When a relative datetime in time zone aware (i.e., it is a `Zoned`), then
/// a `Span` will also consider its day units to be variable in length. For
/// example, `2024-03-10` in `America/New_York` was only 23 hours long, where
/// as `2024-11-03` in `America/New_York` was 25 hours long. When a relative
/// datetime is civil, then days are considered to always be of a fixed 24
/// hour length.
///
/// This type is principally used as an input to one of several different
/// [`Span`] APIs:
///
/// * [`Span::round`] rounds spans. A relative datetime is necessary when
/// dealing with calendar units. (But spans without calendar units can be
/// rounded without providing a relative datetime.)
/// * Span arithmetic via [`Span::checked_add`] and [`Span::checked_sub`].
/// A relative datetime is needed when adding or subtracting spans with
/// calendar units.
/// * Span comarisons via [`Span::compare`] require a relative datetime when
/// comparing spans with calendar units.
/// * Computing the "total" duration as a single floating point number via
/// [`Span::total`] also requires a relative datetime when dealing with
/// calendar units.
///
/// # Example
///
/// This example shows how to round a span with larger calendar units to
/// smaller units:
///
/// ```
/// use jiff::{SpanRound, ToSpan, Unit, Zoned};
///
/// let zdt: Zoned = "2012-01-01[Antarctica/Troll]".parse()?;
/// let round = SpanRound::new().largest(Unit::Day).relative(&zdt);
/// assert_eq!(1.year().round(round)?, 366.days().fieldwise());
///
/// // If you tried this without a relative datetime, it would fail:
/// let round = SpanRound::new().largest(Unit::Day);
/// assert!(1.year().round(round).is_err());
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[derive(Clone, Copy, Debug)]
pub struct SpanRelativeTo<'a> {
kind: SpanRelativeToKind<'a>,
}
impl<'a> SpanRelativeTo<'a> {
/// Creates a special marker that indicates all days ought to be assumed
/// to be 24 hours without providing a relative reference time.
///
/// This is relevant to the following APIs:
///
/// * [`Span::checked_add`]
/// * [`Span::checked_sub`]
/// * [`Span::compare`]
/// * [`Span::total`]
/// * [`Span::round`]
/// * [`Span::to_duration`]
///
/// Specifically, in a previous version of Jiff, the above APIs permitted
/// _silently_ assuming that days are always 24 hours when a relative
/// reference date wasn't provided. In the current version of Jiff, this
/// silent interpretation no longer happens and instead an error will
/// occur.
///
/// If you need to use these APIs with spans that contain non-zero units
/// of days or weeks but without a relative reference date, then you may
/// use this routine to create a special marker for `SpanRelativeTo` that
/// permits the APIs above to assume days are always 24 hours.
///
/// # Motivation
///
/// The purpose of the marker is two-fold:
///
/// * Requiring the marker is important for improving the consistency of
/// `Span` APIs. Previously, some APIs (like [`Timestamp::checked_add`])
/// would always return an error if the `Span` given had non-zero
/// units of days or greater. On the other hand, other APIs (like
/// [`Span::checked_add`]) would autoamtically assume days were always
/// 24 hours if no relative reference time was given and either span had
/// non-zero units of days. With this marker, APIs _never_ assume days are
/// always 24 hours automatically.
/// * When it _is_ appropriate to assume all days are 24 hours
/// (for example, when only dealing with spans derived from
/// [`civil`](crate::civil) datetimes) and where providing a relative
/// reference datetime doesn't make sense. In this case, one _could_
/// provide a "dummy" reference date since the precise date in civil time
/// doesn't impact the length of a day. But a marker like the one returned
/// here is more explicit for the purpose of assuming days are always 24
/// hours.
///
/// With that said, ideally, callers should provide a relative reference
/// datetime if possible.
///
/// See [Issue #48] for more discussion on this topic.
///
/// # Example: different interpretations of "1 day"
///
/// This example shows how "1 day" can be interpreted differently via the
/// [`Span::total`] API:
///
/// ```
/// use jiff::{SpanRelativeTo, ToSpan, Unit, Zoned};
///
/// let span = 1.day();
///
/// // An error because days aren't always 24 hours:
/// assert_eq!(
/// span.total(Unit::Hour).unwrap_err().to_string(),
/// "using unit 'day' in a span or configuration requires that either \
/// a relative reference time be given or \
/// `SpanRelativeTo::days_are_24_hours()` is used to indicate \
/// invariant 24-hour days, but neither were provided",
/// );
/// // Opt into invariant 24 hour days without a relative date:
/// let marker = SpanRelativeTo::days_are_24_hours();
/// let hours = span.total((Unit::Hour, marker))?;
/// assert_eq!(hours, 24.0);
/// // Days can be shorter than 24 hours:
/// let zdt: Zoned = "2024-03-10[America/New_York]".parse()?;
/// let hours = span.total((Unit::Hour, &zdt))?;
/// assert_eq!(hours, 23.0);
/// // Days can be longer than 24 hours:
/// let zdt: Zoned = "2024-11-03[America/New_York]".parse()?;
/// let hours = span.total((Unit::Hour, &zdt))?;
/// assert_eq!(hours, 25.0);
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// Similar behavior applies to the other APIs listed above.
///
/// # Example: different interpretations of "1 week"
///
/// This example shows how "1 week" can be interpreted differently via the
/// [`Span::total`] API:
///
/// ```
/// use jiff::{SpanRelativeTo, ToSpan, Unit, Zoned};
///
/// let span = 1.week();
///
/// // An error because days aren't always 24 hours:
/// assert_eq!(
/// span.total(Unit::Hour).unwrap_err().to_string(),
/// "using unit 'week' in a span or configuration requires that either \
/// a relative reference time be given or \
/// `SpanRelativeTo::days_are_24_hours()` is used to indicate \
/// invariant 24-hour days, but neither were provided",
/// );
/// // Opt into invariant 24 hour days without a relative date:
/// let marker = SpanRelativeTo::days_are_24_hours();
/// let hours = span.total((Unit::Hour, marker))?;
/// assert_eq!(hours, 168.0);
/// // Weeks can be shorter than 24*7 hours:
/// let zdt: Zoned = "2024-03-10[America/New_York]".parse()?;
/// let hours = span.total((Unit::Hour, &zdt))?;
/// assert_eq!(hours, 167.0);
/// // Weeks can be longer than 24*7 hours:
/// let zdt: Zoned = "2024-11-03[America/New_York]".parse()?;
/// let hours = span.total((Unit::Hour, &zdt))?;
/// assert_eq!(hours, 169.0);
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Example: working with [`civil::Date`](crate::civil::Date)
///
/// A `Span` returned by computing the difference in time between two
/// [`civil::Date`](crate::civil::Date)s will have a non-zero number of
/// days. In older versions of Jiff, if one wanted to add spans returned by
/// these APIs, you could do so without futzing with relative dates. But
/// now you either need to provide a relative date:
///
/// ```
/// use jiff::{civil::date, ToSpan};
///
/// let d1 = date(2025, 1, 18);
/// let d2 = date(2025, 1, 26);
/// let d3 = date(2025, 2, 14);
///
/// let span1 = d2 - d1;
/// let span2 = d3 - d2;
/// let total = span1.checked_add((span2, d1))?;
/// assert_eq!(total, 27.days().fieldwise());
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// Or you can provide a marker indicating that days are always 24 hours.
/// This is fine for this use case since one is only doing civil calendar
/// arithmetic and not working with time zones:
///
/// ```
/// use jiff::{civil::date, SpanRelativeTo, ToSpan};
///
/// let d1 = date(2025, 1, 18);
/// let d2 = date(2025, 1, 26);
/// let d3 = date(2025, 2, 14);
///
/// let span1 = d2 - d1;
/// let span2 = d3 - d2;
/// let total = span1.checked_add(
/// (span2, SpanRelativeTo::days_are_24_hours()),
/// )?;
/// assert_eq!(total, 27.days().fieldwise());
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// [Issue #48]: https://github.com/BurntSushi/jiff/issues/48
#[inline]
pub const fn days_are_24_hours() -> SpanRelativeTo<'static> {
let kind = SpanRelativeToKind::DaysAre24Hours;
SpanRelativeTo { kind }
}
/// Converts this public API relative datetime into a more versatile
/// internal representation of the same concept.
///
/// Basically, the internal `Relative` type is `Cow` which means it isn't
/// `Copy`. But it can present a more uniform API. The public API type
/// doesn't have `Cow` so that it can be `Copy`.
///
/// We also take this opportunity to attach some convenient data, such
/// as a timestamp when the relative datetime type is civil.
///
/// This can return `None` if this `SpanRelativeTo` isn't actually a
/// datetime but a "marker" indicating some unit (like days) should be
/// treated as invariant. Or `None` is returned when the given unit is
/// always invariant (hours or smaller).
///
/// # Errors
///
/// If there was a problem doing this conversion, then an error is
/// returned. In practice, this only occurs for a civil datetime near the
/// civil datetime minimum and maximum values.
fn to_relative(&self, unit: Unit) -> Result<Option<Relative<'a>>, Error> {
if !unit.is_variable() {
return Ok(None);
}
match self.kind {
SpanRelativeToKind::Civil(dt) => {
Ok(Some(Relative::Civil(RelativeCivil::new(dt)?)))
}
SpanRelativeToKind::Zoned(zdt) => {
Ok(Some(Relative::Zoned(RelativeZoned {
zoned: DumbCow::Borrowed(zdt),
})))
}
SpanRelativeToKind::DaysAre24Hours => {
if matches!(unit, Unit::Year | Unit::Month) {
return Err(err!(
"using unit '{unit}' in span or configuration \
requires that a relative reference time be given \
(`SpanRelativeTo::days_are_24_hours()` was given \
but this only permits using days and weeks \
without a relative reference time)",
unit = unit.singular(),
));
}
Ok(None)
}
}
}
}
#[derive(Clone, Copy, Debug)]
enum SpanRelativeToKind<'a> {
Civil(DateTime),
Zoned(&'a Zoned),
DaysAre24Hours,
}
impl<'a> From<&'a Zoned> for SpanRelativeTo<'a> {
fn from(zdt: &'a Zoned) -> SpanRelativeTo<'a> {
SpanRelativeTo { kind: SpanRelativeToKind::Zoned(zdt) }
}
}
impl From<DateTime> for SpanRelativeTo<'static> {
fn from(dt: DateTime) -> SpanRelativeTo<'static> {
SpanRelativeTo { kind: SpanRelativeToKind::Civil(dt) }
}
}
impl From<Date> for SpanRelativeTo<'static> {
fn from(date: Date) -> SpanRelativeTo<'static> {
let dt = DateTime::from_parts(date, Time::midnight());
SpanRelativeTo { kind: SpanRelativeToKind::Civil(dt) }
}
}
impl<'a> core::fmt::Display for SpanRelativeToKind<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match *self {
SpanRelativeToKind::Civil(dt) => core::fmt::Display::fmt(&dt, f),
SpanRelativeToKind::Zoned(zdt) => core::fmt::Display::fmt(zdt, f),
SpanRelativeToKind::DaysAre24Hours => write!(f, "TODO"),
}
}
}
/// An internal abstraction for managing a relative datetime for use in some
/// `Span` APIs.
///
/// This is effectively the same as a `SpanRelativeTo`, but uses a `Cow<Zoned>`
/// instead of a `&Zoned`. This makes it non-`Copy`, but allows us to craft a
/// more uniform API. (i.e., `relative + span = relative` instead of `relative
/// + span = owned_relative` or whatever.) Note that the `Copy` impl on
/// `SpanRelativeTo` means it has to accept a `&Zoned`. It can't ever take a
/// `Zoned` since it is non-Copy.
///
/// NOTE: Separately from above, I think it's plausible that this type could be
/// designed a bit differently. Namely, something like this:
///
/// ```text
/// struct Relative<'a> {
/// tz: Option<&'a TimeZone>,
/// dt: DateTime,
/// ts: Timestamp,
/// }
/// ```
///
/// That is, we do zone aware stuff but without an actual `Zoned` type. But I
/// think in order to make that work, we would need to expose most of the
/// `Zoned` API as functions on its component types (DateTime, Timestamp and
/// TimeZone). I think we are likely to want to do that for public API reasons,
/// but I'd like to resist it since I think it will add a lot of complexity.
/// Or maybe we need a `Unzoned` type that is `DateTime` and `Timestamp`, but
/// requires passing the time zone in to each of its methods. That might work
/// quite well, even if it was just an internal type.
///
/// Anyway, I'm not 100% sure the above would work, but I think it would. It
/// would be nicer because everything would be `Copy` all the time. We'd never
/// need a `Cow<TimeZone>` for example, because we never need to change or
/// create a new time zone.
#[derive(Clone, Debug)]
enum Relative<'a> {
Civil(RelativeCivil),
Zoned(RelativeZoned<'a>),
}
impl<'a> Relative<'a> {
/// Adds the given span to this relative datetime.
///
/// This defers to either [`DateTime::checked_add`] or
/// [`Zoned::checked_add`], depending on the type of relative datetime.
///
/// The `Relative` datetime returned is guaranteed to have the same
/// internal datetie type as `self`.
///
/// # Errors
///
/// This returns an error in the same cases as the underlying checked
/// arithmetic APIs. In general, this occurs when adding the given `span`
/// would result in overflow.
fn checked_add(&self, span: Span) -> Result<Relative, Error> {
match *self {
Relative::Civil(dt) => Ok(Relative::Civil(dt.checked_add(span)?)),
Relative::Zoned(ref zdt) => {
Ok(Relative::Zoned(zdt.checked_add(span)?))
}
}
}
fn checked_add_duration(
&self,
duration: SignedDuration,
) -> Result<Relative, Error> {
match *self {
Relative::Civil(dt) => {
Ok(Relative::Civil(dt.checked_add_duration(duration)?))
}
Relative::Zoned(ref zdt) => {
Ok(Relative::Zoned(zdt.checked_add_duration(duration)?))
}
}
}
/// Returns the span of time from this relative datetime to the one given,
/// with units as large as `largest`.
///
/// # Errors
///
/// This returns an error in the same cases as when the underlying
/// [`DateTime::until`] or [`Zoned::until`] fail. Because this doesn't
/// set or expose any rounding configuration, this can generally only
/// occur when `largest` is `Unit::Nanosecond` and the span of time
/// between `self` and `other` is too big to represent as a 64-bit integer
/// nanosecond count.
///
/// # Panics
///
/// This panics if `self` and `other` are different internal datetime
/// types. For example, if `self` was a civil datetime and `other` were
/// a zoned datetime.
fn until(&self, largest: Unit, other: &Relative) -> Result<Span, Error> {
match (self, other) {
(&Relative::Civil(ref dt1), &Relative::Civil(ref dt2)) => {
dt1.until(largest, dt2)
}
(&Relative::Zoned(ref zdt1), &Relative::Zoned(ref zdt2)) => {
zdt1.until(largest, zdt2)
}
// This would be bad if `Relative` were a public API, but in
// practice, this case never occurs because we don't mixup our
// `Relative` datetime types.
_ => unreachable!(),
}
}
/// Converts this relative datetime to a nanosecond in UTC time.
///
/// # Errors
///
/// If there was a problem doing this conversion, then an error is
/// returned. In practice, this only occurs for a civil datetime near the
/// civil datetime minimum and maximum values.
fn to_nanosecond(&self) -> NoUnits128 {
match *self {
Relative::Civil(dt) => dt.timestamp.as_nanosecond_ranged().rinto(),
Relative::Zoned(ref zdt) => {
zdt.zoned.timestamp().as_nanosecond_ranged().rinto()
}
}
}
/// Create a balanced span of time relative to this datetime.
///
/// The relative span returned has the same internal datetime type
/// (civil or zoned) as this relative datetime.
///
/// # Errors
///
/// This returns an error when the span in this range cannot be
/// represented. In general, this only occurs when asking for largest units
/// of `Unit::Nanosecond` *and* when the span is too big to fit into a
/// 64-bit nanosecond count.
///
/// This can also return an error in other extreme cases, such as when
/// adding the given span to this relative datetime results in overflow,
/// or if this relative datetime is a civil datetime and it couldn't be
/// converted to a timestamp in UTC.
fn into_relative_span(
self,
largest: Unit,
span: Span,
) -> Result<RelativeSpan<'a>, Error> {
let kind = match self {
Relative::Civil(start) => {
let end = start.checked_add(span)?;
RelativeSpanKind::Civil { start, end }
}
Relative::Zoned(start) => {
let end = start.checked_add(span)?;
RelativeSpanKind::Zoned { start, end }
}
};
let relspan = kind.into_relative_span(largest)?;
if span.get_sign_ranged() != 0
&& relspan.span.get_sign_ranged() != 0
&& span.get_sign_ranged() != relspan.span.get_sign_ranged()
{
// I haven't quite figured out when this case is hit. I think it's
// actually impossible right? Balancing a duration should not flip
// the sign.
//
// ref: https://github.com/fullcalendar/temporal-polyfill/blob/9e001042864394247181d1a5d591c18057ce32d2/packages/temporal-polyfill/src/internal/durationMath.ts#L236-L238
unreachable!(
"balanced span should have same sign as original span"
)
}
Ok(relspan)
}
/// Rounds the given span using the given rounding configuration.
fn round(
self,
span: Span,
smallest: Unit,
largest: Unit,
increment: NoUnits128,
mode: RoundMode,
) -> Result<Span, Error> {
let relspan = self.into_relative_span(largest, span)?;
if relspan.span.get_sign_ranged() == 0 {
return Ok(relspan.span);
}
let nudge = match relspan.kind {
RelativeSpanKind::Civil { start, end } => {
if smallest > Unit::Day {
Nudge::relative_calendar(
relspan.span,
&Relative::Civil(start),
&Relative::Civil(end),
smallest,
increment,
mode,
)?
} else {
let relative_end = end.timestamp.as_nanosecond_ranged();
Nudge::relative_invariant(
relspan.span,
relative_end.rinto(),
smallest,
largest,
increment,
mode,
)?
}
}
RelativeSpanKind::Zoned { ref start, ref end } => {
if smallest >= Unit::Day {
Nudge::relative_calendar(
relspan.span,
// FIXME: Find a way to drop these clones.
&Relative::Zoned(start.clone()),
&Relative::Zoned(end.clone()),
smallest,
increment,
mode,
)?
} else if largest >= Unit::Day {
// This is a special case for zoned datetimes when rounding
// could bleed into variable units.
Nudge::relative_zoned_time(
relspan.span,
start,
smallest,
increment,
mode,
)?
} else {
// Otherwise, rounding is the same as civil datetime.
let relative_end =
end.zoned.timestamp().as_nanosecond_ranged();
Nudge::relative_invariant(
relspan.span,
relative_end.rinto(),
smallest,
largest,
increment,
mode,
)?
}
}
};
nudge.bubble(&relspan, smallest, largest)
}
}
/// A balanced span between a range of civil or zoned datetimes.
///
/// The span is always balanced up to a certain unit as given to
/// `RelativeSpanKind::into_relative_span`.
#[derive(Clone, Debug)]
struct RelativeSpan<'a> {
span: Span,
kind: RelativeSpanKind<'a>,
}
/// A civil or zoned datetime range of time.
#[derive(Clone, Debug)]
enum RelativeSpanKind<'a> {
Civil { start: RelativeCivil, end: RelativeCivil },
Zoned { start: RelativeZoned<'a>, end: RelativeZoned<'a> },
}
impl<'a> RelativeSpanKind<'a> {
/// Create a balanced `RelativeSpan` from this range of time.
///
/// # Errors
///
/// This returns an error when the span in this range cannot be
/// represented. In general, this only occurs when asking for largest units
/// of `Unit::Nanosecond` *and* when the span is too big to fit into a
/// 64-bit nanosecond count.
fn into_relative_span(
self,
largest: Unit,
) -> Result<RelativeSpan<'a>, Error> {
let span = match self {
RelativeSpanKind::Civil { ref start, ref end } => start
.datetime
.until((largest, end.datetime))
.with_context(|| {
err!(
"failed to get span between {start} and {end} \
with largest unit as {unit}",
start = start.datetime,
end = end.datetime,
unit = largest.plural(),
)
})?,
RelativeSpanKind::Zoned { ref start, ref end } => start
.zoned
.until((largest, &*end.zoned))
.with_context(|| {
err!(
"failed to get span between {start} and {end} \
with largest unit as {unit}",
start = start.zoned,
end = end.zoned,
unit = largest.plural(),
)
})?,
};
Ok(RelativeSpan { span, kind: self })
}
}
/// A wrapper around a civil datetime and a timestamp corresponding to that
/// civil datetime in UTC.
///
/// Haphazardly interpreting a civil datetime in UTC is an odd and *usually*
/// incorrect thing to do. But the way we use it here is basically just to give
/// it an "anchoring" point such that we can represent it using a single
/// integer for rounding purposes. It is only used in a context *relative* to
/// another civil datetime interpreted in UTC. In this fashion, the selection
/// of UTC specifically doesn't really matter. We could use any time zone.
/// (Although, it must be a time zone without any transitions, otherwise we
/// could wind up with time zone aware results in a context where that would
/// be unexpected since this is civil time.)
#[derive(Clone, Copy, Debug)]
struct RelativeCivil {
datetime: DateTime,
timestamp: Timestamp,
}
impl RelativeCivil {
/// Creates a new relative wrapper around the given civil datetime.
///
/// This wrapper bundles a timestamp for the given datetime by interpreting
/// it as being in UTC. This is an "odd" thing to do, but it's only used
/// in the context of determining the length of time between two civil
/// datetimes. So technically, any time zone without transitions could be
/// used.
///
/// # Errors
///
/// This returns an error if the datetime could not be converted to a
/// timestamp. This only occurs near the minimum and maximum civil datetime
/// values.
fn new(datetime: DateTime) -> Result<RelativeCivil, Error> {
let timestamp = datetime
.to_zoned(TimeZone::UTC)
.with_context(|| {
err!("failed to convert {datetime} to timestamp")
})?
.timestamp();
Ok(RelativeCivil { datetime, timestamp })
}
/// Returns the result of [`DateTime::checked_add`].
///
/// # Errors
///
/// Returns an error in the same cases as `DateTime::checked_add`. That is,
/// when adding the span to this zoned datetime would overflow.
///
/// This also returns an error if the resulting datetime could not be
/// converted to a timestamp in UTC. This only occurs near the minimum and
/// maximum datetime values.
fn checked_add(&self, span: Span) -> Result<RelativeCivil, Error> {
let datetime = self.datetime.checked_add(span).with_context(|| {
err!("failed to add {span} to {dt}", dt = self.datetime)
})?;
let timestamp = datetime
.to_zoned(TimeZone::UTC)
.with_context(|| {
err!("failed to convert {datetime} to timestamp")
})?
.timestamp();
Ok(RelativeCivil { datetime, timestamp })
}
/// Returns the result of [`DateTime::checked_add`] with an absolute
/// duration.
///
/// # Errors
///
/// Returns an error in the same cases as `DateTime::checked_add`. That is,
/// when adding the span to this zoned datetime would overflow.
///
/// This also returns an error if the resulting datetime could not be
/// converted to a timestamp in UTC. This only occurs near the minimum and
/// maximum datetime values.
fn checked_add_duration(
&self,
duration: SignedDuration,
) -> Result<RelativeCivil, Error> {
let datetime =
self.datetime.checked_add(duration).with_context(|| {
err!("failed to add {duration:?} to {dt}", dt = self.datetime)
})?;
let timestamp = datetime
.to_zoned(TimeZone::UTC)
.with_context(|| {
err!("failed to convert {datetime} to timestamp")
})?
.timestamp();
Ok(RelativeCivil { datetime, timestamp })
}
/// Returns the result of [`DateTime::until`].
///
/// # Errors
///
/// Returns an error in the same cases as `DateTime::until`. That is, when
/// the span for the given largest unit cannot be represented. This can
/// generally only happen when `largest` is `Unit::Nanosecond` and the span
/// cannot be represented as a 64-bit integer of nanoseconds.
fn until(
&self,
largest: Unit,
other: &RelativeCivil,
) -> Result<Span, Error> {
self.datetime.until((largest, other.datetime)).with_context(|| {
err!(
"failed to get span between {dt1} and {dt2} \
with largest unit as {unit}",
unit = largest.plural(),
dt1 = self.datetime,
dt2 = other.datetime,
)
})
}
}
/// A simple wrapper around a possibly borrowed `Zoned`.
#[derive(Clone, Debug)]
struct RelativeZoned<'a> {
zoned: DumbCow<'a, Zoned>,
}
impl<'a> RelativeZoned<'a> {
/// Returns the result of [`Zoned::checked_add`].
///
/// # Errors
///
/// Returns an error in the same cases as `Zoned::checked_add`. That is,
/// when adding the span to this zoned datetime would overflow.
fn checked_add(
&self,
span: Span,
) -> Result<RelativeZoned<'static>, Error> {
let zoned = self.zoned.checked_add(span).with_context(|| {
err!("failed to add {span} to {zoned}", zoned = self.zoned)
})?;
Ok(RelativeZoned { zoned: DumbCow::Owned(zoned) })
}
/// Returns the result of [`Zoned::checked_add`] with an absolute duration.
///
/// # Errors
///
/// Returns an error in the same cases as `Zoned::checked_add`. That is,
/// when adding the span to this zoned datetime would overflow.
fn checked_add_duration(
&self,
duration: SignedDuration,
) -> Result<RelativeZoned<'static>, Error> {
let zoned = self.zoned.checked_add(duration).with_context(|| {
err!("failed to add {duration:?} to {zoned}", zoned = self.zoned)
})?;
Ok(RelativeZoned { zoned: DumbCow::Owned(zoned) })
}
/// Returns the result of [`Zoned::until`].
///
/// # Errors
///
/// Returns an error in the same cases as `Zoned::until`. That is, when
/// the span for the given largest unit cannot be represented. This can
/// generally only happen when `largest` is `Unit::Nanosecond` and the span
/// cannot be represented as a 64-bit integer of nanoseconds.
fn until(
&self,
largest: Unit,
other: &RelativeZoned<'a>,
) -> Result<Span, Error> {
self.zoned.until((largest, &*other.zoned)).with_context(|| {
err!(
"failed to get span between {zdt1} and {zdt2} \
with largest unit as {unit}",
unit = largest.plural(),
zdt1 = self.zoned,
zdt2 = other.zoned,
)
})
}
}
// The code below is the "core" rounding logic for spans. It was greatly
// inspired by this gist[1] and the fullcalendar Temporal polyfill[2]. In
// particular, the algorithm implemented below is a major simplification from
// how Temporal used to work[3]. Parts of it are still in rough and unclear
// shape IMO.
//
// [1]: https://gist.github.com/arshaw/36d3152c21482bcb78ea2c69591b20e0
// [2]: https://github.com/fullcalendar/temporal-polyfill
// [3]: https://github.com/tc39/proposal-temporal/issues/2792
/// The result of a span rounding strategy. There are three:
///
/// * Rounding spans relative to civil datetimes using only invariant
/// units (days or less). This is achieved by converting the span to a simple
/// integer number of nanoseconds and then rounding that.
/// * Rounding spans relative to either a civil datetime or a zoned datetime
/// where rounding might involve changing non-uniform units. That is, when
/// the smallest unit is greater than days for civil datetimes and greater
/// than hours for zoned datetimes.
/// * Rounding spans relative to a zoned datetime whose smallest unit is
/// less than days.
///
/// Each of these might produce a bottom heavy span that needs to be
/// re-balanced. This type represents that result via one of three constructors
/// corresponding to each of the above strategies, and then provides a routine
/// for rebalancing via "bubbling."
#[derive(Debug)]
struct Nudge {
/// A possibly bottom heavy rounded span.
span: Span,
/// The nanosecond timestamp corresponding to `relative + span`, where
/// `span` is the (possibly bottom heavy) rounded span.
rounded_relative_end: NoUnits128,
/// Whether rounding may have created a bottom heavy span such that a
/// calendar unit might need to be incremented after re-balancing smaller
/// units.
grew_big_unit: bool,
}
impl Nudge {
/// Performs rounding on the given span limited to invariant units.
///
/// For civil datetimes, this means the smallest unit must be days or less,
/// but the largest unit can be bigger. For zoned datetimes, this means
/// that *both* the largest and smallest unit must be hours or less. This
/// is because zoned datetimes with rounding that can spill up to days
/// requires special handling.
///
/// It works by converting the span to a single integer number of
/// nanoseconds, rounding it and then converting back to a span.
fn relative_invariant(
balanced: Span,
relative_end: NoUnits128,
smallest: Unit,
largest: Unit,
increment: NoUnits128,
mode: RoundMode,
) -> Result<Nudge, Error> {
// Ensures this is only called when rounding invariant units.
assert!(smallest <= Unit::Week);
let sign = balanced.get_sign_ranged();
let balanced_nanos = balanced.to_invariant_nanoseconds();
let rounded_nanos = mode.round_by_unit_in_nanoseconds(
balanced_nanos,
smallest,
increment,
);
let span = Span::from_invariant_nanoseconds(largest, rounded_nanos)
.with_context(|| {
err!(
"failed to convert rounded nanoseconds {rounded_nanos} \
to span for largest unit as {unit}",
unit = largest.plural(),
)
})?
.years_ranged(balanced.get_years_ranged())
.months_ranged(balanced.get_months_ranged())
.weeks_ranged(balanced.get_weeks_ranged());
let diff_nanos = rounded_nanos - balanced_nanos;
let diff_days = rounded_nanos.div_ceil(t::NANOS_PER_CIVIL_DAY)
- balanced_nanos.div_ceil(t::NANOS_PER_CIVIL_DAY);
let grew_big_unit = diff_days.signum() == sign;
let rounded_relative_end = relative_end + diff_nanos;
Ok(Nudge { span, rounded_relative_end, grew_big_unit })
}
/// Performs rounding on the given span where the smallest unit configured
/// implies that rounding will cover calendar or "non-uniform" units. (That
/// is, units whose length can change based on the relative datetime.)
fn relative_calendar(
balanced: Span,
relative_start: &Relative<'_>,
relative_end: &Relative<'_>,
smallest: Unit,
increment: NoUnits128,
mode: RoundMode,
) -> Result<Nudge, Error> {
#[cfg(not(feature = "std"))]
use crate::util::libm::Float;
assert!(smallest >= Unit::Day);
let sign = balanced.get_sign_ranged();
let truncated = increment
* balanced.get_units_ranged(smallest).div_ceil(increment);
let span = balanced
.without_lower(smallest)
.try_units_ranged(smallest, truncated)
.with_context(|| {
err!(
"failed to set {unit} to {truncated} on span {balanced}",
unit = smallest.singular()
)
})?;
let (relative0, relative1) = clamp_relative_span(
relative_start,
span,
smallest,
NoUnits::try_rfrom("increment", increment)?
.try_checked_mul("signed increment", sign)?,
)?;
// FIXME: This is brutal. This is the only non-optional floating point
// used so far in Jiff. We do expose floating point for things like
// `Span::total`, but that's optional and not a core part of Jiff's
// functionality. This is in the core part of Jiff's span rounding...
let denom = (relative1 - relative0).get() as f64;
let numer = (relative_end.to_nanosecond() - relative0).get() as f64;
let exact = (truncated.get() as f64)
+ (numer / denom) * (sign.get() as f64) * (increment.get() as f64);
let rounded = mode.round_float(exact, increment);
let grew_big_unit =
((rounded.get() as f64) - exact).signum() == (sign.get() as f64);
let span =
span.try_units_ranged(smallest, rounded).with_context(|| {
err!(
"failed to set {unit} to {truncated} on span {span}",
unit = smallest.singular()
)
})?;
let rounded_relative_end =
if grew_big_unit { relative1 } else { relative0 };
Ok(Nudge { span, rounded_relative_end, grew_big_unit })
}
/// Performs rounding on the given span where the smallest unit is hours
/// or less *and* the relative datetime is time zone aware.
fn relative_zoned_time(
balanced: Span,
relative_start: &RelativeZoned<'_>,
smallest: Unit,
increment: NoUnits128,
mode: RoundMode,
) -> Result<Nudge, Error> {
let sign = balanced.get_sign_ranged();
let time_nanos =
balanced.only_lower(Unit::Day).to_invariant_nanoseconds();
let mut rounded_time_nanos =
mode.round_by_unit_in_nanoseconds(time_nanos, smallest, increment);
let (relative0, relative1) = clamp_relative_span(
// FIXME: Find a way to drop this clone.
&Relative::Zoned(relative_start.clone()),
balanced.without_lower(Unit::Day),
Unit::Day,
sign.rinto(),
)?;
let day_nanos = relative1 - relative0;
let beyond_day_nanos = rounded_time_nanos - day_nanos;
let mut day_delta = NoUnits::N::<0>();
let rounded_relative_end =
if beyond_day_nanos == 0 || beyond_day_nanos.signum() == sign {
day_delta += C(1);
rounded_time_nanos = mode.round_by_unit_in_nanoseconds(
beyond_day_nanos,
smallest,
increment,
);
relative1 + rounded_time_nanos
} else {
relative0 + rounded_time_nanos
};
let span =
Span::from_invariant_nanoseconds(Unit::Hour, rounded_time_nanos)
.with_context(|| {
err!(
"failed to convert rounded nanoseconds \
{rounded_time_nanos} to span for largest unit as {unit}",
unit = Unit::Hour.plural(),
)
})?
.years_ranged(balanced.get_years_ranged())
.months_ranged(balanced.get_months_ranged())
.weeks_ranged(balanced.get_weeks_ranged())
.days_ranged(balanced.get_days_ranged() + day_delta);
let grew_big_unit = day_delta != 0;
Ok(Nudge { span, rounded_relative_end, grew_big_unit })
}
/// This "bubbles" up the units in a potentially "bottom heavy" span to
/// larger units. For example, P1m50d relative to March 1 is bottom heavy.
/// This routine will bubble the days up to months to get P2m19d.
///
/// # Errors
///
/// This routine fails if any arithmetic on the individual units fails, or
/// when span arithmetic on the relative datetime given fails.
fn bubble(
&self,
relative: &RelativeSpan,
smallest: Unit,
largest: Unit,
) -> Result<Span, Error> {
if !self.grew_big_unit || smallest == Unit::Week {
return Ok(self.span);
}
let smallest = smallest.max(Unit::Day);
let mut balanced = self.span;
let sign = balanced.get_sign_ranged();
let mut unit = smallest;
while let Some(u) = unit.next() {
unit = u;
if unit > largest {
break;
}
// We only bubble smaller units up into weeks when the largest unit
// is explicitly set to weeks. Otherwise, we leave it as-is.
if unit == Unit::Week && largest != Unit::Week {
continue;
}
let span_start = balanced.without_lower(unit);
let new_units = span_start
.get_units_ranged(unit)
.try_checked_add("bubble-units", sign)
.with_context(|| {
err!(
"failed to add sign {sign} to {unit} value {value}",
unit = unit.plural(),
value = span_start.get_units_ranged(unit),
)
})?;
let span_end = span_start
.try_units_ranged(unit, new_units)
.with_context(|| {
err!(
"failed to set {unit} to value \
{new_units} on span {span_start}",
unit = unit.plural(),
)
})?;
let threshold = match relative.kind {
RelativeSpanKind::Civil { ref start, .. } => {
start.checked_add(span_end)?.timestamp
}
RelativeSpanKind::Zoned { ref start, .. } => {
start.checked_add(span_end)?.zoned.timestamp()
}
};
let beyond =
self.rounded_relative_end - threshold.as_nanosecond_ranged();
if beyond == 0 || beyond.signum() == sign {
balanced = span_end;
} else {
break;
}
}
Ok(balanced)
}
}
/// Rounds a span consisting of only invariant units.
///
/// This only applies when the max of the units in the span being rounded,
/// the largest configured unit and the smallest configured unit are all
/// invariant. That is, days or lower for spans without a relative datetime or
/// a relative civil datetime, and hours or lower for spans with a relative
/// zoned datetime.
///
/// All we do here is convert the span to an integer number of nanoseconds,
/// round that and then convert back. There aren't any tricky corner cases to
/// consider here.
fn round_span_invariant(
span: Span,
smallest: Unit,
largest: Unit,
increment: NoUnits128,
mode: RoundMode,
) -> Result<Span, Error> {
assert!(smallest <= Unit::Week);
assert!(largest <= Unit::Week);
let nanos = span.to_invariant_nanoseconds();
let rounded =
mode.round_by_unit_in_nanoseconds(nanos, smallest, increment);
Span::from_invariant_nanoseconds(largest, rounded).with_context(|| {
err!(
"failed to convert rounded nanoseconds {rounded} \
to span for largest unit as {unit}",
unit = largest.plural(),
)
})
}
/// Returns the nanosecond timestamps of `relative + span` and `relative +
/// {amount of unit} + span`.
///
/// This is useful for determining the actual length, in nanoseconds, of some
/// unit amount (usually a single unit). Usually, this is called with a span
/// whose units lower than `unit` are zeroed out and with an `amount` that
/// is `-1` or `1` or `0`. So for example, if `unit` were `Unit::Day`, then
/// you'd get back two nanosecond timestamps relative to the relative datetime
/// given that start exactly "one day" apart. (Which might be different than 24
/// hours, depending on the time zone.)
///
/// # Errors
///
/// This returns an error if adding the units overflows, or if doing the span
/// arithmetic on `relative` overflows.
fn clamp_relative_span(
relative: &Relative<'_>,
span: Span,
unit: Unit,
amount: NoUnits,
) -> Result<(NoUnits128, NoUnits128), Error> {
let amount = span
.get_units_ranged(unit)
.try_checked_add("clamp-units", amount)
.with_context(|| {
err!(
"failed to add {amount} to {unit} \
value {value} on span {span}",
unit = unit.plural(),
value = span.get_units_ranged(unit),
)
})?;
let span_amount =
span.try_units_ranged(unit, amount).with_context(|| {
err!(
"failed to set {unit} unit to {amount} on span {span}",
unit = unit.plural(),
)
})?;
let relative0 = relative.checked_add(span)?.to_nanosecond();
let relative1 = relative.checked_add(span_amount)?.to_nanosecond();
Ok((relative0, relative1))
}
/// A common parsing function that works in bytes.
///
/// Specifically, this parses either an ISO 8601 duration into a `Span` or
/// a "friendly" duration into a `Span`. It also tries to give decent error
/// messages.
///
/// This works because the friendly and ISO 8601 formats have non-overlapping
/// prefixes. Both can start with a `+` or `-`, but aside from that, an ISO
/// 8601 duration _always_ has to start with a `P` or `p`. We can utilize this
/// property to very quickly determine how to parse the input. We just need to
/// handle the possibly ambiguous case with a leading sign a little carefully
/// in order to ensure good error messages.
///
/// (We do the same thing for `SignedDuration`.)
#[inline(always)]
fn parse_iso_or_friendly(bytes: &[u8]) -> Result<Span, Error> {
if bytes.is_empty() {
return Err(err!(
"an empty string is not a valid `Span`, \
expected either a ISO 8601 or Jiff's 'friendly' \
format",
));
}
let mut first = bytes[0];
if first == b'+' || first == b'-' {
if bytes.len() == 1 {
return Err(err!(
"found nothing after sign `{sign}`, \
which is not a valid `Span`, \
expected either a ISO 8601 or Jiff's 'friendly' \
format",
sign = escape::Byte(first),
));
}
first = bytes[1];
}
if first == b'P' || first == b'p' {
temporal::DEFAULT_SPAN_PARSER.parse_span(bytes)
} else {
friendly::DEFAULT_SPAN_PARSER.parse_span(bytes)
}
}
fn requires_relative_date_err(unit: Unit) -> Result<(), Error> {
if unit.is_variable() {
return Err(if matches!(unit, Unit::Week | Unit::Day) {
err!(
"using unit '{unit}' in a span or configuration \
requires that either a relative reference time be given \
or `SpanRelativeTo::days_are_24_hours()` is used to \
indicate invariant 24-hour days, \
but neither were provided",
unit = unit.singular(),
)
} else {
err!(
"using unit '{unit}' in a span or configuration \
requires that a relative reference time be given, \
but none was provided",
unit = unit.singular(),
)
});
}
Ok(())
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use alloc::string::ToString;
use crate::{civil::date, RoundMode};
use super::*;
#[test]
fn test_total() {
if crate::tz::db().is_definitively_empty() {
return;
}
let span = 130.hours().minutes(20);
let total = span.total(Unit::Second).unwrap();
assert_eq!(total, 469200.0);
let span = 123456789.seconds();
let total = span
.total(SpanTotal::from(Unit::Day).days_are_24_hours())
.unwrap();
assert_eq!(total, 1428.8980208333332);
let span = 2756.hours();
let dt = date(2020, 1, 1).at(0, 0, 0, 0);
let zdt = dt.in_tz("Europe/Rome").unwrap();
let total = span.total((Unit::Month, &zdt)).unwrap();
assert_eq!(total, 3.7958333333333334);
let total = span.total((Unit::Month, dt)).unwrap();
assert_eq!(total, 3.7944444444444443);
}
#[test]
fn test_compare() {
if crate::tz::db().is_definitively_empty() {
return;
}
let span1 = 79.hours().minutes(10);
let span2 = 79.hours().seconds(630);
let span3 = 78.hours().minutes(50);
let mut array = [span1, span2, span3];
array.sort_by(|sp1, sp2| sp1.compare(sp2).unwrap());
assert_eq!(array, [span3, span1, span2].map(SpanFieldwise));
let day24 = SpanRelativeTo::days_are_24_hours();
let span1 = 79.hours().minutes(10);
let span2 = 3.days().hours(7).seconds(630);
let span3 = 3.days().hours(6).minutes(50);
let mut array = [span1, span2, span3];
array.sort_by(|sp1, sp2| sp1.compare((sp2, day24)).unwrap());
assert_eq!(array, [span3, span1, span2].map(SpanFieldwise));
let dt = date(2020, 11, 1).at(0, 0, 0, 0);
let zdt = dt.in_tz("America/Los_Angeles").unwrap();
array.sort_by(|sp1, sp2| sp1.compare((sp2, &zdt)).unwrap());
assert_eq!(array, [span1, span3, span2].map(SpanFieldwise));
}
#[test]
fn test_checked_add() {
let span1 = 1.hour();
let span2 = 30.minutes();
let sum = span1.checked_add(span2).unwrap();
span_eq!(sum, 1.hour().minutes(30));
let span1 = 1.hour().minutes(30);
let span2 = 2.hours().minutes(45);
let sum = span1.checked_add(span2).unwrap();
span_eq!(sum, 4.hours().minutes(15));
let span = 50
.years()
.months(50)
.days(50)
.hours(50)
.minutes(50)
.seconds(50)
.milliseconds(500)
.microseconds(500)
.nanoseconds(500);
let relative = date(1900, 1, 1).at(0, 0, 0, 0);
let sum = span.checked_add((span, relative)).unwrap();
let expected = 108
.years()
.months(7)
.days(12)
.hours(5)
.minutes(41)
.seconds(41)
.milliseconds(1)
.microseconds(1)
.nanoseconds(0);
span_eq!(sum, expected);
let span = 1.month().days(15);
let relative = date(2000, 2, 1).at(0, 0, 0, 0);
let sum = span.checked_add((span, relative)).unwrap();
span_eq!(sum, 3.months());
let relative = date(2000, 3, 1).at(0, 0, 0, 0);
let sum = span.checked_add((span, relative)).unwrap();
span_eq!(sum, 2.months().days(30));
}
#[test]
fn test_round_day_time() {
let span = 29.seconds();
let rounded = span.round(Unit::Minute).unwrap();
span_eq!(rounded, 0.minute());
let span = 30.seconds();
let rounded = span.round(Unit::Minute).unwrap();
span_eq!(rounded, 1.minute());
let span = 8.seconds();
let rounded = span
.round(
SpanRound::new()
.smallest(Unit::Nanosecond)
.largest(Unit::Microsecond),
)
.unwrap();
span_eq!(rounded, 8_000_000.microseconds());
let span = 130.minutes();
let rounded = span
.round(SpanRound::new().largest(Unit::Day).days_are_24_hours())
.unwrap();
span_eq!(rounded, 2.hours().minutes(10));
let span = 10.minutes().seconds(52);
let rounded = span.round(Unit::Minute).unwrap();
span_eq!(rounded, 11.minutes());
let span = 10.minutes().seconds(52);
let rounded = span
.round(
SpanRound::new().smallest(Unit::Minute).mode(RoundMode::Trunc),
)
.unwrap();
span_eq!(rounded, 10.minutes());
let span = 2.hours().minutes(34).seconds(18);
let rounded =
span.round(SpanRound::new().largest(Unit::Second)).unwrap();
span_eq!(rounded, 9258.seconds());
let span = 6.minutes();
let rounded = span
.round(
SpanRound::new()
.smallest(Unit::Minute)
.increment(5)
.mode(RoundMode::Ceil),
)
.unwrap();
span_eq!(rounded, 10.minutes());
}
#[test]
fn test_round_relative_zoned_calendar() {
if crate::tz::db().is_definitively_empty() {
return;
}
let span = 2756.hours();
let relative =
date(2020, 1, 1).at(0, 0, 0, 0).in_tz("America/New_York").unwrap();
let options = SpanRound::new()
.largest(Unit::Year)
.smallest(Unit::Day)
.relative(&relative);
let rounded = span.round(options).unwrap();
span_eq!(rounded, 3.months().days(24));
let span = 24.hours().nanoseconds(5);
let relative = date(2000, 10, 29)
.at(0, 0, 0, 0)
.in_tz("America/Vancouver")
.unwrap();
let options = SpanRound::new()
.largest(Unit::Day)
.smallest(Unit::Minute)
.relative(&relative)
.mode(RoundMode::Expand)
.increment(30);
let rounded = span.round(options).unwrap();
// It seems like this is the correct answer, although it apparently
// differs from Temporal and the FullCalendar polyfill. I'm not sure
// what accounts for the difference in the implementation.
//
// See: https://github.com/tc39/proposal-temporal/pull/2758#discussion_r1597255245
span_eq!(rounded, 24.hours().minutes(30));
// Ref: https://github.com/tc39/proposal-temporal/issues/2816#issuecomment-2115608460
let span = -1.month().hours(24);
let relative: crate::Zoned = date(2024, 4, 11)
.at(2, 0, 0, 0)
.in_tz("America/New_York")
.unwrap();
let options =
SpanRound::new().smallest(Unit::Millisecond).relative(&relative);
let rounded = span.round(options).unwrap();
span_eq!(rounded, -1.month().days(1).hours(1));
let dt = relative.checked_add(span).unwrap();
let diff = relative.until((Unit::Month, &dt)).unwrap();
span_eq!(diff, -1.month().days(1).hours(1));
// Like the above, but don't use a datetime near a DST transition. In
// this case, a day is a normal 24 hours. (Unlike above, where the
// duration includes a 23 hour day, and so an additional hour has to be
// added to the span to account for that.)
let span = -1.month().hours(24);
let relative = date(2024, 6, 11)
.at(2, 0, 0, 0)
.in_tz("America/New_York")
.unwrap();
let options =
SpanRound::new().smallest(Unit::Millisecond).relative(&relative);
let rounded = span.round(options).unwrap();
span_eq!(rounded, -1.month().days(1));
}
#[test]
fn test_round_relative_zoned_time() {
if crate::tz::db().is_definitively_empty() {
return;
}
let span = 2756.hours();
let relative =
date(2020, 1, 1).at(0, 0, 0, 0).in_tz("America/New_York").unwrap();
let options = SpanRound::new().largest(Unit::Year).relative(&relative);
let rounded = span.round(options).unwrap();
span_eq!(rounded, 3.months().days(23).hours(21));
let span = 2756.hours();
let relative =
date(2020, 9, 1).at(0, 0, 0, 0).in_tz("America/New_York").unwrap();
let options = SpanRound::new().largest(Unit::Year).relative(&relative);
let rounded = span.round(options).unwrap();
span_eq!(rounded, 3.months().days(23).hours(19));
let span = 3.hours();
let relative =
date(2020, 3, 8).at(0, 0, 0, 0).in_tz("America/New_York").unwrap();
let options = SpanRound::new().largest(Unit::Year).relative(&relative);
let rounded = span.round(options).unwrap();
span_eq!(rounded, 3.hours());
}
#[test]
fn test_round_relative_day_time() {
let span = 2756.hours();
let options =
SpanRound::new().largest(Unit::Year).relative(date(2020, 1, 1));
let rounded = span.round(options).unwrap();
span_eq!(rounded, 3.months().days(23).hours(20));
let span = 2756.hours();
let options =
SpanRound::new().largest(Unit::Year).relative(date(2020, 9, 1));
let rounded = span.round(options).unwrap();
span_eq!(rounded, 3.months().days(23).hours(20));
let span = 190.days();
let options =
SpanRound::new().largest(Unit::Year).relative(date(2020, 1, 1));
let rounded = span.round(options).unwrap();
span_eq!(rounded, 6.months().days(8));
let span = 30
.days()
.hours(23)
.minutes(59)
.seconds(59)
.milliseconds(999)
.microseconds(999)
.nanoseconds(999);
let options = SpanRound::new()
.smallest(Unit::Microsecond)
.largest(Unit::Year)
.relative(date(2024, 5, 1));
let rounded = span.round(options).unwrap();
span_eq!(rounded, 1.month());
let span = 364
.days()
.hours(23)
.minutes(59)
.seconds(59)
.milliseconds(999)
.microseconds(999)
.nanoseconds(999);
let options = SpanRound::new()
.smallest(Unit::Microsecond)
.largest(Unit::Year)
.relative(date(2023, 1, 1));
let rounded = span.round(options).unwrap();
span_eq!(rounded, 1.year());
let span = 365
.days()
.hours(23)
.minutes(59)
.seconds(59)
.milliseconds(999)
.microseconds(999)
.nanoseconds(999);
let options = SpanRound::new()
.smallest(Unit::Microsecond)
.largest(Unit::Year)
.relative(date(2023, 1, 1));
let rounded = span.round(options).unwrap();
span_eq!(rounded, 1.year().days(1));
let span = 365
.days()
.hours(23)
.minutes(59)
.seconds(59)
.milliseconds(999)
.microseconds(999)
.nanoseconds(999);
let options = SpanRound::new()
.smallest(Unit::Microsecond)
.largest(Unit::Year)
.relative(date(2024, 1, 1));
let rounded = span.round(options).unwrap();
span_eq!(rounded, 1.year());
let span = 3.hours();
let options =
SpanRound::new().largest(Unit::Year).relative(date(2020, 3, 8));
let rounded = span.round(options).unwrap();
span_eq!(rounded, 3.hours());
}
#[test]
fn span_sign() {
assert_eq!(Span::new().get_sign_ranged(), 0);
assert_eq!(Span::new().days(1).get_sign_ranged(), 1);
assert_eq!(Span::new().days(-1).get_sign_ranged(), -1);
assert_eq!(Span::new().days(1).days(0).get_sign_ranged(), 0);
assert_eq!(Span::new().days(-1).days(0).get_sign_ranged(), 0);
assert_eq!(Span::new().years(1).days(1).days(0).get_sign_ranged(), 1);
assert_eq!(
Span::new().years(-1).days(-1).days(0).get_sign_ranged(),
-1
);
}
#[test]
fn span_size() {
#[cfg(target_pointer_width = "64")]
{
#[cfg(debug_assertions)]
{
assert_eq!(core::mem::align_of::<Span>(), 8);
assert_eq!(core::mem::size_of::<Span>(), 184);
}
#[cfg(not(debug_assertions))]
{
assert_eq!(core::mem::align_of::<Span>(), 8);
assert_eq!(core::mem::size_of::<Span>(), 64);
}
}
}
quickcheck::quickcheck! {
fn prop_roundtrip_span_nanoseconds(span: Span) -> quickcheck::TestResult {
let largest = span.largest_unit();
if largest > Unit::Day {
return quickcheck::TestResult::discard();
}
let nanos = span.to_invariant_nanoseconds();
let got = Span::from_invariant_nanoseconds(largest, nanos).unwrap();
quickcheck::TestResult::from_bool(nanos == got.to_invariant_nanoseconds())
}
}
/// # `serde` deserializer compatibility test
///
/// Serde YAML used to be unable to deserialize `jiff` types,
/// as deserializing from bytes is not supported by the deserializer.
///
/// - <https://github.com/BurntSushi/jiff/issues/138>
/// - <https://github.com/BurntSushi/jiff/discussions/148>
#[test]
fn span_deserialize_yaml() {
let expected = Span::new()
.years(1)
.months(2)
.weeks(3)
.days(4)
.hours(5)
.minutes(6)
.seconds(7);
let deserialized: Span =
serde_yaml::from_str("P1y2m3w4dT5h6m7s").unwrap();
span_eq!(deserialized, expected);
let deserialized: Span =
serde_yaml::from_slice("P1y2m3w4dT5h6m7s".as_bytes()).unwrap();
span_eq!(deserialized, expected);
let cursor = Cursor::new(b"P1y2m3w4dT5h6m7s");
let deserialized: Span = serde_yaml::from_reader(cursor).unwrap();
span_eq!(deserialized, expected);
}
#[test]
fn display() {
let span = Span::new()
.years(1)
.months(2)
.weeks(3)
.days(4)
.hours(5)
.minutes(6)
.seconds(7)
.milliseconds(8)
.microseconds(9)
.nanoseconds(10);
insta::assert_snapshot!(
span,
@"P1Y2M3W4DT5H6M7.00800901S",
);
insta::assert_snapshot!(
alloc::format!("{span:#}"),
@"1y 2mo 3w 4d 5h 6m 7s 8ms 9µs 10ns",
);
}
/// This test ensures that we can parse `humantime` formatted durations.
#[test]
fn humantime_compatibility_parse() {
let dur = std::time::Duration::new(60 * 60 * 24 * 411, 123_456_789);
let formatted = humantime::format_duration(dur).to_string();
assert_eq!(
formatted,
"1year 1month 15days 7h 26m 24s 123ms 456us 789ns"
);
let expected = 1
.year()
.months(1)
.days(15)
.hours(7)
.minutes(26)
.seconds(24)
.milliseconds(123)
.microseconds(456)
.nanoseconds(789);
span_eq!(formatted.parse::<Span>().unwrap(), expected);
}
/// This test ensures that we can print a `Span` that `humantime` can
/// parse.
///
/// Note that this isn't the default since `humantime`'s parser is
/// pretty limited. e.g., It doesn't support things like `nsecs`
/// despite supporting `secs`. And other reasons. See the docs on
/// `Designator::HumanTime` for why we sadly provide a custom variant for
/// it.
#[test]
fn humantime_compatibility_print() {
static PRINTER: friendly::SpanPrinter = friendly::SpanPrinter::new()
.designator(friendly::Designator::HumanTime);
let span = 1
.year()
.months(1)
.days(15)
.hours(7)
.minutes(26)
.seconds(24)
.milliseconds(123)
.microseconds(456)
.nanoseconds(789);
let formatted = PRINTER.span_to_string(&span);
assert_eq!(formatted, "1y 1month 15d 7h 26m 24s 123ms 456us 789ns");
let dur = humantime::parse_duration(&formatted).unwrap();
let expected =
std::time::Duration::new(60 * 60 * 24 * 411, 123_456_789);
assert_eq!(dur, expected);
}
#[test]
fn from_str() {
let p = |s: &str| -> Result<Span, Error> { s.parse() };
insta::assert_snapshot!(
p("1 day").unwrap(),
@"P1D",
);
insta::assert_snapshot!(
p("+1 day").unwrap(),
@"P1D",
);
insta::assert_snapshot!(
p("-1 day").unwrap(),
@"-P1D",
);
insta::assert_snapshot!(
p("P1d").unwrap(),
@"P1D",
);
insta::assert_snapshot!(
p("+P1d").unwrap(),
@"P1D",
);
insta::assert_snapshot!(
p("-P1d").unwrap(),
@"-P1D",
);
insta::assert_snapshot!(
p("").unwrap_err(),
@"an empty string is not a valid `Span`, expected either a ISO 8601 or Jiff's 'friendly' format",
);
insta::assert_snapshot!(
p("+").unwrap_err(),
@"found nothing after sign `+`, which is not a valid `Span`, expected either a ISO 8601 or Jiff's 'friendly' format",
);
insta::assert_snapshot!(
p("-").unwrap_err(),
@"found nothing after sign `-`, which is not a valid `Span`, expected either a ISO 8601 or Jiff's 'friendly' format",
);
}
#[test]
fn serde_deserialize() {
let p = |s: &str| -> Result<Span, serde_json::Error> {
serde_json::from_str(&alloc::format!("\"{s}\""))
};
insta::assert_snapshot!(
p("1 day").unwrap(),
@"P1D",
);
insta::assert_snapshot!(
p("+1 day").unwrap(),
@"P1D",
);
insta::assert_snapshot!(
p("-1 day").unwrap(),
@"-P1D",
);
insta::assert_snapshot!(
p("P1d").unwrap(),
@"P1D",
);
insta::assert_snapshot!(
p("+P1d").unwrap(),
@"P1D",
);
insta::assert_snapshot!(
p("-P1d").unwrap(),
@"-P1D",
);
insta::assert_snapshot!(
p("").unwrap_err(),
@"an empty string is not a valid `Span`, expected either a ISO 8601 or Jiff's 'friendly' format at line 1 column 2",
);
insta::assert_snapshot!(
p("+").unwrap_err(),
@"found nothing after sign `+`, which is not a valid `Span`, expected either a ISO 8601 or Jiff's 'friendly' format at line 1 column 3",
);
insta::assert_snapshot!(
p("-").unwrap_err(),
@"found nothing after sign `-`, which is not a valid `Span`, expected either a ISO 8601 or Jiff's 'friendly' format at line 1 column 3",
);
}
}