use crate::format::*;
use crate::{EpochDays, ParseError, ParseResult, MILLIS_PER_DAY};
use std::fmt::{Debug, Display, Formatter};
use std::str::FromStr;
const OFFSET_BITS: u32 = 12;
const MILLI_BITS: u32 = 10;
const SECOND_BITS: u32 = 6;
const MINUTE_BITS: u32 = 6;
const HOUR_BITS: u32 = 5;
const DAY_BITS: u32 = 5;
const MONTH_BITS: u32 = 4;
const YEAR_BITS: u32 =
64 - (MONTH_BITS + DAY_BITS + HOUR_BITS + MINUTE_BITS + SECOND_BITS + MILLI_BITS + OFFSET_BITS);
const MIN_YEAR_INTERNAL: i32 = -(1 << (YEAR_BITS - 1));
const MAX_YEAR_INTERNAL: i32 = (1 << (YEAR_BITS - 1)) - 1;
const MIN_YEAR: i32 = -9999;
const MAX_YEAR: i32 = 9999;
const MIN_OFFSET_MINUTES_INTERNAL: i32 = -(1 << (OFFSET_BITS - 1));
const MAX_OFFSET_MINUTES_INTERNAL: i32 = (1 << (OFFSET_BITS - 1)) - 1;
const MAX_OFFSET_HOURS: i32 = 18;
const MIN_OFFSET_HOURS: i32 = -18;
const MIN_OFFSET_MINUTES: i32 = MIN_OFFSET_HOURS * 60;
const MAX_OFFSET_MINUTES: i32 = MAX_OFFSET_HOURS * 60;
#[allow(clippy::assertions_on_constants)]
const _: () = {
assert!(MIN_YEAR_INTERNAL < MIN_YEAR || MAX_YEAR_INTERNAL > MAX_YEAR);
assert!(
MIN_OFFSET_MINUTES_INTERNAL < MIN_OFFSET_MINUTES
|| MAX_OFFSET_MINUTES_INTERNAL > MAX_OFFSET_MINUTES
);
};
#[derive(PartialEq, Clone, Copy, Ord, PartialOrd, Eq)]
#[repr(transparent)]
pub struct PackedTimestamp {
value: u64,
}
impl PackedTimestamp {
#[inline]
#[allow(clippy::too_many_arguments)]
pub fn new_utc(
year: i32,
month: u32,
day: u32,
hour: u32,
minute: u32,
second: u32,
milli: u32,
) -> Self {
Self::new(year, month, day, hour, minute, second, milli, 0)
}
#[inline]
pub fn new_ymd_utc(year: i32, month: u32, day: u32) -> Self {
Self::new(year, month, day, 0, 0, 0, 0, 0)
}
#[inline]
#[allow(clippy::too_many_arguments)]
pub fn new(
year: i32,
month: u32,
day: u32,
hour: u32,
minute: u32,
second: u32,
milli: u32,
offset_minutes: i32,
) -> Self {
let value = ((((((((year as u64) << MONTH_BITS | month as u64) << DAY_BITS
| day as u64)
<< HOUR_BITS
| hour as u64)
<< MINUTE_BITS
| minute as u64)
<< SECOND_BITS
| second as u64)
<< MILLI_BITS
| milli as u64)
<< OFFSET_BITS)
| offset_minutes as u64;
Self { value }
}
#[inline]
pub fn from_value(value: u64) -> Self {
Self { value }
}
#[inline]
pub fn value(&self) -> u64 {
self.value
}
#[inline]
pub fn from_timestamp_millis(ts: i64) -> Self {
let epoch_days = ts.div_euclid(MILLIS_PER_DAY) as i32;
let milli_of_day = ts.rem_euclid(MILLIS_PER_DAY) as u32;
let millisecond = milli_of_day % 1000;
let second_of_day = milli_of_day / 1000;
let second = second_of_day % 60;
let minute_of_day = second_of_day / 60;
let minute = minute_of_day % 60;
let hour_of_day = minute_of_day / 60;
let (year, month, day) = EpochDays::new(epoch_days as i32).to_ymd();
Self::new_utc(
year,
month as u32,
day as u32,
hour_of_day,
minute,
second,
millisecond,
)
}
#[inline]
pub fn to_timestamp_millis(&self) -> i64 {
let date_part =
EpochDays::from_ymd(self.year() as i32, self.month() as i32, self.day() as i32)
.to_timestamp_millis();
let h = self.hour() as i64;
let m = self.minute() as i64;
let s = self.second() as i64;
let o = self.offset_minutes() as i64;
let seconds = h * 60 * 60 + m * 60 + s - o * 60;
let millis = self.millisecond() as i64;
let time_part = seconds * 1000 + millis;
date_part + time_part
}
pub fn from_rfc3339_bytes(input: &[u8]) -> ParseResult<Self> {
#[cfg(target_feature = "sse4.1")]
{
let ts = crate::parse::parse_simd(input)?;
Ok(ts.to_packed())
}
#[cfg(not(target_feature = "sse4.1"))]
{
let ts = crate::parse::parse_scalar(input)?;
Ok(ts.to_packed())
}
}
pub fn from_rfc3339_str(input: &str) -> ParseResult<Self> {
Self::from_rfc3339_bytes(input.as_bytes())
}
#[inline]
pub fn year(&self) -> u32 {
(self.value
>> (MONTH_BITS
+ DAY_BITS
+ HOUR_BITS
+ MINUTE_BITS
+ SECOND_BITS
+ MILLI_BITS
+ OFFSET_BITS)) as u32
}
#[inline]
pub fn month(&self) -> u32 {
((self.value
>> (DAY_BITS + HOUR_BITS + MINUTE_BITS + SECOND_BITS + MILLI_BITS + OFFSET_BITS))
& ((1 << MONTH_BITS) - 1)) as u32
}
#[inline]
pub fn day(&self) -> u32 {
((self.value >> (HOUR_BITS + MINUTE_BITS + SECOND_BITS + MILLI_BITS + OFFSET_BITS))
& ((1 << DAY_BITS) - 1)) as u32
}
#[inline]
pub fn hour(&self) -> u32 {
((self.value >> (MINUTE_BITS + SECOND_BITS + MILLI_BITS + OFFSET_BITS))
& ((1 << HOUR_BITS) - 1)) as u32
}
#[inline]
pub fn minute(&self) -> u32 {
((self.value >> (SECOND_BITS + MILLI_BITS + OFFSET_BITS)) & ((1 << MINUTE_BITS) - 1)) as u32
}
#[inline]
pub fn second(&self) -> u32 {
((self.value >> (MILLI_BITS + OFFSET_BITS)) & ((1 << SECOND_BITS) - 1)) as u32
}
#[inline]
pub fn millisecond(&self) -> u32 {
((self.value >> (OFFSET_BITS)) & ((1 << MILLI_BITS) - 1)) as u32
}
#[inline]
pub fn offset_minutes(&self) -> i32 {
(self.value & ((1 << OFFSET_BITS) - 1)) as i32
}
#[inline]
pub fn write_rfc3339_bytes<W: std::io::Write>(&self, mut writer: W) -> std::io::Result<()> {
let buffer = self.to_rfc3339_bytes();
writer.write_all(&buffer)
}
#[inline]
pub fn write_rfc3339_str<W: std::fmt::Write>(&self, mut writer: W) -> std::fmt::Result {
let buffer = self.to_rfc3339_bytes();
#[cfg(not(debug_assertions))]
{
writer.write_str(unsafe { std::str::from_utf8_unchecked(&buffer) })
}
#[cfg(debug_assertions)]
{
writer.write_str(std::str::from_utf8(&buffer).expect("utf8 string"))
}
}
#[inline]
pub fn to_rfc3339_bytes(&self) -> [u8; 24] {
let mut buffer = [0_u8; 24];
#[cfg(target_feature = "sse4.1")]
unsafe {
format_simd_mul_to_slice(
&mut buffer,
self.year(),
self.month(),
self.day(),
self.hour(),
self.minute(),
self.second(),
self.millisecond(),
);
}
#[cfg(not(target_feature = "sse4.1"))]
{
format_scalar_to_slice(
&mut buffer,
self.year(),
self.month(),
self.day(),
self.hour(),
self.minute(),
self.second(),
self.millisecond(),
);
}
buffer
}
#[inline]
pub fn to_rfc3339_string(&self) -> String {
let buffer = self.to_rfc3339_bytes();
#[cfg(not(debug_assertions))]
{
unsafe { std::str::from_utf8_unchecked(&buffer).to_string() }
}
#[cfg(debug_assertions)]
{
std::str::from_utf8(&buffer)
.expect("utf8 string")
.to_string()
}
}
}
impl Display for PackedTimestamp {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.write_rfc3339_str(f)
}
}
impl Debug for PackedTimestamp {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
#[cfg(not(debug_assertions))]
{
self.write_rfc3339_str(f)
}
#[cfg(debug_assertions)]
{
f.write_fmt(format_args!(
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z",
self.year(),
self.month(),
self.day(),
self.hour(),
self.minute(),
self.second(),
self.millisecond()
))
}
}
}
impl From<EpochDays> for PackedTimestamp {
fn from(epoch_days: EpochDays) -> Self {
let (year, month, day) = epoch_days.to_ymd();
PackedTimestamp::new_ymd_utc(year, month as _, day as _)
}
}
impl TryFrom<&str> for PackedTimestamp {
type Error = ParseError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
PackedTimestamp::from_rfc3339_str(s)
}
}
impl FromStr for PackedTimestamp {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
PackedTimestamp::from_rfc3339_str(s)
}
}
#[cfg(test)]
pub mod tests {
use crate::{PackedTimestamp, ParseError};
#[test]
fn test_format() {
assert_eq!(
PackedTimestamp::new_utc(2022, 8, 21, 17, 30, 15, 0).to_rfc3339_string(),
"2022-08-21T17:30:15.000Z".to_owned()
);
assert_eq!(
PackedTimestamp::new_utc(2022, 8, 21, 17, 30, 15, 100).to_rfc3339_string(),
"2022-08-21T17:30:15.100Z".to_owned()
);
assert_eq!(
PackedTimestamp::new_utc(2022, 8, 21, 17, 30, 15, 123).to_rfc3339_string(),
"2022-08-21T17:30:15.123Z".to_owned()
);
assert_eq!(
PackedTimestamp::new_utc(2022, 8, 21, 17, 30, 15, 250).to_rfc3339_string(),
"2022-08-21T17:30:15.250Z".to_owned()
);
}
#[test]
fn test_parse() {
assert_eq!(
"2022-08-21T17:30:15.250Z".parse(),
Ok(PackedTimestamp::new_utc(2022, 8, 21, 17, 30, 15, 250))
);
assert_eq!(
"2022-08-21T17:30:15.25Z".parse(),
Ok(PackedTimestamp::new_utc(2022, 8, 21, 17, 30, 15, 250))
);
assert_eq!(
"2022-08-21 17:30:15.1Z".parse(),
Ok(PackedTimestamp::new_utc(2022, 8, 21, 17, 30, 15, 100))
);
assert_eq!(
"2022-08-21 17:30:15Z".parse(),
Ok(PackedTimestamp::new_utc(2022, 8, 21, 17, 30, 15, 0))
);
assert_eq!(
"2022-08-21T17:30:15.250+02:00".parse(),
Ok(PackedTimestamp::new(2022, 8, 21, 17, 30, 15, 250, 120))
);
}
#[test]
fn test_parse_error() {
assert_eq!(
PackedTimestamp::try_from("2022-08-21 FOO"),
Err(ParseError::InvalidLen(14))
);
assert_eq!(
PackedTimestamp::try_from("2022-08-21 00:00"),
Err(ParseError::InvalidLen(16))
);
assert_eq!(
PackedTimestamp::try_from("2022-08-21 XX:YY::ZZZ"),
Err(ParseError::InvalidChar(11))
);
}
#[test]
fn test_packed() {
let ts = PackedTimestamp::new_utc(2020, 9, 10, 17, 30, 15, 123);
assert_eq!(2020, ts.year());
assert_eq!(9, ts.month());
assert_eq!(10, ts.day());
assert_eq!(17, ts.hour());
assert_eq!(30, ts.minute());
assert_eq!(15, ts.second());
assert_eq!(123, ts.millisecond());
}
#[test]
fn test_from_timestamp_millis() {
assert_eq!(
PackedTimestamp::from_timestamp_millis(0),
PackedTimestamp::new_utc(1970, 1, 1, 0, 0, 0, 0)
);
assert_eq!(
PackedTimestamp::from_timestamp_millis(1000),
PackedTimestamp::new_utc(1970, 1, 1, 0, 0, 1, 0)
);
assert_eq!(
PackedTimestamp::from_timestamp_millis(24 * 60 * 60 * 1000),
PackedTimestamp::new_utc(1970, 1, 2, 0, 0, 0, 0)
);
assert_eq!(
PackedTimestamp::from_timestamp_millis(-1),
PackedTimestamp::new_utc(1969, 12, 31, 23, 59, 59, 999)
);
assert_eq!(
PackedTimestamp::from_timestamp_millis(-1000),
PackedTimestamp::new_utc(1969, 12, 31, 23, 59, 59, 0)
);
assert_eq!(
PackedTimestamp::from_timestamp_millis(-24 * 60 * 60 * 1000),
PackedTimestamp::new_utc(1969, 12, 31, 0, 0, 0, 0)
);
}
}