#[cfg(feature = "alloc")]
use alloc::alloc::{Layout, alloc, dealloc};
use core::cmp::Ordering;
use core::fmt::{self, Debug, Formatter};
use core::hash::{Hash, Hasher};
use crate::value::{TypeTag, Value};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DateTimeKind {
Offset {
offset_minutes: i16,
},
LocalDateTime,
LocalDate,
LocalTime,
}
#[repr(C, align(8))]
struct DateTimeHeader {
year: i32,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
_pad: [u8; 3],
nanos: u32,
kind: DateTimeKind,
}
#[repr(transparent)]
#[derive(Clone)]
pub struct VDateTime(pub(crate) Value);
impl VDateTime {
const fn layout() -> Layout {
Layout::new::<DateTimeHeader>()
}
#[cfg(feature = "alloc")]
fn alloc() -> *mut DateTimeHeader {
unsafe { alloc(Self::layout()).cast::<DateTimeHeader>() }
}
#[cfg(feature = "alloc")]
fn dealloc(ptr: *mut DateTimeHeader) {
unsafe {
dealloc(ptr.cast::<u8>(), Self::layout());
}
}
fn header(&self) -> &DateTimeHeader {
unsafe { &*(self.0.heap_ptr() as *const DateTimeHeader) }
}
#[allow(dead_code)]
fn header_mut(&mut self) -> &mut DateTimeHeader {
unsafe { &mut *(self.0.heap_ptr_mut() as *mut DateTimeHeader) }
}
#[cfg(feature = "alloc")]
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn new_offset(
year: i32,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
nanos: u32,
offset_minutes: i16,
) -> Self {
unsafe {
let ptr = Self::alloc();
(*ptr).year = year;
(*ptr).month = month;
(*ptr).day = day;
(*ptr).hour = hour;
(*ptr).minute = minute;
(*ptr).second = second;
(*ptr)._pad = [0; 3];
(*ptr).nanos = nanos;
(*ptr).kind = DateTimeKind::Offset { offset_minutes };
VDateTime(Value::new_ptr(ptr.cast(), TypeTag::DateTime))
}
}
#[cfg(feature = "alloc")]
#[must_use]
pub fn new_local_datetime(
year: i32,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
nanos: u32,
) -> Self {
unsafe {
let ptr = Self::alloc();
(*ptr).year = year;
(*ptr).month = month;
(*ptr).day = day;
(*ptr).hour = hour;
(*ptr).minute = minute;
(*ptr).second = second;
(*ptr)._pad = [0; 3];
(*ptr).nanos = nanos;
(*ptr).kind = DateTimeKind::LocalDateTime;
VDateTime(Value::new_ptr(ptr.cast(), TypeTag::DateTime))
}
}
#[cfg(feature = "alloc")]
#[must_use]
pub fn new_local_date(year: i32, month: u8, day: u8) -> Self {
unsafe {
let ptr = Self::alloc();
(*ptr).year = year;
(*ptr).month = month;
(*ptr).day = day;
(*ptr).hour = 0;
(*ptr).minute = 0;
(*ptr).second = 0;
(*ptr)._pad = [0; 3];
(*ptr).nanos = 0;
(*ptr).kind = DateTimeKind::LocalDate;
VDateTime(Value::new_ptr(ptr.cast(), TypeTag::DateTime))
}
}
#[cfg(feature = "alloc")]
#[must_use]
pub fn new_local_time(hour: u8, minute: u8, second: u8, nanos: u32) -> Self {
unsafe {
let ptr = Self::alloc();
(*ptr).year = 0;
(*ptr).month = 0;
(*ptr).day = 0;
(*ptr).hour = hour;
(*ptr).minute = minute;
(*ptr).second = second;
(*ptr)._pad = [0; 3];
(*ptr).nanos = nanos;
(*ptr).kind = DateTimeKind::LocalTime;
VDateTime(Value::new_ptr(ptr.cast(), TypeTag::DateTime))
}
}
#[must_use]
pub fn kind(&self) -> DateTimeKind {
self.header().kind
}
#[must_use]
pub fn year(&self) -> i32 {
self.header().year
}
#[must_use]
pub fn month(&self) -> u8 {
self.header().month
}
#[must_use]
pub fn day(&self) -> u8 {
self.header().day
}
#[must_use]
pub fn hour(&self) -> u8 {
self.header().hour
}
#[must_use]
pub fn minute(&self) -> u8 {
self.header().minute
}
#[must_use]
pub fn second(&self) -> u8 {
self.header().second
}
#[must_use]
pub fn nanos(&self) -> u32 {
self.header().nanos
}
#[must_use]
pub fn offset_minutes(&self) -> Option<i16> {
match self.kind() {
DateTimeKind::Offset { offset_minutes } => Some(offset_minutes),
_ => None,
}
}
#[must_use]
pub fn has_date(&self) -> bool {
!matches!(self.kind(), DateTimeKind::LocalTime)
}
#[must_use]
pub fn has_time(&self) -> bool {
!matches!(self.kind(), DateTimeKind::LocalDate)
}
#[must_use]
pub fn has_offset(&self) -> bool {
matches!(self.kind(), DateTimeKind::Offset { .. })
}
pub(crate) fn clone_impl(&self) -> Value {
#[cfg(feature = "alloc")]
{
let h = self.header();
match h.kind {
DateTimeKind::Offset { offset_minutes } => {
Self::new_offset(
h.year,
h.month,
h.day,
h.hour,
h.minute,
h.second,
h.nanos,
offset_minutes,
)
.0
}
DateTimeKind::LocalDateTime => {
Self::new_local_datetime(
h.year, h.month, h.day, h.hour, h.minute, h.second, h.nanos,
)
.0
}
DateTimeKind::LocalDate => Self::new_local_date(h.year, h.month, h.day).0,
DateTimeKind::LocalTime => {
Self::new_local_time(h.hour, h.minute, h.second, h.nanos).0
}
}
}
#[cfg(not(feature = "alloc"))]
{
panic!("cannot clone VDateTime without alloc feature")
}
}
pub(crate) fn drop_impl(&mut self) {
#[cfg(feature = "alloc")]
unsafe {
Self::dealloc(self.0.heap_ptr_mut().cast());
}
}
}
impl PartialEq for VDateTime {
fn eq(&self, other: &Self) -> bool {
let (h1, h2) = (self.header(), other.header());
h1.kind == h2.kind
&& h1.year == h2.year
&& h1.month == h2.month
&& h1.day == h2.day
&& h1.hour == h2.hour
&& h1.minute == h2.minute
&& h1.second == h2.second
&& h1.nanos == h2.nanos
}
}
impl Eq for VDateTime {}
impl PartialOrd for VDateTime {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
let (h1, h2) = (self.header(), other.header());
match (&h1.kind, &h2.kind) {
(
DateTimeKind::Offset { offset_minutes: o1 },
DateTimeKind::Offset { offset_minutes: o2 },
) => {
let to_comparable = |h: &DateTimeHeader, offset: i16| -> (i64, u32) {
let days = h.year as i64 * 366 + h.month as i64 * 31 + h.day as i64;
let secs = days * 86400
+ h.hour as i64 * 3600
+ h.minute as i64 * 60
+ h.second as i64
- offset as i64 * 60;
(secs, h.nanos)
};
let c1 = to_comparable(h1, *o1);
let c2 = to_comparable(h2, *o2);
c1.partial_cmp(&c2)
}
(DateTimeKind::LocalDateTime, DateTimeKind::LocalDateTime)
| (DateTimeKind::LocalDate, DateTimeKind::LocalDate) => {
(
h1.year, h1.month, h1.day, h1.hour, h1.minute, h1.second, h1.nanos,
)
.partial_cmp(&(
h2.year, h2.month, h2.day, h2.hour, h2.minute, h2.second, h2.nanos,
))
}
(DateTimeKind::LocalTime, DateTimeKind::LocalTime) => {
(h1.hour, h1.minute, h1.second, h1.nanos)
.partial_cmp(&(h2.hour, h2.minute, h2.second, h2.nanos))
}
_ => None, }
}
}
impl Hash for VDateTime {
fn hash<H: Hasher>(&self, state: &mut H) {
let h = self.header();
h.kind.hash(state);
h.year.hash(state);
h.month.hash(state);
h.day.hash(state);
h.hour.hash(state);
h.minute.hash(state);
h.second.hash(state);
h.nanos.hash(state);
}
}
impl Debug for VDateTime {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let h = self.header();
match h.kind {
DateTimeKind::Offset { offset_minutes } => {
write!(
f,
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
h.year, h.month, h.day, h.hour, h.minute, h.second
)?;
if h.nanos > 0 {
write!(f, ".{:09}", h.nanos)?;
}
if offset_minutes == 0 {
write!(f, "Z")
} else {
let sign = if offset_minutes >= 0 { '+' } else { '-' };
let abs = offset_minutes.abs();
write!(f, "{}{:02}:{:02}", sign, abs / 60, abs % 60)
}
}
DateTimeKind::LocalDateTime => {
write!(
f,
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
h.year, h.month, h.day, h.hour, h.minute, h.second
)?;
if h.nanos > 0 {
write!(f, ".{:09}", h.nanos)?;
}
Ok(())
}
DateTimeKind::LocalDate => {
write!(f, "{:04}-{:02}-{:02}", h.year, h.month, h.day)
}
DateTimeKind::LocalTime => {
write!(f, "{:02}:{:02}:{:02}", h.hour, h.minute, h.second)?;
if h.nanos > 0 {
write!(f, ".{:09}", h.nanos)?;
}
Ok(())
}
}
}
}
#[cfg(feature = "alloc")]
impl From<VDateTime> for Value {
fn from(dt: VDateTime) -> Self {
dt.0
}
}