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 low_byte(value: i64) -> u8 {
value.to_le_bytes()[0]
}
fn push_digit(out: &mut String, value: i64) {
out.push(char::from(
b'0'.saturating_add(low_byte(value.rem_euclid(10))),
));
}
fn digit_byte(value: i64) -> u8 {
b'0'.saturating_add(low_byte(value.rem_euclid(10)))
}
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) {
match unix_parts(at) {
Ok(parts) => parts,
Err(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 = i64::from(nanos.div_euclid(NANOS_PER_SECOND));
let fraction = nanos.rem_euclid(NANOS_PER_SECOND);
let normalized_secs = secs
.checked_add(carry)
.ok_or(UnixTimeError::SecondsOverflow {
seconds: secs,
nanoseconds: nanos,
})?;
let instant = if normalized_secs >= 0 {
let whole = u64::from_le_bytes(normalized_secs.to_le_bytes());
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 {
match from_unix_parts(secs, nanos) {
Ok(instant) => instant,
Err(error) => {
#[cfg(feature = "trace")]
crate::trace::debug!(error = ?error, "from_unix_parts_lossy: clamping a refused pair to the Unix epoch");
#[cfg(not(feature = "trace"))]
let _ = error;
UNIX_EPOCH
}
}
}
fn write_two_digits(out: &mut String, value: i64) {
push_digit(out, value.div_euclid(10));
push_digit(out, value.rem_euclid(10));
}
fn write_four_digits(out: &mut String, value: i64) {
write_two_digits(out, value.div_euclid(100));
write_two_digits(out, value.rem_euclid(100));
}
fn extract_fraction_digits(nanos: i64, buf: &mut [u8; 9]) -> usize {
let mut remainder = nanos;
for index in (0..9).rev() {
buf[index] = digit_byte(remainder.rem_euclid(10));
remainder = remainder.div_euclid(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: i64) {
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, year);
out.push('-');
write_two_digits(out, i64::from(month));
out.push('-');
write_two_digits(out, i64::from(day));
}
fn write_time_parts(out: &mut String, hour: i64, min: i64, sec: i64) {
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: i64) {
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 = secs.rem_euclid(SECONDS_PER_DAY);
let hour = rem.div_euclid(3_600);
let min = rem.rem_euclid(3_600).div_euclid(60);
let sec = rem.rem_euclid(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, i64::from(nanos));
Ok(out)
}