use crate::{
error::{fmt::util::Error as E, ErrorContext},
fmt::Parsed,
util::{
b::{self, Sign},
parse,
},
Error, SignedDuration, Span, Unit,
};
#[derive(Debug, Default)]
pub(crate) struct DurationUnits {
values: [u64; 10],
fraction: Option<u32>,
sign: Sign,
min: Option<Unit>,
max: Option<Unit>,
any_non_zero_units: bool,
}
impl DurationUnits {
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(crate) fn set_unit_value(
&mut self,
unit: Unit,
value: u64,
) -> Result<(), Error> {
assert!(self.fraction.is_none());
if let Some(min) = self.min {
if min <= unit {
return Err(Error::from(E::OutOfOrderUnits {
found: unit,
previous: min,
}));
}
}
self.min = Some(unit);
if self.max.is_none() {
self.max = Some(unit);
}
self.values[unit.as_usize()] = value;
self.any_non_zero_units = self.any_non_zero_units || value != 0;
Ok(())
}
pub(crate) fn set_hms(
&mut self,
hours: u64,
minutes: u64,
seconds: u64,
fraction: Option<u32>,
) -> Result<(), Error> {
if let Some(min) = self.min {
if min <= Unit::Hour {
return Err(Error::from(E::OutOfOrderHMS { found: min }));
}
}
self.set_unit_value(Unit::Hour, hours)?;
self.set_unit_value(Unit::Minute, minutes)?;
self.set_unit_value(Unit::Second, seconds)?;
if let Some(fraction) = fraction {
self.set_fraction(fraction)?;
}
Ok(())
}
pub(crate) fn set_fraction(&mut self, fraction: u32) -> Result<(), Error> {
assert!(fraction <= 999_999_999);
if let Some(min) = self.min {
if min > Unit::Hour || min == Unit::Nanosecond {
return Err(Error::from(E::NotAllowedFractionalUnit {
found: min,
}));
}
}
self.fraction = Some(fraction);
Ok(())
}
pub(crate) fn set_sign(&mut self, sign: Sign) {
self.sign = sign;
}
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(crate) fn to_span(&self) -> Result<Span, Error> {
const LIMIT: u64 = b::SpanYears::MAX as u64;
if self.fraction.is_some()
|| self.values.iter().any(|&value| value > LIMIT)
|| self.max.is_none()
{
return self.to_span_general();
}
let mut span = Span::new();
let years = self.values[Unit::Year.as_usize()] as i16;
let months = self.values[Unit::Month.as_usize()] as i32;
let weeks = self.values[Unit::Week.as_usize()] as i32;
let days = self.values[Unit::Day.as_usize()] as i32;
let hours = self.values[Unit::Hour.as_usize()] as i32;
let mins = self.values[Unit::Minute.as_usize()] as i64;
let secs = self.values[Unit::Second.as_usize()] as i64;
let millis = self.values[Unit::Millisecond.as_usize()] as i64;
let micros = self.values[Unit::Microsecond.as_usize()] as i64;
let nanos = self.values[Unit::Nanosecond.as_usize()] as i64;
span = span.years_unchecked(years);
span = span.months_unchecked(months);
span = span.weeks_unchecked(weeks);
span = span.days_unchecked(days);
span = span.hours_unchecked(hours);
span = span.minutes_unchecked(mins);
span = span.seconds_unchecked(secs);
span = span.milliseconds_unchecked(millis);
span = span.microseconds_unchecked(micros);
span = span.nanoseconds_unchecked(nanos);
span = span.sign_unchecked(self.get_sign());
Ok(span)
}
#[cold]
#[inline(never)]
fn to_span_general(&self) -> Result<Span, Error> {
#[cfg_attr(feature = "perf-inline", inline(always))]
fn set_time_unit(
unit: Unit,
value: i64,
span: Span,
set: impl FnOnce(Span) -> Result<Span, Error>,
) -> Result<Span, Error> {
#[cold]
#[inline(never)]
fn fractional_fallback(
err: Error,
unit: Unit,
value: i64,
span: Span,
) -> Result<Span, Error> {
if unit > Unit::Hour || unit == Unit::Nanosecond {
Err(err)
} else {
fractional_time_to_span(unit, value, 0, span)
}
}
set(span)
.or_else(|err| fractional_fallback(err, unit, value, span))
.context(E::FailedValueSet { unit })
}
let (min, _) = self.get_min_max_units()?;
let mut span = Span::new();
if self.values[Unit::Year.as_usize()] != 0 {
let value = self.get_unit_value(Unit::Year)?;
span = span
.try_years(value)
.context(E::FailedValueSet { unit: Unit::Year })?;
}
if self.values[Unit::Month.as_usize()] != 0 {
let value = self.get_unit_value(Unit::Month)?;
span = span
.try_months(value)
.context(E::FailedValueSet { unit: Unit::Month })?;
}
if self.values[Unit::Week.as_usize()] != 0 {
let value = self.get_unit_value(Unit::Week)?;
span = span
.try_weeks(value)
.context(E::FailedValueSet { unit: Unit::Week })?;
}
if self.values[Unit::Day.as_usize()] != 0 {
let value = self.get_unit_value(Unit::Day)?;
span = span
.try_days(value)
.context(E::FailedValueSet { unit: Unit::Day })?;
}
if self.values[Unit::Hour.as_usize()] != 0 {
let value = self.get_unit_value(Unit::Hour)?;
span = set_time_unit(Unit::Hour, value, span, |span| {
span.try_hours(value)
})?;
}
if self.values[Unit::Minute.as_usize()] != 0 {
let value = self.get_unit_value(Unit::Minute)?;
span = set_time_unit(Unit::Minute, value, span, |span| {
span.try_minutes(value)
})?;
}
if self.values[Unit::Second.as_usize()] != 0 {
let value = self.get_unit_value(Unit::Second)?;
span = set_time_unit(Unit::Second, value, span, |span| {
span.try_seconds(value)
})?;
}
if self.values[Unit::Millisecond.as_usize()] != 0 {
let value = self.get_unit_value(Unit::Millisecond)?;
span = set_time_unit(Unit::Millisecond, value, span, |span| {
span.try_milliseconds(value)
})?;
}
if self.values[Unit::Microsecond.as_usize()] != 0 {
let value = self.get_unit_value(Unit::Microsecond)?;
span = set_time_unit(Unit::Microsecond, value, span, |span| {
span.try_microseconds(value)
})?;
}
if self.values[Unit::Nanosecond.as_usize()] != 0 {
let value = self.get_unit_value(Unit::Nanosecond)?;
span = set_time_unit(Unit::Nanosecond, value, span, |span| {
span.try_nanoseconds(value)
})?;
}
if let Some(fraction) = self.get_fraction()? {
let value = self.get_unit_value(min)?;
span = fractional_time_to_span(min, value, fraction, span)?;
}
Ok(span)
}
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(crate) fn to_signed_duration(&self) -> Result<SignedDuration, Error> {
const LIMIT: u64 = 999;
if self.fraction.is_some()
|| self.values[..Unit::Day.as_usize()]
.iter()
.any(|&value| value > LIMIT)
|| self.max.map_or(true, |max| max > Unit::Hour)
{
return self.to_signed_duration_general();
}
let hours = self.values[Unit::Hour.as_usize()] as i64;
let mins = self.values[Unit::Minute.as_usize()] as i64;
let secs = self.values[Unit::Second.as_usize()] as i64;
let millis = self.values[Unit::Millisecond.as_usize()] as i32;
let micros = self.values[Unit::Microsecond.as_usize()] as i32;
let nanos = self.values[Unit::Nanosecond.as_usize()] as i32;
let total_secs = (hours * 3600) + (mins * 60) + secs;
let total_nanos = (millis * 1_000_000) + (micros * 1_000) + nanos;
let mut sdur =
SignedDuration::new_without_nano_overflow(total_secs, total_nanos);
if self.get_sign().is_negative() {
sdur = -sdur;
}
Ok(sdur)
}
#[cold]
#[inline(never)]
fn to_signed_duration_general(&self) -> Result<SignedDuration, Error> {
let (min, max) = self.get_min_max_units()?;
if max > Unit::Hour {
return Err(Error::from(E::NotAllowedCalendarUnit { unit: max }));
}
let mut sdur = SignedDuration::ZERO;
if self.values[Unit::Hour.as_usize()] != 0 {
let value = self.get_unit_value(Unit::Hour)?;
sdur = SignedDuration::try_from_hours(value)
.and_then(|nanos| sdur.checked_add(nanos))
.ok_or(E::OverflowForUnit { unit: Unit::Hour })?;
}
if self.values[Unit::Minute.as_usize()] != 0 {
let value = self.get_unit_value(Unit::Minute)?;
sdur = SignedDuration::try_from_mins(value)
.and_then(|nanos| sdur.checked_add(nanos))
.ok_or(E::OverflowForUnit { unit: Unit::Minute })?;
}
if self.values[Unit::Second.as_usize()] != 0 {
let value = self.get_unit_value(Unit::Second)?;
sdur = SignedDuration::from_secs(value)
.checked_add(sdur)
.ok_or(E::OverflowForUnit { unit: Unit::Second })?;
}
if self.values[Unit::Millisecond.as_usize()] != 0 {
let value = self.get_unit_value(Unit::Millisecond)?;
sdur = SignedDuration::from_millis(value)
.checked_add(sdur)
.ok_or(E::OverflowForUnit { unit: Unit::Millisecond })?;
}
if self.values[Unit::Microsecond.as_usize()] != 0 {
let value = self.get_unit_value(Unit::Microsecond)?;
sdur = SignedDuration::from_micros(value)
.checked_add(sdur)
.ok_or(E::OverflowForUnit { unit: Unit::Microsecond })?;
}
if self.values[Unit::Nanosecond.as_usize()] != 0 {
let value = self.get_unit_value(Unit::Nanosecond)?;
sdur = SignedDuration::from_nanos(value)
.checked_add(sdur)
.ok_or(E::OverflowForUnit { unit: Unit::Nanosecond })?;
}
if let Some(fraction) = self.get_fraction()? {
sdur = sdur
.checked_add(fractional_duration(min, fraction)?)
.ok_or(E::OverflowForUnitFractional { unit: min })?;
}
Ok(sdur)
}
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(crate) fn to_unsigned_duration(
&self,
) -> Result<core::time::Duration, Error> {
const LIMIT: u64 = 999;
if self.fraction.is_some()
|| self.values[..Unit::Day.as_usize()]
.iter()
.any(|&value| value > LIMIT)
|| self.max.map_or(true, |max| max > Unit::Hour)
|| self.sign.is_negative()
{
return self.to_unsigned_duration_general();
}
let hours = self.values[Unit::Hour.as_usize()];
let mins = self.values[Unit::Minute.as_usize()];
let secs = self.values[Unit::Second.as_usize()];
let millis = self.values[Unit::Millisecond.as_usize()] as u32;
let micros = self.values[Unit::Microsecond.as_usize()] as u32;
let nanos = self.values[Unit::Nanosecond.as_usize()] as u32;
let total_secs = (hours * 3600) + (mins * 60) + secs;
let total_nanos = (millis * 1_000_000) + (micros * 1_000) + nanos;
let sdur = core::time::Duration::new(total_secs, total_nanos);
Ok(sdur)
}
#[cold]
#[inline(never)]
fn to_unsigned_duration_general(
&self,
) -> Result<core::time::Duration, Error> {
#[inline]
const fn try_from_hours(hours: u64) -> Option<core::time::Duration> {
const MAX_HOUR: u64 = u64::MAX / (60 * 60);
if hours > MAX_HOUR {
return None;
}
Some(core::time::Duration::from_secs(hours * 60 * 60))
}
#[inline]
const fn try_from_mins(mins: u64) -> Option<core::time::Duration> {
const MAX_MINUTE: u64 = u64::MAX / 60;
if mins > MAX_MINUTE {
return None;
}
Some(core::time::Duration::from_secs(mins * 60))
}
if self.sign.is_negative() {
return Err(Error::from(E::NotAllowedNegative));
}
let (min, max) = self.get_min_max_units()?;
if max > Unit::Hour {
return Err(Error::from(E::NotAllowedCalendarUnit { unit: max }));
}
let mut sdur = core::time::Duration::ZERO;
if self.values[Unit::Hour.as_usize()] != 0 {
let value = self.values[Unit::Hour.as_usize()];
sdur = try_from_hours(value)
.and_then(|nanos| sdur.checked_add(nanos))
.ok_or(E::OverflowForUnit { unit: Unit::Hour })?;
}
if self.values[Unit::Minute.as_usize()] != 0 {
let value = self.values[Unit::Minute.as_usize()];
sdur = try_from_mins(value)
.and_then(|nanos| sdur.checked_add(nanos))
.ok_or(E::OverflowForUnit { unit: Unit::Minute })?;
}
if self.values[Unit::Second.as_usize()] != 0 {
let value = self.values[Unit::Second.as_usize()];
sdur = core::time::Duration::from_secs(value)
.checked_add(sdur)
.ok_or(E::OverflowForUnit { unit: Unit::Second })?;
}
if self.values[Unit::Millisecond.as_usize()] != 0 {
let value = self.values[Unit::Millisecond.as_usize()];
sdur = core::time::Duration::from_millis(value)
.checked_add(sdur)
.ok_or(E::OverflowForUnit { unit: Unit::Millisecond })?;
}
if self.values[Unit::Microsecond.as_usize()] != 0 {
let value = self.values[Unit::Microsecond.as_usize()];
sdur = core::time::Duration::from_micros(value)
.checked_add(sdur)
.ok_or(E::OverflowForUnit { unit: Unit::Microsecond })?;
}
if self.values[Unit::Nanosecond.as_usize()] != 0 {
let value = self.values[Unit::Nanosecond.as_usize()];
sdur = core::time::Duration::from_nanos(value)
.checked_add(sdur)
.ok_or(E::OverflowForUnit { unit: Unit::Nanosecond })?;
}
if let Some(fraction) = self.get_fraction()? {
sdur = sdur
.checked_add(
fractional_duration(min, fraction)?.unsigned_abs(),
)
.ok_or(E::OverflowForUnitFractional { unit: Unit::Hour })?;
}
Ok(sdur)
}
pub(crate) fn get_min(&self) -> Option<Unit> {
self.min
}
fn get_min_max_units(&self) -> Result<(Unit, Unit), Error> {
let (Some(min), Some(max)) = (self.min, self.max) else {
return Err(Error::from(E::EmptyDuration));
};
Ok((min, max))
}
#[cfg_attr(feature = "perf-inline", inline(always))]
fn get_unit_value(&self, unit: Unit) -> Result<i64, Error> {
const I64_MIN_ABS: u64 = i64::MIN.unsigned_abs();
#[cold]
#[inline(never)]
fn general(unit: Unit, value: u64, sign: Sign) -> Result<i64, Error> {
if sign.is_negative() && value == I64_MIN_ABS {
return Ok(i64::MIN);
}
let mut value = i64::try_from(value)
.map_err(|_| E::SignedOverflowForUnit { unit })?;
if sign.is_negative() {
value = value
.checked_neg()
.ok_or(E::SignedOverflowForUnit { unit })?;
}
Ok(value)
}
let sign = self.get_sign();
let value = self.values[unit.as_usize()];
if value >= I64_MIN_ABS {
return general(unit, value, sign);
}
let mut value = value as i64;
if sign.is_negative() {
value = -value;
}
Ok(value)
}
fn get_fraction(&self) -> Result<Option<i32>, Error> {
let Some(fraction) = self.fraction else {
return Ok(None);
};
let mut fraction = fraction as i32;
if self.get_sign().is_negative() {
fraction = -fraction;
}
Ok(Some(fraction))
}
fn get_sign(&self) -> Sign {
if self.any_non_zero_units {
self.sign
} else {
Sign::Zero
}
}
}
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(crate) fn parse_temporal_fraction<'i>(
input: &'i [u8],
) -> Result<Parsed<'i, Option<u32>>, Error> {
#[inline(never)]
fn imp<'i>(mut input: &'i [u8]) -> Result<Parsed<'i, Option<u32>>, Error> {
let mkdigits = parse::slicer(input);
while mkdigits(input).len() <= 8
&& input.first().map_or(false, u8::is_ascii_digit)
{
input = &input[1..];
}
let digits = mkdigits(input);
if digits.is_empty() {
return Err(Error::from(E::MissingFractionalDigits));
}
let nanoseconds =
parse::fraction(digits).context(E::InvalidFraction)?;
let nanoseconds = nanoseconds as u32;
Ok(Parsed { value: Some(nanoseconds), input })
}
if input.is_empty() || (input[0] != b'.' && input[0] != b',') {
return Ok(Parsed { value: None, input });
}
imp(&input[1..])
}
#[inline(never)]
fn fractional_time_to_span(
unit: Unit,
value: i64,
fraction: i32,
mut span: Span,
) -> Result<Span, Error> {
const MAX_HOURS: i64 = b::SpanHours::MAX as i64;
const MAX_MINS: i64 = b::SpanMinutes::MAX;
const MAX_SECS: i64 = b::SpanSeconds::MAX;
const MAX_MILLIS: i128 = b::SpanMilliseconds::MAX as i128;
const MAX_MICROS: i128 = b::SpanMicroseconds::MAX as i128;
const MIN_HOURS: i64 = b::SpanHours::MIN as i64;
const MIN_MINS: i64 = b::SpanMinutes::MIN;
const MIN_SECS: i64 = b::SpanSeconds::MIN;
const MIN_MILLIS: i128 = b::SpanMilliseconds::MIN as i128;
const MIN_MICROS: i128 = b::SpanMicroseconds::MIN as i128;
let mut sdur = fractional_time_to_duration(unit, value, fraction)?;
if unit >= Unit::Hour && !sdur.is_zero() {
let (mut hours, rem) = sdur.as_hours_with_remainder();
sdur = rem;
if hours > MAX_HOURS {
sdur += SignedDuration::from_hours(hours - MAX_HOURS);
hours = MAX_HOURS;
} else if hours < MIN_HOURS {
sdur += SignedDuration::from_hours(hours - MIN_HOURS);
hours = MIN_HOURS;
}
span = span.hours(hours);
}
if unit >= Unit::Minute && !sdur.is_zero() {
let (mut mins, rem) = sdur.as_mins_with_remainder();
sdur = rem;
if mins > MAX_MINS {
sdur += SignedDuration::from_mins(mins - MAX_MINS);
mins = MAX_MINS;
} else if mins < MIN_MINS {
sdur += SignedDuration::from_mins(mins - MIN_MINS);
mins = MIN_MINS;
}
span = span.minutes(mins);
}
if unit >= Unit::Second && !sdur.is_zero() {
let (mut secs, rem) = sdur.as_secs_with_remainder();
sdur = rem;
if secs > MAX_SECS {
sdur += SignedDuration::from_secs(secs - MAX_SECS);
secs = MAX_SECS;
} else if secs < MIN_SECS {
sdur += SignedDuration::from_secs(secs - MIN_SECS);
secs = MIN_SECS;
}
span = span.seconds(secs);
}
if unit >= Unit::Millisecond && !sdur.is_zero() {
let (mut millis, rem) = sdur.as_millis_with_remainder();
sdur = rem;
if millis > MAX_MILLIS {
sdur += SignedDuration::from_millis_i128(millis - MAX_MILLIS);
millis = MAX_MILLIS;
} else if millis < MIN_MILLIS {
sdur += SignedDuration::from_millis_i128(millis - MIN_MILLIS);
millis = MIN_MILLIS;
}
span = span.milliseconds(i64::try_from(millis).unwrap());
}
if unit >= Unit::Microsecond && !sdur.is_zero() {
let (mut micros, rem) = sdur.as_micros_with_remainder();
sdur = rem;
if micros > MAX_MICROS {
sdur += SignedDuration::from_micros_i128(micros - MAX_MICROS);
micros = MAX_MICROS;
} else if micros < MIN_MICROS {
sdur += SignedDuration::from_micros_i128(micros - MIN_MICROS);
micros = MIN_MICROS;
}
span = span.microseconds(i64::try_from(micros).unwrap());
}
if !sdur.is_zero() {
let nanos = sdur.as_nanos();
let nanos64 =
i64::try_from(nanos).map_err(|_| E::InvalidFractionNanos)?;
span =
span.try_nanoseconds(nanos64).context(E::InvalidFractionNanos)?;
}
Ok(span)
}
#[inline(never)]
fn fractional_time_to_duration(
unit: Unit,
value: i64,
fraction: i32,
) -> Result<SignedDuration, Error> {
let sdur = duration_unit_value(unit, value)?;
let fraction_dur = fractional_duration(unit, fraction)?;
Ok(sdur
.checked_add(fraction_dur)
.ok_or(E::OverflowForUnitFractional { unit })?)
}
#[inline(never)]
fn fractional_duration(
unit: Unit,
fraction: i32,
) -> Result<SignedDuration, Error> {
let fraction = i64::from(fraction);
let nanos = match unit {
Unit::Hour => fraction * b::SECS_PER_HOUR,
Unit::Minute => fraction * b::SECS_PER_MIN,
Unit::Second => fraction,
Unit::Millisecond => fraction / b::NANOS_PER_MICRO,
Unit::Microsecond => fraction / b::NANOS_PER_MILLI,
unit => {
return Err(Error::from(E::NotAllowedFractionalUnit {
found: unit,
}));
}
};
Ok(SignedDuration::from_nanos(nanos))
}
#[cfg_attr(feature = "perf-inline", inline(always))]
fn duration_unit_value(
unit: Unit,
value: i64,
) -> Result<SignedDuration, Error> {
let sdur = match unit {
Unit::Hour => {
let seconds = value
.checked_mul(b::SECS_PER_HOUR)
.ok_or(E::ConversionToSecondsFailed { unit: Unit::Hour })?;
SignedDuration::from_secs(seconds)
}
Unit::Minute => {
let seconds = value
.checked_mul(b::SECS_PER_MIN)
.ok_or(E::ConversionToSecondsFailed { unit: Unit::Minute })?;
SignedDuration::from_secs(seconds)
}
Unit::Second => SignedDuration::from_secs(value),
Unit::Millisecond => SignedDuration::from_millis(value),
Unit::Microsecond => SignedDuration::from_micros(value),
Unit::Nanosecond => SignedDuration::from_nanos(value),
unsupported => {
return Err(Error::from(E::NotAllowedCalendarUnit {
unit: unsupported,
}))
}
};
Ok(sdur)
}