use crate::{
error::{err, ErrorContext},
fmt::Parsed,
util::{c::Sign, escape, parse, t},
Error, SignedDuration, Span, Unit,
};
#[derive(Clone, Copy, Debug)]
pub(crate) struct DecimalFormatter {
force_sign: Option<bool>,
minimum_digits: u8,
padding_byte: u8,
}
impl DecimalFormatter {
pub(crate) const fn new() -> DecimalFormatter {
DecimalFormatter {
force_sign: None,
minimum_digits: 0,
padding_byte: b'0',
}
}
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(crate) const fn format_signed(&self, value: i64) -> Decimal {
Decimal::signed(self, value)
}
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(crate) const fn format_unsigned(&self, value: u64) -> Decimal {
Decimal::unsigned(self, value)
}
#[cfg(test)]
pub(crate) const fn force_sign(
self,
zero_is_positive: bool,
) -> DecimalFormatter {
DecimalFormatter { force_sign: Some(zero_is_positive), ..self }
}
pub(crate) const fn padding(self, mut digits: u8) -> DecimalFormatter {
if digits > Decimal::MAX_I64_DIGITS {
digits = Decimal::MAX_I64_DIGITS;
}
DecimalFormatter { minimum_digits: digits, ..self }
}
pub(crate) const fn padding_byte(self, byte: u8) -> DecimalFormatter {
DecimalFormatter { padding_byte: byte, ..self }
}
const fn get_signed_minimum_digits(&self) -> u8 {
if self.minimum_digits <= Decimal::MAX_I64_DIGITS {
self.minimum_digits
} else {
Decimal::MAX_I64_DIGITS
}
}
const fn get_unsigned_minimum_digits(&self) -> u8 {
if self.minimum_digits <= Decimal::MAX_U64_DIGITS {
self.minimum_digits
} else {
Decimal::MAX_U64_DIGITS
}
}
}
impl Default for DecimalFormatter {
fn default() -> DecimalFormatter {
DecimalFormatter::new()
}
}
#[derive(Debug)]
pub(crate) struct Decimal {
buf: [u8; Self::MAX_LEN as usize],
start: u8,
end: u8,
}
impl Decimal {
const MAX_LEN: u8 = 20;
const MAX_I64_DIGITS: u8 = 19;
const MAX_U64_DIGITS: u8 = 20;
#[cfg_attr(feature = "perf-inline", inline(always))]
const fn unsigned(
formatter: &DecimalFormatter,
mut value: u64,
) -> Decimal {
let mut decimal = Decimal {
buf: [0; Self::MAX_LEN as usize],
start: Self::MAX_LEN,
end: Self::MAX_LEN,
};
loop {
decimal.start -= 1;
let digit = (value % 10) as u8;
value /= 10;
decimal.buf[decimal.start as usize] = b'0' + digit;
if value == 0 {
break;
}
}
while decimal.len() < formatter.get_unsigned_minimum_digits() {
decimal.start -= 1;
decimal.buf[decimal.start as usize] = formatter.padding_byte;
}
decimal
}
#[cfg_attr(feature = "perf-inline", inline(always))]
const fn signed(formatter: &DecimalFormatter, mut value: i64) -> Decimal {
if value >= 0 && formatter.force_sign.is_none() {
let mut decimal = Decimal {
buf: [0; Self::MAX_LEN as usize],
start: Self::MAX_LEN,
end: Self::MAX_LEN,
};
loop {
decimal.start -= 1;
let digit = (value % 10) as u8;
value /= 10;
decimal.buf[decimal.start as usize] = b'0' + digit;
if value == 0 {
break;
}
}
while decimal.len() < formatter.get_signed_minimum_digits() {
decimal.start -= 1;
decimal.buf[decimal.start as usize] = formatter.padding_byte;
}
return decimal;
}
Decimal::signed_cold(formatter, value)
}
#[cold]
#[inline(never)]
const fn signed_cold(formatter: &DecimalFormatter, value: i64) -> Decimal {
let sign = value.signum();
let Some(mut value) = value.checked_abs() else {
let buf = [
b'-', b'9', b'2', b'2', b'3', b'3', b'7', b'2', b'0', b'3',
b'6', b'8', b'5', b'4', b'7', b'7', b'5', b'8', b'0', b'8',
];
return Decimal { buf, start: 0, end: Self::MAX_LEN };
};
let mut decimal = Decimal {
buf: [0; Self::MAX_LEN as usize],
start: Self::MAX_LEN,
end: Self::MAX_LEN,
};
loop {
decimal.start -= 1;
let digit = (value % 10) as u8;
value /= 10;
decimal.buf[decimal.start as usize] = b'0' + digit;
if value == 0 {
break;
}
}
while decimal.len() < formatter.get_signed_minimum_digits() {
decimal.start -= 1;
decimal.buf[decimal.start as usize] = formatter.padding_byte;
}
if sign < 0 {
decimal.start -= 1;
decimal.buf[decimal.start as usize] = b'-';
} else if let Some(zero_is_positive) = formatter.force_sign {
let ascii_sign =
if sign > 0 || zero_is_positive { b'+' } else { b'-' };
decimal.start -= 1;
decimal.buf[decimal.start as usize] = ascii_sign;
}
decimal
}
#[inline]
const fn len(&self) -> u8 {
self.end - self.start
}
#[inline]
fn as_bytes(&self) -> &[u8] {
&self.buf[usize::from(self.start)..usize::from(self.end)]
}
#[inline]
pub(crate) fn as_str(&self) -> &str {
unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct FractionalFormatter {
precision: Option<u8>,
}
impl FractionalFormatter {
pub(crate) const fn new() -> FractionalFormatter {
FractionalFormatter { precision: None }
}
pub(crate) const fn format(&self, value: u32) -> Fractional {
Fractional::new(self, value)
}
pub(crate) const fn precision(
self,
precision: Option<u8>,
) -> FractionalFormatter {
let precision = match precision {
None => None,
Some(p) if p > 9 => Some(9),
Some(p) => Some(p),
};
FractionalFormatter { precision, ..self }
}
pub(crate) fn will_write_digits(self, value: u32) -> bool {
self.precision.map_or_else(|| value != 0, |p| p > 0)
}
pub(crate) fn has_non_zero_fixed_precision(self) -> bool {
self.precision.map_or(false, |p| p > 0)
}
pub(crate) fn has_zero_fixed_precision(self) -> bool {
self.precision.map_or(false, |p| p == 0)
}
}
#[derive(Debug)]
pub(crate) struct Fractional {
buf: [u8; Self::MAX_LEN as usize],
end: u8,
}
impl Fractional {
const MAX_LEN: u8 = 9;
pub(crate) const fn new(
formatter: &FractionalFormatter,
mut value: u32,
) -> Fractional {
assert!(value <= 999_999_999);
let mut fractional = Fractional {
buf: [b'0'; Self::MAX_LEN as usize],
end: Self::MAX_LEN,
};
let mut i = 9;
loop {
i -= 1;
let digit = (value % 10) as u8;
value /= 10;
fractional.buf[i] += digit;
if value == 0 {
break;
}
}
if let Some(precision) = formatter.precision {
fractional.end = precision;
} else {
while fractional.end > 0
&& fractional.buf[fractional.end as usize - 1] == b'0'
{
fractional.end -= 1;
}
}
fractional
}
pub(crate) fn as_bytes(&self) -> &[u8] {
&self.buf[..usize::from(self.end)]
}
pub(crate) fn as_str(&self) -> &str {
unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
}
}
#[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(err!(
"found value {value:?} with unit {unit} \
after unit {prev_unit}, but units must be \
written from largest to smallest \
(and they can't be repeated)",
unit = unit.singular(),
prev_unit = min.singular(),
));
}
}
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(err!(
"found `HH:MM:SS` after unit {min}, \
but `HH:MM:SS` can only appear after \
years, months, weeks or days",
min = min.singular(),
));
}
}
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 self.min == Some(Unit::Nanosecond) {
return Err(err!("fractional nanoseconds are not supported"));
}
if let Some(min) = self.min {
if min > Unit::Hour {
return Err(err!(
"fractional {plural} are not supported",
plural = min.plural()
));
}
}
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 = t::SpanYears::MAX_SELF.get_unchecked() 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().as_ranged_integer());
Ok(span)
}
#[cold]
#[inline(never)]
fn to_span_general(&self) -> Result<Span, Error> {
fn error_context(unit: Unit, value: i64) -> Error {
err!(
"failed to set value {value:?} as {unit} unit on span",
unit = unit.singular(),
)
}
#[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))
.with_context(|| error_context(unit, value))
}
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)
.with_context(|| error_context(Unit::Year, value))?;
}
if self.values[Unit::Month.as_usize()] != 0 {
let value = self.get_unit_value(Unit::Month)?;
span = span
.try_months(value)
.with_context(|| error_context(Unit::Month, value))?;
}
if self.values[Unit::Week.as_usize()] != 0 {
let value = self.get_unit_value(Unit::Week)?;
span = span
.try_weeks(value)
.with_context(|| error_context(Unit::Week, value))?;
}
if self.values[Unit::Day.as_usize()] != 0 {
let value = self.get_unit_value(Unit::Day)?;
span = span
.try_days(value)
.with_context(|| error_context(Unit::Day, value))?;
}
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(err!(
"parsing {unit} units into a `SignedDuration` is not supported \
(perhaps try parsing into a `Span` instead)",
unit = max.singular(),
));
}
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_else(|| {
err!(
"accumulated `SignedDuration` of `{sdur:?}` \
overflowed when adding {value} of unit {unit}",
unit = Unit::Hour.singular(),
)
})?;
}
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_else(|| {
err!(
"accumulated `SignedDuration` of `{sdur:?}` \
overflowed when adding {value} of unit {unit}",
unit = Unit::Minute.singular(),
)
})?;
}
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_else(|| {
err!(
"accumulated `SignedDuration` of `{sdur:?}` \
overflowed when adding {value} of unit {unit}",
unit = Unit::Second.singular(),
)
})?;
}
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_else(|| {
err!(
"accumulated `SignedDuration` of `{sdur:?}` \
overflowed when adding {value} of unit {unit}",
unit = Unit::Millisecond.singular(),
)
})?;
}
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_else(|| {
err!(
"accumulated `SignedDuration` of `{sdur:?}` \
overflowed when adding {value} of unit {unit}",
unit = Unit::Microsecond.singular(),
)
})?;
}
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_else(|| {
err!(
"accumulated `SignedDuration` of `{sdur:?}` \
overflowed when adding {value} of unit {unit}",
unit = Unit::Nanosecond.singular(),
)
})?;
}
if let Some(fraction) = self.get_fraction()? {
sdur = sdur
.checked_add(fractional_duration(min, fraction)?)
.ok_or_else(|| {
err!(
"accumulated `SignedDuration` of `{sdur:?}` \
overflowed when adding 0.{fraction} of unit {unit}",
unit = min.singular(),
)
})?;
}
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(err!(
"cannot parse negative duration into unsigned \
`std::time::Duration`",
));
}
let (min, max) = self.get_min_max_units()?;
if max > Unit::Hour {
return Err(err!(
"parsing {unit} units into a `std::time::Duration` \
is not supported (perhaps try parsing into a `Span` instead)",
unit = max.singular(),
));
}
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_else(|| {
err!(
"accumulated `std::time::Duration` of `{sdur:?}` \
overflowed when adding {value} of unit {unit}",
unit = Unit::Hour.singular(),
)
})?;
}
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_else(|| {
err!(
"accumulated `std::time::Duration` of `{sdur:?}` \
overflowed when adding {value} of unit {unit}",
unit = Unit::Minute.singular(),
)
})?;
}
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_else(|| {
err!(
"accumulated `std::time::Duration` of `{sdur:?}` \
overflowed when adding {value} of unit {unit}",
unit = Unit::Second.singular(),
)
})?;
}
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_else(|| {
err!(
"accumulated `std::time::Duration` of `{sdur:?}` \
overflowed when adding {value} of unit {unit}",
unit = Unit::Millisecond.singular(),
)
})?;
}
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_else(|| {
err!(
"accumulated `std::time::Duration` of `{sdur:?}` \
overflowed when adding {value} of unit {unit}",
unit = Unit::Microsecond.singular(),
)
})?;
}
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_else(|| {
err!(
"accumulated `std::time::Duration` of `{sdur:?}` \
overflowed when adding {value} of unit {unit}",
unit = Unit::Nanosecond.singular(),
)
})?;
}
if let Some(fraction) = self.get_fraction()? {
sdur = sdur
.checked_add(
fractional_duration(min, fraction)?.unsigned_abs(),
)
.ok_or_else(|| {
err!(
"accumulated `std::time::Duration` of `{sdur:?}` \
overflowed when adding 0.{fraction} of unit {unit}",
unit = min.singular(),
)
})?;
}
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(err!("no parsed duration components"));
};
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(|_| {
err!(
"`{sign}{value}` {unit} is too big (or small) \
to fit into a signed 64-bit integer",
unit = unit.plural()
)
})?;
if sign.is_negative() {
value = value.checked_neg().ok_or_else(|| {
err!(
"`{sign}{value}` {unit} is too big (or small) \
to fit into a signed 64-bit integer",
unit = unit.plural()
)
})?;
}
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(err!(
"found decimal after seconds component, \
but did not find any decimal digits after decimal",
));
}
let nanoseconds = parse::fraction(digits).map_err(|err| {
err!(
"failed to parse {digits:?} as fractional component \
(up to 9 digits, nanosecond precision): {err}",
digits = escape::Bytes(digits),
)
})?;
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 = t::SpanHours::MAX_SELF.get_unchecked() as i64;
const MAX_MINS: i64 = t::SpanMinutes::MAX_SELF.get_unchecked() as i64;
const MAX_SECS: i64 = t::SpanSeconds::MAX_SELF.get_unchecked() as i64;
const MAX_MILLIS: i128 =
t::SpanMilliseconds::MAX_SELF.get_unchecked() as i128;
const MAX_MICROS: i128 =
t::SpanMicroseconds::MAX_SELF.get_unchecked() as i128;
const MIN_HOURS: i64 = t::SpanHours::MIN_SELF.get_unchecked() as i64;
const MIN_MINS: i64 = t::SpanMinutes::MIN_SELF.get_unchecked() as i64;
const MIN_SECS: i64 = t::SpanSeconds::MIN_SELF.get_unchecked() as i64;
const MIN_MILLIS: i128 =
t::SpanMilliseconds::MIN_SELF.get_unchecked() as i128;
const MIN_MICROS: i128 =
t::SpanMicroseconds::MIN_SELF.get_unchecked() 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(|_| {
err!(
"failed to set nanosecond value {nanos} (it overflows \
`i64`) on span determined from {value}.{fraction}",
)
})?;
span = span.try_nanoseconds(nanos64).with_context(|| {
err!(
"failed to set nanosecond value {nanos64} on span \
determined from {value}.{fraction}",
)
})?;
}
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)?;
sdur.checked_add(fraction_dur).ok_or_else(|| {
err!(
"accumulated `SignedDuration` of `{sdur:?}` overflowed \
when adding `{fraction_dur:?}` (from fractional {unit} units)",
unit = unit.singular(),
)
})
}
#[inline(never)]
fn fractional_duration(
unit: Unit,
fraction: i32,
) -> Result<SignedDuration, Error> {
let fraction = i64::from(fraction);
let nanos = match unit {
Unit::Hour => fraction * t::SECONDS_PER_HOUR.value(),
Unit::Minute => fraction * t::SECONDS_PER_MINUTE.value(),
Unit::Second => fraction,
Unit::Millisecond => fraction / t::NANOS_PER_MICRO.value(),
Unit::Microsecond => fraction / t::NANOS_PER_MILLI.value(),
unit => {
return Err(err!(
"fractional {unit} units are not allowed",
unit = unit.singular(),
))
}
};
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(t::SECONDS_PER_HOUR.value())
.ok_or_else(|| {
err!("converting {value} hours to seconds overflows i64")
})?;
SignedDuration::from_secs(seconds)
}
Unit::Minute => {
let seconds = value
.checked_mul(t::SECONDS_PER_MINUTE.value())
.ok_or_else(|| {
err!("converting {value} minutes to seconds overflows i64")
})?;
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(err!(
"parsing {unit} units into a `SignedDuration` is not supported \
(perhaps try parsing into a `Span` instead)",
unit = unsupported.singular(),
));
}
};
Ok(sdur)
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use super::*;
#[test]
fn decimal() {
let x = DecimalFormatter::new().format_signed(i64::MIN);
assert_eq!(x.as_str(), "-9223372036854775808");
let x = DecimalFormatter::new().format_signed(i64::MIN + 1);
assert_eq!(x.as_str(), "-9223372036854775807");
let x = DecimalFormatter::new().format_signed(i64::MAX);
assert_eq!(x.as_str(), "9223372036854775807");
let x =
DecimalFormatter::new().force_sign(true).format_signed(i64::MAX);
assert_eq!(x.as_str(), "+9223372036854775807");
let x = DecimalFormatter::new().format_signed(0);
assert_eq!(x.as_str(), "0");
let x = DecimalFormatter::new().force_sign(true).format_signed(0);
assert_eq!(x.as_str(), "+0");
let x = DecimalFormatter::new().force_sign(false).format_signed(0);
assert_eq!(x.as_str(), "-0");
let x = DecimalFormatter::new().padding(4).format_signed(0);
assert_eq!(x.as_str(), "0000");
let x = DecimalFormatter::new().padding(4).format_signed(789);
assert_eq!(x.as_str(), "0789");
let x = DecimalFormatter::new().padding(4).format_signed(-789);
assert_eq!(x.as_str(), "-0789");
let x = DecimalFormatter::new()
.force_sign(true)
.padding(4)
.format_signed(789);
assert_eq!(x.as_str(), "+0789");
}
#[test]
fn fractional_auto() {
let f = |n| FractionalFormatter::new().format(n).as_str().to_string();
assert_eq!(f(0), "");
assert_eq!(f(123_000_000), "123");
assert_eq!(f(123_456_000), "123456");
assert_eq!(f(123_456_789), "123456789");
assert_eq!(f(456_789), "000456789");
assert_eq!(f(789), "000000789");
}
#[test]
fn fractional_precision() {
let f = |precision, n| {
FractionalFormatter::new()
.precision(Some(precision))
.format(n)
.as_str()
.to_string()
};
assert_eq!(f(0, 0), "");
assert_eq!(f(1, 0), "0");
assert_eq!(f(9, 0), "000000000");
assert_eq!(f(3, 123_000_000), "123");
assert_eq!(f(6, 123_000_000), "123000");
assert_eq!(f(9, 123_000_000), "123000000");
assert_eq!(f(3, 123_456_000), "123");
assert_eq!(f(6, 123_456_000), "123456");
assert_eq!(f(9, 123_456_000), "123456000");
assert_eq!(f(3, 123_456_789), "123");
assert_eq!(f(6, 123_456_789), "123456");
assert_eq!(f(9, 123_456_789), "123456789");
assert_eq!(f(2, 889_000_000), "88");
assert_eq!(f(2, 999_000_000), "99");
}
}