use core::{
cmp, fmt,
ops::{Add, AddAssign, Sub, SubAssign},
str::{self, FromStr},
time::Duration,
};
use crate::{
buf::Buffer,
value::{FromValue, ToValue, Value},
};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Timestamp(Duration);
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Parts {
pub years: u16,
pub months: u8,
pub days: u8,
pub hours: u8,
pub minutes: u8,
pub seconds: u8,
pub nanos: u32,
}
const LEAPOCH_SECS: u64 = 946_684_800 + 86400 * (31 + 29);
const DAYS_PER_400Y: i32 = 365 * 400 + 97;
const DAYS_PER_100Y: i32 = 365 * 100 + 24;
const DAYS_PER_4Y: i32 = 365 * 4 + 1;
const DAYS_IN_MONTH: [u8; 12] = [31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29];
const MIN: Duration = Duration::new(0, 0);
const MAX: Duration = Duration::new(253402300799, 999999999);
impl Timestamp {
pub const MIN: Self = Timestamp(MIN);
pub const MAX: Self = Timestamp(MAX);
pub fn from_unix(unix_time: Duration) -> Option<Self> {
if unix_time >= MIN && unix_time <= MAX {
Some(Timestamp(unix_time))
} else {
None
}
}
pub fn to_unix(&self) -> Duration {
self.0
}
pub fn try_from_str(ts: &str) -> Result<Self, ParseTimestampError> {
ts.parse()
}
pub fn parse(ts: impl fmt::Display) -> Result<Self, ParseTimestampError> {
let mut buf = Buffer::<30>::new();
Self::try_from_str(
str::from_utf8(buf.buffer(ts).ok_or_else(|| ParseTimestampError {})?)
.map_err(|_| ParseTimestampError {})?,
)
}
pub fn duration_since(self, earlier: Self) -> Option<Duration> {
self.0.checked_sub(earlier.0)
}
#[cfg(feature = "std")]
pub fn from_system_time(ts: std::time::SystemTime) -> Option<Self> {
Self::from_unix(ts.duration_since(std::time::SystemTime::UNIX_EPOCH).ok()?)
}
#[cfg(feature = "std")]
pub fn to_system_time(&self) -> std::time::SystemTime {
std::time::SystemTime::UNIX_EPOCH + self.0
}
pub fn from_parts(parts: Parts) -> Option<Self> {
let is_leap;
let start_of_year;
let year = (parts.years as i64) - 1900;
if year as u64 <= 138 {
let mut leaps: i64 = (year - 68) >> 2;
if (year - 68).trailing_zeros() >= 2 {
leaps -= 1;
is_leap = true;
} else {
is_leap = false;
}
start_of_year = i128::from(31_536_000 * (year - 70) + 86400 * leaps);
} else {
let centuries: i64;
let mut leaps: i64;
let mut cycles: i64 = (year - 100) / 400;
let mut rem: i64 = (year - 100) % 400;
if rem < 0 {
cycles -= 1;
rem += 400
}
if rem == 0 {
is_leap = true;
centuries = 0;
leaps = 0;
} else {
if rem >= 200 {
if rem >= 300 {
centuries = 3;
rem -= 300;
} else {
centuries = 2;
rem -= 200;
}
} else if rem >= 100 {
centuries = 1;
rem -= 100;
} else {
centuries = 0;
}
if rem == 0 {
is_leap = false;
leaps = 0;
} else {
leaps = rem / 4;
rem %= 4;
is_leap = rem == 0;
}
}
leaps += 97 * cycles + 24 * centuries - i64::from(is_leap);
start_of_year = i128::from((year - 100) * 31_536_000)
+ i128::from(leaps * 86400 + 946_684_800 + 86400);
}
let seconds_within_month = 86400 * u32::from(parts.days - 1)
+ 3600 * u32::from(parts.hours)
+ 60 * u32::from(parts.minutes)
+ u32::from(parts.seconds);
let mut seconds_within_year = [
0, 31 * 86400, 59 * 86400, 90 * 86400, 120 * 86400, 151 * 86400, 181 * 86400, 212 * 86400, 243 * 86400, 273 * 86400, 304 * 86400, 334 * 86400, ][usize::from(parts.months - 1) % 12]
+ seconds_within_month;
if is_leap && parts.months > 2 {
seconds_within_year += 86400
}
Timestamp::from_unix(Duration::new(
(start_of_year + i128::from(seconds_within_year))
.try_into()
.ok()?,
parts.nanos,
))
}
pub fn to_parts(&self) -> Parts {
let dur = self.0;
let secs = dur.as_secs();
let nanos = dur.subsec_nanos();
let mut days = ((secs as i64 / 86_400) - (LEAPOCH_SECS as i64 / 86_400)) as i64;
let mut remsecs = (secs % 86_400) as i32;
if remsecs < 0i32 {
remsecs += 86_400;
days -= 1
}
let mut qc_cycles: i32 = (days / (DAYS_PER_400Y as i64)) as i32;
let mut remdays: i32 = (days % (DAYS_PER_400Y as i64)) as i32;
if remdays < 0 {
remdays += DAYS_PER_400Y;
qc_cycles -= 1;
}
let mut c_cycles: i32 = remdays / DAYS_PER_100Y;
if c_cycles == 4 {
c_cycles -= 1;
}
remdays -= c_cycles * DAYS_PER_100Y;
let mut q_cycles: i32 = remdays / DAYS_PER_4Y;
if q_cycles == 25 {
q_cycles -= 1;
}
remdays -= q_cycles * DAYS_PER_4Y;
let mut remyears: i32 = remdays / 365;
if remyears == 4 {
remyears -= 1;
}
remdays -= remyears * 365;
let mut years: i64 = i64::from(remyears)
+ 4 * i64::from(q_cycles)
+ 100 * i64::from(c_cycles)
+ 400 * i64::from(qc_cycles);
let mut months: i32 = 0;
while i32::from(DAYS_IN_MONTH[months as usize]) <= remdays {
remdays -= i32::from(DAYS_IN_MONTH[months as usize]);
months += 1
}
if months >= 10 {
months -= 12;
years += 1;
}
let years = (years + 2000) as u16;
let months = (months + 3) as u8;
let days = (remdays + 1) as u8;
let hours = (remsecs / 3600) as u8;
let minutes = (remsecs / 60 % 60) as u8;
let seconds = (remsecs % 60) as u8;
Parts {
years,
months,
days,
hours,
minutes,
seconds,
nanos,
}
}
#[must_use = "the result of addition is returned without modifying the original"]
pub fn checked_add(&self, rhs: Duration) -> Option<Self> {
Timestamp::from_unix(self.to_unix().checked_add(rhs)?)
}
#[must_use = "the result of subtraction is returned without modifying the original"]
pub fn checked_sub(&self, rhs: Duration) -> Option<Self> {
Timestamp::from_unix(self.to_unix().checked_sub(rhs)?)
}
pub fn checked_duration_since(&self, earlier: Timestamp) -> Option<Duration> {
self.to_unix().checked_sub(earlier.to_unix())
}
}
impl Add<Duration> for Timestamp {
type Output = Timestamp;
fn add(self, rhs: Duration) -> Self::Output {
self.checked_add(rhs).expect("overflow adding to timestamp")
}
}
impl AddAssign<Duration> for Timestamp {
fn add_assign(&mut self, rhs: Duration) {
*self = *self + rhs;
}
}
impl Sub<Duration> for Timestamp {
type Output = Timestamp;
fn sub(self, rhs: Duration) -> Self::Output {
self.checked_sub(rhs)
.expect("overflow subtracting from timestamp")
}
}
impl SubAssign<Duration> for Timestamp {
fn sub_assign(&mut self, rhs: Duration) {
*self = *self - rhs;
}
}
impl Sub<Timestamp> for Timestamp {
type Output = Duration;
fn sub(self, earlier: Timestamp) -> Self::Output {
self.checked_duration_since(earlier)
.expect("overflow subtracting from timestamp")
}
}
impl fmt::Debug for Timestamp {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use fmt::Write as _;
f.write_char('"')?;
fmt_rfc3339(*self, f)?;
f.write_char('"')
}
}
impl fmt::Display for Timestamp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt_rfc3339(*self, f)
}
}
impl FromStr for Timestamp {
type Err = ParseTimestampError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
parse_rfc3339(s)
}
}
impl ToValue for Timestamp {
fn to_value(&self) -> Value<'_> {
Value::capture_display(self)
}
}
impl<'v> FromValue<'v> for Timestamp {
fn from_value(value: Value<'v>) -> Option<Self> {
value
.downcast_ref::<Timestamp>()
.copied()
.or_else(|| value.parse())
}
}
impl<'a> PartialEq<&'a Timestamp> for Timestamp {
fn eq(&self, other: &&'a Timestamp) -> bool {
self == *other
}
}
impl<'a> PartialEq<Timestamp> for &'a Timestamp {
fn eq(&self, other: &Timestamp) -> bool {
*self == other
}
}
#[derive(Debug)]
pub struct ParseTimestampError {}
impl fmt::Display for ParseTimestampError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "the input was not a valid timestamp")
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseTimestampError {}
fn parse_rfc3339(fmt: &str) -> Result<Timestamp, ParseTimestampError> {
if fmt.len() > 30 || fmt.len() < 19 {
return Err(ParseTimestampError {});
}
if *fmt.as_bytes().last().unwrap() != b'Z' {
return Err(ParseTimestampError {});
}
let years = u16::from_str_radix(&fmt[0..4], 10).map_err(|_| ParseTimestampError {})?;
let months = u8::from_str_radix(&fmt[5..7], 10).map_err(|_| ParseTimestampError {})?;
let days = u8::from_str_radix(&fmt[8..10], 10).map_err(|_| ParseTimestampError {})?;
let hours = u8::from_str_radix(&fmt[11..13], 10).map_err(|_| ParseTimestampError {})?;
let minutes = u8::from_str_radix(&fmt[14..16], 10).map_err(|_| ParseTimestampError {})?;
let seconds = u8::from_str_radix(&fmt[17..19], 10).map_err(|_| ParseTimestampError {})?;
let nanos = if fmt.len() > 19 {
let subsecond = &fmt[20..fmt.len() - 1];
u32::from_str_radix(subsecond, 10).unwrap() * 10u32.pow(9 - subsecond.len() as u32)
} else {
0
};
Timestamp::from_parts(Parts {
years,
months,
days,
hours,
minutes,
seconds,
nanos,
})
.ok_or_else(|| ParseTimestampError {})
}
fn fmt_rfc3339(ts: Timestamp, f: &mut fmt::Formatter) -> fmt::Result {
let Parts {
years,
months,
days,
hours,
minutes,
seconds,
nanos: subsecond_nanos,
} = ts.to_parts();
const BUF_INIT: [u8; 30] = *b"0000-00-00T00:00:00.000000000Z";
let mut buf: [u8; 30] = BUF_INIT;
buf[0] = b'0' + (years / 1000) as u8;
buf[1] = b'0' + (years / 100 % 10) as u8;
buf[2] = b'0' + (years / 10 % 10) as u8;
buf[3] = b'0' + (years % 10) as u8;
buf[5] = b'0' + (months / 10) as u8;
buf[6] = b'0' + (months % 10) as u8;
buf[8] = b'0' + (days / 10) as u8;
buf[9] = b'0' + (days % 10) as u8;
buf[11] = b'0' + (hours / 10) as u8;
buf[12] = b'0' + (hours % 10) as u8;
buf[14] = b'0' + (minutes / 10) as u8;
buf[15] = b'0' + (minutes % 10) as u8;
buf[17] = b'0' + (seconds / 10) as u8;
buf[18] = b'0' + (seconds % 10) as u8;
let i = match f.precision() {
Some(0) => 19,
precision => {
let mut i = 20;
let mut divisor = 100_000_000;
let end = i + cmp::min(9, precision.unwrap_or(9));
while i < end {
buf[i] = b'0' + (subsecond_nanos / divisor % 10) as u8;
i += 1;
divisor /= 10;
}
i
}
};
buf[i] = b'Z';
f.write_str(str::from_utf8(&buf[..=i]).expect("Conversion to utf8 failed"))
}
#[cfg(feature = "sval")]
impl sval::Value for Timestamp {
fn stream<'sval, S: sval::Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> sval::Result {
sval::stream_display(stream, self)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Timestamp {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrip() {
let ts = Timestamp::from_unix(Duration::new(1691961703, 17532)).unwrap();
let fmt = ts.to_string();
for parsed in [
Timestamp::try_from_str(&fmt),
Timestamp::parse(&fmt),
fmt.parse(),
] {
let parsed = parsed.unwrap();
assert_eq!(ts, parsed, "{}", fmt);
}
}
#[test]
fn parse_invalid() {
for case in [
"",
"0",
"2024-01-01T00:00:00.00000000000000000000000000Z",
"2024-01-01T00:00:00.000+10",
"Thursday, September 12, 2024",
] {
assert!(Timestamp::try_from_str(case).is_err());
assert!(Timestamp::parse(case).is_err());
}
}
#[test]
fn parts_max() {
let ts = Timestamp::from_parts(Parts {
years: 9999,
months: 12,
days: 31,
hours: 23,
minutes: 59,
seconds: 59,
nanos: 999999999,
})
.unwrap();
assert_eq!(ts.to_unix(), MAX);
}
#[test]
fn parts_min() {
let ts = Timestamp::from_parts(Parts {
years: 1970,
months: 1,
days: 1,
hours: 0,
minutes: 0,
seconds: 0,
nanos: 0,
})
.unwrap();
assert_eq!(ts.to_unix(), MIN);
}
#[test]
fn parts_overflow() {
let ts = Timestamp::from_parts(Parts {
years: 2000,
months: 13,
days: 32,
hours: 25,
minutes: 61,
seconds: 61,
nanos: 1000000000,
})
.unwrap();
let expected = Timestamp::from_parts(Parts {
years: 2000,
months: 13,
days: 32,
hours: 25,
minutes: 61,
seconds: 62,
nanos: 0,
})
.unwrap();
assert_eq!(expected, ts);
}
#[test]
fn add() {
for (case, add, expected) in [
(
Timestamp::MIN,
Duration::from_nanos(1),
Some(Timestamp::from_unix(Duration::from_nanos(1)).unwrap()),
),
(Timestamp::MAX, Duration::from_nanos(1), None),
(Timestamp::MAX, Duration::MAX, None),
] {
assert_eq!(expected, case.checked_add(add));
}
}
#[test]
fn sub() {
for (case, sub, expected) in [
(
Timestamp::MAX,
Duration::from_nanos(1),
Some(Timestamp::from_unix(MAX - Duration::from_nanos(1)).unwrap()),
),
(Timestamp::MIN, Duration::from_nanos(1), None),
(Timestamp::MIN, Duration::MAX, None),
] {
assert_eq!(expected, case.checked_sub(sub));
}
}
#[test]
fn sub_timestamp() {
for (case, earlier, expected) in [
(Timestamp::MIN, Timestamp::MIN, Some(Duration::from_secs(0))),
(
Timestamp::from_unix(Duration::from_secs(10)).unwrap(),
Timestamp::from_unix(Duration::from_secs(0)).unwrap(),
Some(Duration::from_secs(10)),
),
(
Timestamp::from_unix(Duration::from_secs(0)).unwrap(),
Timestamp::from_unix(Duration::from_secs(10)).unwrap(),
None,
),
(Timestamp::MAX, Timestamp::MIN, Some(MAX)),
] {
assert_eq!(expected, case.checked_duration_since(earlier));
}
}
#[test]
#[cfg(all(
feature = "std",
not(all(
target_arch = "wasm32",
target_vendor = "unknown",
target_os = "unknown"
))
))]
fn to_from_system_time() {
let ts_sys = std::time::SystemTime::UNIX_EPOCH + Duration::from_secs(30);
let value = Timestamp::from_system_time(ts_sys).unwrap();
assert_eq!(ts_sys, value.to_system_time());
}
#[test]
fn to_from_value() {
for case in [
Timestamp::MIN,
Timestamp::MAX,
Timestamp::from_unix(Duration::from_secs(1)).unwrap(),
] {
let value = case.to_value();
assert_eq!(case, value.cast::<Timestamp>().unwrap());
}
for (case, expected) in [
(
Value::from("2024-01-01T00:13:00.000Z"),
Some(
Timestamp::from_parts(Parts {
years: 2024,
months: 01,
days: 01,
hours: 00,
minutes: 13,
seconds: 00,
nanos: 000,
})
.unwrap(),
),
),
(Value::from(""), None),
(Value::from("12024-01-01T00:13:00.000Z"), None),
(Value::from("2024-01-01T00:13:00.000+10"), None),
] {
assert_eq!(expected, case.cast::<Timestamp>());
}
}
#[cfg(feature = "sval")]
#[test]
fn stream() {
sval_test::assert_tokens(
&Timestamp::try_from_str("2024-01-01T00:13:00.000Z").unwrap(),
&[
sval_test::Token::TextBegin(None),
sval_test::Token::TextFragmentComputed("2024-01-01T00:13:00.000000000Z".to_owned()),
sval_test::Token::TextEnd,
],
);
}
#[cfg(feature = "serde")]
#[test]
fn serialize() {
serde_test::assert_ser_tokens(
&Timestamp::try_from_str("2024-01-01T00:13:00.000Z").unwrap(),
&[serde_test::Token::Str("2024-01-01T00:13:00.000000000Z")],
);
}
}