use super::calendar::civil_from_days;
use super::error::{FormatError, UnixTimeError};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
const NANOS_PER_SECOND: u32 = 1_000_000_000;
const SECONDS_PER_DAY: i64 = 86_400;
fn divide(numerator: u32, denominator: u32) -> u32 {
numerator.checked_div(denominator).unwrap_or(0)
}
fn push_digit(out: &mut String, value: u32) {
out.push(char::from_digit(value % 10, 10).unwrap_or('0'));
}
fn digit_byte(value: u32) -> u8 {
b'0'.saturating_add(u8::try_from(value % 10).unwrap_or(0))
}
pub fn unix_parts(at: SystemTime) -> Result<(i64, u32), UnixTimeError> {
match at.duration_since(UNIX_EPOCH) {
Ok(duration) => {
let seconds = i64::try_from(duration.as_secs()).map_err(|cause| {
UnixTimeError::SystemTimeOutsideI64Range {
before_epoch: false,
cause,
}
})?;
Ok((seconds, duration.subsec_nanos()))
}
Err(before) => {
let duration = before.duration();
if duration.as_secs() == i64::MAX.unsigned_abs().saturating_add(1)
&& duration.subsec_nanos() == 0
{
return Ok((i64::MIN, 0));
}
let whole = match i64::try_from(duration.as_secs()) {
Ok(whole) => whole,
Err(cause) => {
let refusal = Err(UnixTimeError::SystemTimeOutsideI64Range {
before_epoch: true,
cause,
});
#[cfg(feature = "trace")]
crate::trace::debug!(error = ?refusal.as_ref().err(), "unix_parts: returning an error to the caller");
return refusal;
}
};
let nanos = duration.subsec_nanos();
let seconds = if nanos == 0 {
whole.checked_neg()
} else {
whole.checked_neg().and_then(|value| value.checked_sub(1))
};
let seconds = match seconds {
Some(seconds) => seconds,
None => {
let refusal = Err(UnixTimeError::NegationOverflow {
magnitude: whole.unsigned_abs(),
});
#[cfg(feature = "trace")]
crate::trace::debug!(error = ?refusal.as_ref().err(), "unix_parts: returning an error to the caller");
return refusal;
}
};
Ok((
seconds,
if nanos == 0 {
0
} else {
NANOS_PER_SECOND.saturating_sub(nanos)
},
))
}
}
}
#[deprecated(note = "lossy; use unix_parts and handle UnixTimeError")]
#[must_use]
pub fn unix_parts_lossy(at: SystemTime) -> (i64, u32) {
unix_parts(at).unwrap_or_else(|error| match error {
UnixTimeError::SystemTimeOutsideI64Range {
before_epoch: true, ..
} => (i64::MIN, 0),
UnixTimeError::SystemTimeOutsideI64Range {
before_epoch: false,
..
}
| UnixTimeError::NegationOverflow { .. }
| UnixTimeError::SecondsOverflow { .. }
| UnixTimeError::SystemTimeOutOfRange { .. } => (i64::MAX, 0),
})
}
pub fn from_unix_parts(secs: i64, nanos: u32) -> Result<SystemTime, UnixTimeError> {
let carry = nanos.checked_div(NANOS_PER_SECOND).unwrap_or(0);
let fraction = nanos.checked_rem(NANOS_PER_SECOND).unwrap_or(0);
let normalized_secs =
secs.checked_add(i64::from(carry))
.ok_or(UnixTimeError::SecondsOverflow {
seconds: secs,
nanoseconds: nanos,
})?;
let instant = if normalized_secs >= 0 {
let whole = u64::try_from(normalized_secs).unwrap_or(0);
UNIX_EPOCH.checked_add(Duration::new(whole, fraction))
} else if fraction == 0 {
UNIX_EPOCH.checked_sub(Duration::new(normalized_secs.unsigned_abs(), 0))
} else {
UNIX_EPOCH.checked_sub(Duration::new(
normalized_secs.unsigned_abs().saturating_sub(1),
NANOS_PER_SECOND.saturating_sub(fraction),
))
};
instant.ok_or(UnixTimeError::SystemTimeOutOfRange {
seconds: normalized_secs,
nanoseconds: fraction,
})
}
#[deprecated(note = "lossy; use from_unix_parts and handle UnixTimeError")]
#[must_use]
pub fn from_unix_parts_lossy(secs: i64, nanos: u32) -> SystemTime {
from_unix_parts(secs, nanos).unwrap_or(UNIX_EPOCH)
}
fn write_two_digits(out: &mut String, value: u32) {
push_digit(out, divide(value, 10));
push_digit(out, value % 10);
}
fn write_four_digits(out: &mut String, value: u32) {
write_two_digits(out, divide(value, 100));
write_two_digits(out, value % 100);
}
fn extract_fraction_digits(nanos: u32, buf: &mut [u8; 9]) -> usize {
let mut remainder = nanos;
for index in (0..9).rev() {
buf[index] = digit_byte(remainder % 10);
remainder = divide(remainder, 10);
}
let mut length: usize = 9;
while length > 0 && buf[length.saturating_sub(1)] == b'0' {
length = length.saturating_sub(1);
}
length
}
fn write_fraction(out: &mut String, nanos: u32) {
let mut buf = [0u8; 9];
let length = extract_fraction_digits(nanos, &mut buf);
for &byte in &buf[..length] {
out.push(char::from(byte));
}
}
fn write_civil(out: &mut String, year: i64, month: u32, day: u32) {
write_four_digits(out, u32::try_from(year).unwrap_or(0));
out.push('-');
write_two_digits(out, month);
out.push('-');
write_two_digits(out, day);
}
fn write_time_parts(out: &mut String, hour: u32, min: u32, sec: u32) {
out.push('T');
write_two_digits(out, hour);
out.push(':');
write_two_digits(out, min);
out.push(':');
write_two_digits(out, sec);
}
fn write_optional_fraction(out: &mut String, nanos: u32) {
if nanos > 0 {
out.push('.');
write_fraction(out, nanos);
}
out.push('Z');
}
pub fn to_rfc3339(at: SystemTime) -> Result<String, FormatError> {
let (secs, nanos) = unix_parts(at).map_err(FormatError::UnixTime)?;
let (year, month, day) = civil_from_days(secs.div_euclid(SECONDS_PER_DAY));
if !(0..=9999).contains(&year) {
let refusal = Err(FormatError::YearOutsideRfc3339 { year });
#[cfg(feature = "trace")]
crate::trace::debug!(error = ?refusal.as_ref().err(), "to_rfc3339: returning an error to the caller");
return refusal;
}
let rem = u32::try_from(secs.rem_euclid(SECONDS_PER_DAY)).unwrap_or(0);
let hour = divide(rem, 3_600);
let min = divide(rem % 3_600, 60);
let sec = rem % 60;
let mut out = String::with_capacity(35);
write_civil(&mut out, year, month, day);
write_time_parts(&mut out, hour, min, sec);
write_optional_fraction(&mut out, nanos);
Ok(out)
}