///|
/// Nanoseconds per second.
const NANOS_PER_SEC : Int64 = 1_000_000_000L
///|
/// Bound on a duration's magnitude in seconds, chosen so `num_milliseconds`
/// always fits in `Int64` (`Int64::max_value / 1000`, with headroom). The
/// representable range is exactly `-MAX_SECS..=MAX_SECS` seconds, so a
/// duration is valid when its absolute value does not exceed `MAX_SECS`
/// seconds; that symmetry is what makes `neg` and `abs` total.
const MAX_SECS : Int64 = 9_223_372_036_854_774L
///|
const MIN_SECS : Int64 = -9_223_372_036_854_774L
///|
/// A signed duration, precise to the nanosecond.
///
/// Internally represented as whole seconds plus a non-negative nanosecond
/// remainder (`0..=999_999_999`); the sign of the duration is carried
/// entirely by the seconds component.
pub struct TimeDelta {
priv seconds : Int64
priv nanoseconds : Int
} derive(Eq, Compare, Hash, @debug.Debug)
///|
pub extend TimeDelta with Eq::{equal}
///|
pub extend TimeDelta with Eq::{not_equal}
///|
pub extend TimeDelta with Compare::{compare, op_lt, op_gt, op_le, op_ge}
///|
pub extend TimeDelta with Hash::{hash, hash_combine}
///|
/// The zero-length duration; see `zero`.
pub impl Default for TimeDelta with fn default() {
TimeDelta::zero()
}
///|
pub extend TimeDelta with Default::{default}
///|
pub extend TimeDelta with @debug.Debug::{to_repr}
///|
/// Renders the duration compactly: an optional leading
/// `-`, then hours, minutes and seconds (`1h2m3.5s`) with hours as the
/// largest unit, no fractional part's trailing zeros, and every unit between
/// the largest and the seconds kept even when zero (`1h0m0s`). A duration
/// under one second uses `ns`, `us` or `ms` instead (`1.5ms`), and zero is
/// `0s`.
pub impl Show for TimeDelta with fn output(self, logger) {
let magnitude = self.abs()
if self.seconds < 0L {
logger.write_char('-')
}
if magnitude.seconds == 0L {
write_subsecond(magnitude.nanoseconds, logger)
} else {
let secs = magnitude.seconds
let hours = secs / 3600L
let minutes = secs % 3600L / 60L
if hours > 0L {
logger.write_string(hours.to_string() + "h")
}
if hours > 0L || minutes > 0L {
logger.write_string(minutes.to_string() + "m")
}
logger.write_string((secs % 60L).to_string())
write_fraction(magnitude.nanoseconds, 9, logger)
logger.write_char('s')
}
}
///|
pub extend TimeDelta with Show::{to_string, output}
///|
fn write_subsecond(nanos : Int, logger : &Logger) -> Unit {
if nanos == 0 {
logger.write_string("0s")
} else if nanos < 1_000 {
logger.write_string(nanos.to_string() + "ns")
} else if nanos < 1_000_000 {
logger.write_string((nanos / 1_000).to_string())
write_fraction(nanos % 1_000, 3, logger)
logger.write_string("us")
} else {
logger.write_string((nanos / 1_000_000).to_string())
write_fraction(nanos % 1_000_000, 6, logger)
logger.write_string("ms")
}
}
///|
fn write_fraction(value : Int, width : Int, logger : &Logger) -> Unit {
if value != 0 {
let mut digits = value.to_string()
while digits.length() < width {
digits = "0" + digits
}
let mut end = digits.length()
while digits[end - 1] == '0' {
end -= 1
}
logger.write_char('.')
logger.write_string(digits[:end].to_owned())
}
}
///|
/// Builds a `TimeDelta` from a whole-seconds component and a nanosecond
/// component of arbitrary sign and magnitude, normalizing the nanoseconds
/// into the `0..=999_999_999` range and carrying the excess into `seconds`.
/// Returns `None` if the result's magnitude exceeds `MAX_SECS` seconds (a
/// `MAX_SECS` boundary value must have no nanosecond remainder).
fn from_secs_nanos(secs : Int64, nanos : Int64) -> TimeDelta? {
let extra_secs = floor_div64(nanos, NANOS_PER_SEC)
let rem_nanos = floor_mod64(nanos, NANOS_PER_SEC)
match checked_add64(secs, extra_secs) {
None => None
Some(total_secs) =>
if total_secs < MIN_SECS ||
total_secs > MAX_SECS ||
(total_secs == MAX_SECS && rem_nanos != 0L) {
None
} else {
Some(TimeDelta::{
seconds: total_secs,
nanoseconds: rem_nanos.to_int(),
})
}
}
}
///|
/// The duration of `seconds` whole seconds plus `nanoseconds`, which must be
/// in `0..=999_999_999`: the sign of the duration is carried entirely by
/// `seconds`, so `new(-1L, 500_000_000)` is minus half a second. Returns
/// `None` if `nanoseconds` is outside that range or the result does not
/// fit in the representable range.
pub fn TimeDelta::new(seconds : Int64, nanoseconds : Int) -> TimeDelta? {
if nanoseconds < 0 || nanoseconds >= 1_000_000_000 {
None
} else {
from_secs_nanos(seconds, nanoseconds.to_int64())
}
}
///|
/// The duration of the given number of whole weeks, or `None` if it does
/// not fit in the representable range.
pub fn TimeDelta::weeks(n : Int64) -> TimeDelta? {
match checked_mul64(n, 604800L) {
None => None
Some(secs) => from_secs_nanos(secs, 0L)
}
}
///|
/// The duration of the given number of whole days, or `None` if it does
/// not fit in the representable range.
pub fn TimeDelta::days(n : Int64) -> TimeDelta? {
match checked_mul64(n, 86400L) {
None => None
Some(secs) => from_secs_nanos(secs, 0L)
}
}
///|
/// The duration of the given number of whole hours, or `None` if it does
/// not fit in the representable range.
pub fn TimeDelta::hours(n : Int64) -> TimeDelta? {
match checked_mul64(n, 3600L) {
None => None
Some(secs) => from_secs_nanos(secs, 0L)
}
}
///|
/// The duration of the given number of whole minutes, or `None` if it does
/// not fit in the representable range.
pub fn TimeDelta::minutes(n : Int64) -> TimeDelta? {
match checked_mul64(n, 60L) {
None => None
Some(secs) => from_secs_nanos(secs, 0L)
}
}
///|
/// The duration of the given number of whole seconds, or `None` if it does
/// not fit in the representable range.
pub fn TimeDelta::seconds(n : Int64) -> TimeDelta? {
from_secs_nanos(n, 0L)
}
///|
/// The duration of the given number of whole milliseconds, or `None` if it
/// does not fit in the representable range.
pub fn TimeDelta::milliseconds(n : Int64) -> TimeDelta? {
from_secs_nanos(floor_div64(n, 1_000L), floor_mod64(n, 1_000L) * 1_000_000L)
}
///|
/// The duration of the given number of whole microseconds, or `None` if it
/// does not fit in the representable range.
pub fn TimeDelta::microseconds(n : Int64) -> TimeDelta? {
from_secs_nanos(
floor_div64(n, 1_000_000L),
floor_mod64(n, 1_000_000L) * 1_000L,
)
}
///|
/// The duration of the given number of whole nanoseconds, or `None` if it
/// does not fit in the representable range.
pub fn TimeDelta::nanoseconds(n : Int64) -> TimeDelta? {
from_secs_nanos(0L, n)
}
///|
/// The duration of zero length; `Default::default()` returns it.
pub fn TimeDelta::zero() -> TimeDelta {
TimeDelta::{ seconds: 0L, nanoseconds: 0, }
}
///|
/// The most negative representable duration, exactly `-9_223_372_036_854_774`
/// seconds: the negation of `max_value`. The range is part of the type's
/// contract and will not change.
pub fn TimeDelta::min_value() -> TimeDelta {
TimeDelta::{ seconds: MIN_SECS, nanoseconds: 0, }
}
///|
/// The most positive representable duration, exactly `9_223_372_036_854_774`
/// seconds with no nanosecond remainder: the negation of `min_value`. The
/// range is part of the type's contract and will not change.
pub fn TimeDelta::max_value() -> TimeDelta {
TimeDelta::{ seconds: MAX_SECS, nanoseconds: 0, }
}
///|
/// The number of whole seconds in this duration, truncated toward zero
/// (e.g. a duration of `-1.7s` reports `-1`, not `-2`).
pub fn TimeDelta::num_seconds(self : TimeDelta) -> Int64 {
if self.seconds < 0L && self.nanoseconds > 0 {
self.seconds + 1L
} else {
self.seconds
}
}
///|
/// The nanosecond remainder such that
/// `self.num_seconds() * 1_000_000_000 + self.subsec_nanoseconds()` equals
/// this duration's total nanosecond count. Carries the same sign as the
/// overall duration, so it may be negative even though the internal
/// representation's nanosecond component is always non-negative.
pub fn TimeDelta::subsec_nanoseconds(self : TimeDelta) -> Int {
if self.seconds < 0L && self.nanoseconds > 0 {
self.nanoseconds - 1_000_000_000
} else {
self.nanoseconds
}
}
///|
/// The `subsec_nanoseconds` remainder expressed in whole milliseconds,
/// truncated toward zero.
pub fn TimeDelta::subsec_milliseconds(self : TimeDelta) -> Int {
self.subsec_nanoseconds() / 1_000_000
}
///|
/// The `subsec_nanoseconds` remainder expressed in whole microseconds,
/// truncated toward zero.
pub fn TimeDelta::subsec_microseconds(self : TimeDelta) -> Int {
self.subsec_nanoseconds() / 1_000
}
///|
/// This duration's total length in fractional seconds, as a 64-bit float.
/// Loses precision for a very large duration; never fails.
pub fn TimeDelta::as_seconds_double(self : TimeDelta) -> Double {
self.seconds.to_double() + self.nanoseconds.to_double() / 1_000_000_000.0
}
///|
/// The duration of `seconds` fractional seconds, rounded to the nearest
/// nanosecond with an exact half-nanosecond tie breaking away from zero (so
/// a decimal such as `0.3` becomes exactly 300 000 000 ns despite its
/// binary representation error). `None` for NaN, an infinity, or a value
/// outside the representable range. A `Double` carries about 15 significant
/// digits, so a very large duration cannot keep nanosecond precision; the
/// nanosecond is exact only while the whole-second part is below about
/// 9 million seconds.
pub fn TimeDelta::from_seconds_double(seconds : Double) -> TimeDelta? {
if seconds.is_nan() || seconds.is_inf() {
return None
}
let whole = seconds.floor()
if whole > 9.0e18 || whole < -9.0e18 {
return None
}
let scaled = (seconds - whole) * 1.0e9
let rounded = scaled.round()
let nanos = if seconds < 0.0 && rounded - scaled == 0.5 {
rounded - 1.0
} else {
rounded
}
from_secs_nanos(whole.to_int64(), nanos.to_int64())
}
///|
/// This duration's total length in fractional minutes, as a 64-bit float.
/// Loses precision for a very large duration; never fails.
pub fn TimeDelta::as_minutes_double(self : TimeDelta) -> Double {
self.as_seconds_double() / 60.0
}
///|
/// This duration's total length in fractional hours, as a 64-bit float.
/// Loses precision for a very large duration; never fails.
pub fn TimeDelta::as_hours_double(self : TimeDelta) -> Double {
self.as_seconds_double() / 3600.0
}
///|
/// The number of whole weeks in this duration, truncated toward zero.
pub fn TimeDelta::num_weeks(self : TimeDelta) -> Int64 {
self.num_seconds() / 604800L
}
///|
/// The number of whole days in this duration, truncated toward zero.
pub fn TimeDelta::num_days(self : TimeDelta) -> Int64 {
self.num_seconds() / 86400L
}
///|
/// The number of whole hours in this duration, truncated toward zero.
pub fn TimeDelta::num_hours(self : TimeDelta) -> Int64 {
self.num_seconds() / 3600L
}
///|
/// The number of whole minutes in this duration, truncated toward zero.
pub fn TimeDelta::num_minutes(self : TimeDelta) -> Int64 {
self.num_seconds() / 60L
}
///|
/// The number of whole milliseconds in this duration, truncated toward
/// zero. Always succeeds: the representable range of `TimeDelta` is chosen
/// so this count never overflows `Int64` (unlike `num_microseconds` and
/// `num_nanoseconds`, which can).
pub fn TimeDelta::num_milliseconds(self : TimeDelta) -> Int64 {
self.num_seconds() * 1000L +
(self.subsec_nanoseconds() / 1_000_000).to_int64()
}
///|
/// The number of whole microseconds in this duration, truncated toward
/// zero, or `None` if it does not fit in `Int64`.
pub fn TimeDelta::num_microseconds(self : TimeDelta) -> Int64? {
match checked_mul64(self.num_seconds(), 1_000_000L) {
None => None
Some(secs_part) =>
checked_add64(secs_part, (self.subsec_nanoseconds() / 1_000).to_int64())
}
}
///|
/// The number of whole nanoseconds in this duration, or `None` if it does
/// not fit in `Int64`.
pub fn TimeDelta::num_nanoseconds(self : TimeDelta) -> Int64? {
match checked_mul64(self.num_seconds(), NANOS_PER_SEC) {
None => None
Some(secs_part) =>
checked_add64(secs_part, self.subsec_nanoseconds().to_int64())
}
}
///|
/// The total of `deltas`; an empty array totals `zero()`. The result does
/// not depend on the order of `deltas` (see `checked_sum`).
///
/// Aborts if the true total falls outside the representable range; use
/// `checked_sum` to get `None` instead.
pub fn TimeDelta::sum(deltas : Array[TimeDelta]) -> TimeDelta {
expect_in_range(TimeDelta::checked_sum(deltas), "TimeDelta::sum")
}
///|
/// The sum of two durations.
///
/// Aborts if it overflows the representable range; use `checked_add` to get
/// `None` instead.
pub fn TimeDelta::add(self : TimeDelta, other : TimeDelta) -> TimeDelta {
expect_in_range(self.checked_add(other), "TimeDelta::add")
}
///|
/// The difference of two durations.
///
/// Aborts if it overflows the representable range; use `checked_sub` to get
/// `None` instead.
pub fn TimeDelta::sub(self : TimeDelta, other : TimeDelta) -> TimeDelta {
expect_in_range(self.checked_sub(other), "TimeDelta::sub")
}
///|
/// This duration multiplied by the integer `scalar`.
///
/// Aborts if it overflows the representable range; use `checked_mul` to get
/// `None` instead.
pub fn TimeDelta::mul(self : TimeDelta, scalar : Int) -> TimeDelta {
expect_in_range(self.checked_mul(scalar), "TimeDelta::mul")
}
///|
/// This duration divided by the integer `scalar`, truncated toward zero.
///
/// Aborts if `scalar` is zero; use `checked_div` to get `None` instead.
pub fn TimeDelta::div(self : TimeDelta, scalar : Int) -> TimeDelta {
if scalar == 0 {
abort("TimeDelta::div: division by zero")
}
expect_in_range(self.checked_div(scalar), "TimeDelta::div")
}
///|
/// The total of `deltas`, or `None` if the true total falls outside the
/// representable range; an empty array totals `zero()`. Unlike folding with
/// `checked_add`, the result does not depend on the order of `deltas`: a partial sum
/// that would leave the range does not make the whole sum `None`, as long
/// as the true total is representable. (Seconds are accumulated as
/// `high * 2^31 + low`, so no partial sum can overflow `Int64` whatever the
/// order or length; `scaled + total_low` cannot overflow either, since
/// `scaled` is a multiple of `2^31` and `total_low` is below it.)
pub fn TimeDelta::checked_sum(deltas : Array[TimeDelta]) -> TimeDelta? {
let block = 2_147_483_648L
let mut high = 0L
let mut low = 0L
let mut nanos = 0L
for delta in deltas {
high += floor_div64(delta.seconds, block)
low += floor_mod64(delta.seconds, block)
nanos += delta.nanoseconds.to_int64()
}
let total_high = high + floor_div64(low, block)
let total_low = floor_mod64(low, block)
checked_mul64(total_high, block).bind(scaled => {
from_secs_nanos(scaled + total_low, nanos)
})
}
///|
/// The sum of two durations, or `None` if it overflows the representable
/// range.
///
/// The addition of the `seconds` components alone can never overflow
/// `Int64` (both operands are already bounded well within `Int64`'s range
/// by the representable-range check every `TimeDelta` satisfies), so no
/// separate overflow check is needed here beyond the one `from_secs_nanos`
/// already performs.
pub fn TimeDelta::checked_add(
self : TimeDelta,
other : TimeDelta,
) -> TimeDelta? {
from_secs_nanos(
self.seconds + other.seconds,
self.nanoseconds.to_int64() + other.nanoseconds.to_int64(),
)
}
///|
/// The difference of two durations, or `None` if it overflows the
/// representable range.
pub fn TimeDelta::checked_sub(
self : TimeDelta,
other : TimeDelta,
) -> TimeDelta? {
from_secs_nanos(
self.seconds - other.seconds,
self.nanoseconds.to_int64() - other.nanoseconds.to_int64(),
)
}
///|
/// This duration multiplied by the integer `scalar`, or `None` if it
/// overflows the representable range.
///
/// Only the `seconds` component's multiplication needs a checked
/// (`Int64`-overflowing) guard: `nanoseconds` is always below one second,
/// so `nanoseconds * scalar` stays well within `Int64` for any `Int`
/// `scalar`.
pub fn TimeDelta::checked_mul(self : TimeDelta, scalar : Int) -> TimeDelta? {
let scalar64 = scalar.to_int64()
match checked_mul64(self.seconds, scalar64) {
None => None
Some(secs) => from_secs_nanos(secs, self.nanoseconds.to_int64() * scalar64)
}
}
///|
/// This duration divided by the integer `scalar`, truncated toward zero, or
/// `None` if `scalar` is zero.
pub fn TimeDelta::checked_div(self : TimeDelta, scalar : Int) -> TimeDelta? {
if scalar == 0 {
None
} else {
let scalar64 = scalar.to_int64()
let secs_q = self.seconds / scalar64
let secs_r = self.seconds % scalar64
let nanos_total = secs_r * NANOS_PER_SEC + self.nanoseconds.to_int64()
from_secs_nanos(secs_q, nanos_total / scalar64)
}
}
///|
/// The negation of this duration. Always representable: the representable
/// range is symmetric around zero specifically so this is total.
pub fn TimeDelta::neg(self : TimeDelta) -> TimeDelta {
if self.nanoseconds == 0 {
TimeDelta::{ seconds: -self.seconds, nanoseconds: 0, }
} else {
TimeDelta::{
seconds: -self.seconds - 1L,
nanoseconds: 1_000_000_000 - self.nanoseconds,
}
}
}
///|
/// `a + b`: the sum of two durations. Aborts on overflow; see `add`.
pub impl Add for TimeDelta with fn add(self, other) {
TimeDelta::add(self, other)
}
///|
/// `a - b`: the difference of two durations. Aborts on overflow; see `sub`.
pub impl Sub for TimeDelta with fn sub(self, other) {
TimeDelta::sub(self, other)
}
///|
/// `-a`: the negation of a duration; see `neg`.
pub impl Neg for TimeDelta with fn neg(self) {
TimeDelta::neg(self)
}
///|
/// The absolute value of this duration.
pub fn TimeDelta::abs(self : TimeDelta) -> TimeDelta {
if self.seconds >= 0L {
self
} else {
self.neg()
}
}
///|
/// Whether this duration is exactly zero.
pub fn TimeDelta::is_zero(self : TimeDelta) -> Bool {
self.seconds == 0L && self.nanoseconds == 0
}
///|
/// A rounding/truncation granularity, classified into the only two shapes
/// `TimeDelta::round`/`truncate` support: a whole number of sub-second
/// nanoseconds, or a whole-second-or-larger multiple. A granularity
/// combining both (e.g. 1.5 seconds) is rejected by `classify_granularity`
/// rather than represented here: supporting it in general would require
/// arbitrary-precision arithmetic, since `TimeDelta`'s `seconds` component
/// is far too large to convert to a single nanosecond count (see
/// `num_nanoseconds`, which is already `Option`-returning for exactly this
/// reason).
priv enum Granularity {
SubSecond(Int)
WholeSeconds(Int64)
}
///|
/// Classifies `granularity` for `TimeDelta::round`/`truncate`, or reports
/// why it is rejected: `InvalidGranularity` if it is zero or negative,
/// `MixedGranularity` if it mixes a whole-second part with a sub-second
/// remainder (see `Granularity`).
fn classify_granularity(
granularity : TimeDelta,
) -> Result[Granularity, RoundingError] {
if granularity.seconds < 0L {
Err(InvalidGranularity)
} else if granularity.seconds == 0L {
if granularity.nanoseconds > 0 {
Ok(SubSecond(granularity.nanoseconds))
} else {
Err(InvalidGranularity)
}
} else if granularity.nanoseconds == 0 {
Ok(WholeSeconds(granularity.seconds))
} else {
Err(MixedGranularity)
}
}
///|
fn[T] in_range(value : T?) -> Result[T, RoundingError] {
match value {
Some(v) => Ok(v)
None => Err(OutOfRange)
}
}
///|
/// The remainder of dividing the non-negative magnitude `mag` by the
/// granularity `kind`, itself non-negative and strictly less than the
/// granularity. Computed without ever converting `mag` to a single
/// nanosecond count, which could overflow `Int64` for a `TimeDelta` far
/// from zero.
fn remainder_magnitude(mag : TimeDelta, kind : Granularity) -> TimeDelta {
match kind {
SubSecond(nanos_per_unit) => {
let unit = nanos_per_unit.to_int64()
// `mag.seconds % unit` is bounded by `unit < NANOS_PER_SEC`, so
// multiplying it back out by `NANOS_PER_SEC` stays well within
// Int64's range regardless of how large `mag.seconds` itself is.
let seconds_remainder = mag.seconds % unit
let combined = (
seconds_remainder * NANOS_PER_SEC + mag.nanoseconds.to_int64()
) %
unit
TimeDelta::{ seconds: 0L, nanoseconds: combined.to_int(), }
}
WholeSeconds(seconds_per_unit) =>
TimeDelta::{
seconds: mag.seconds % seconds_per_unit,
nanoseconds: mag.nanoseconds,
}
}
}
///|
/// The signed remainder of dividing `td` by the granularity `kind`,
/// carrying the same sign as `td` (or zero). Its magnitude is always
/// strictly less than `td`'s own, so subtracting it from `td` can never
/// push the result outside `TimeDelta`'s representable range.
fn signed_remainder(td : TimeDelta, kind : Granularity) -> TimeDelta {
let remainder = remainder_magnitude(td.abs(), kind)
if td.seconds < 0L {
remainder.neg()
} else {
remainder
}
}
///|
/// The granularity `kind` itself, expressed as a `TimeDelta` magnitude.
fn granularity_magnitude(kind : Granularity) -> TimeDelta {
match kind {
SubSecond(nanos_per_unit) =>
TimeDelta::{ seconds: 0L, nanoseconds: nanos_per_unit, }
WholeSeconds(seconds_per_unit) =>
TimeDelta::{ seconds: seconds_per_unit, nanoseconds: 0, }
}
}
///|
/// Twice a non-negative `TimeDelta` magnitude, carrying any nanosecond
/// overflow into `seconds`. Never overflows `Int64`: `seconds` is already
/// far below `Int64`'s range at `TimeDelta`'s own bounds (`MAX_SECS`), and
/// doubling it once more still is.
fn double_magnitude(mag : TimeDelta) -> TimeDelta {
let doubled_nanos = mag.nanoseconds.to_int64() * 2L
if doubled_nanos >= NANOS_PER_SEC {
TimeDelta::{
seconds: mag.seconds * 2L + 1L,
nanoseconds: (doubled_nanos - NANOS_PER_SEC).to_int(),
}
} else {
TimeDelta::{
seconds: mag.seconds * 2L,
nanoseconds: doubled_nanos.to_int(),
}
}
}
///|
/// Lexicographic comparison of two non-negative `TimeDelta` magnitudes:
/// negative if `a < b`, positive if `a > b`, zero if equal.
fn compare_magnitude(a : TimeDelta, b : TimeDelta) -> Int {
if a.seconds != b.seconds {
if a.seconds < b.seconds {
-1
} else {
1
}
} else if a.nanoseconds != b.nanoseconds {
if a.nanoseconds < b.nanoseconds {
-1
} else {
1
}
} else {
0
}
}
///|
/// This duration truncated toward zero to the nearest multiple of
/// `granularity`. Fails with `InvalidGranularity` if `granularity` is zero
/// or negative, or `MixedGranularity` if it mixes a whole-second part with
/// a sub-second remainder (e.g. 1.5 seconds) — see `Granularity`.
pub fn TimeDelta::truncate(
self : TimeDelta,
granularity : TimeDelta,
) -> Result[TimeDelta, RoundingError] {
let kind = match classify_granularity(granularity) {
Ok(v) => v
Err(e) => return Err(e)
}
in_range(self.checked_sub(signed_remainder(self, kind)))
}
///|
/// This duration rounded to the nearest multiple of `granularity`, ties
/// (an exact half-multiple) breaking away from zero. See `truncate` for the
/// conditions under which `granularity` is rejected.
pub fn TimeDelta::round(
self : TimeDelta,
granularity : TimeDelta,
) -> Result[TimeDelta, RoundingError] {
let kind = match classify_granularity(granularity) {
Ok(v) => v
Err(e) => return Err(e)
}
let remainder = signed_remainder(self, kind)
let truncated = self.checked_sub(remainder)
in_range(
if compare_magnitude(
double_magnitude(remainder.abs()),
granularity_magnitude(kind),
) <
0 {
truncated
} else if self.seconds < 0L {
truncated.bind(t => t.checked_sub(granularity_magnitude(kind)))
} else {
truncated.bind(t => t.checked_add(granularity_magnitude(kind)))
},
)
}
///|
/// This duration rounded up (toward positive infinity) to the nearest
/// multiple of `granularity`: unchanged if already a multiple, otherwise
/// the next multiple above it. For a negative duration that is toward zero,
/// the same result as `truncate`. Fails if `granularity` is rejected (see
/// `truncate`), or with `OutOfRange` if rounding up would leave
/// `TimeDelta`'s representable range.
pub fn TimeDelta::round_up(
self : TimeDelta,
granularity : TimeDelta,
) -> Result[TimeDelta, RoundingError] {
let kind = match classify_granularity(granularity) {
Ok(v) => v
Err(e) => return Err(e)
}
let remainder = signed_remainder(self, kind)
in_range(
if remainder.is_zero() {
Some(self)
} else if self.seconds < 0L {
self.checked_sub(remainder)
} else {
self
.checked_sub(remainder)
.bind(t => t.checked_add(granularity_magnitude(kind)))
},
)
}