1#[cfg(feature = "alloc")]
10use alloc::alloc::{Layout, alloc, dealloc};
11use core::cmp::Ordering;
12use core::fmt::{self, Debug, Formatter};
13use core::hash::{Hash, Hasher};
14
15use crate::value::{TypeTag, Value};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19#[non_exhaustive]
20pub enum DateTimeKind {
21 Offset {
24 offset_minutes: i16,
26 },
27
28 LocalDateTime,
31
32 LocalDate,
35
36 LocalTime,
39}
40
41#[repr(C, align(8))]
43struct DateTimeHeader {
44 year: i32,
46 month: u8,
48 day: u8,
50 hour: u8,
52 minute: u8,
54 second: u8,
56 _pad: [u8; 3],
58 nanos: u32,
60 kind: DateTimeKind,
62}
63
64#[repr(transparent)]
69#[derive(Clone)]
70pub struct VDateTime(pub(crate) Value);
71
72impl VDateTime {
73 const fn layout() -> Layout {
74 Layout::new::<DateTimeHeader>()
75 }
76
77 #[cfg(feature = "alloc")]
78 fn alloc() -> *mut DateTimeHeader {
79 unsafe { alloc(Self::layout()).cast::<DateTimeHeader>() }
80 }
81
82 #[cfg(feature = "alloc")]
83 fn dealloc(ptr: *mut DateTimeHeader) {
84 unsafe {
85 dealloc(ptr.cast::<u8>(), Self::layout());
86 }
87 }
88
89 fn header(&self) -> &DateTimeHeader {
90 unsafe { &*(self.0.heap_ptr() as *const DateTimeHeader) }
91 }
92
93 #[allow(dead_code)]
94 fn header_mut(&mut self) -> &mut DateTimeHeader {
95 unsafe { &mut *(self.0.heap_ptr_mut() as *mut DateTimeHeader) }
96 }
97
98 #[cfg(feature = "alloc")]
110 #[must_use]
111 #[allow(clippy::too_many_arguments)]
112 pub fn new_offset(
113 year: i32,
114 month: u8,
115 day: u8,
116 hour: u8,
117 minute: u8,
118 second: u8,
119 nanos: u32,
120 offset_minutes: i16,
121 ) -> Self {
122 unsafe {
123 let ptr = Self::alloc();
124 (*ptr).year = year;
125 (*ptr).month = month;
126 (*ptr).day = day;
127 (*ptr).hour = hour;
128 (*ptr).minute = minute;
129 (*ptr).second = second;
130 (*ptr)._pad = [0; 3];
131 (*ptr).nanos = nanos;
132 (*ptr).kind = DateTimeKind::Offset { offset_minutes };
133 VDateTime(Value::new_ptr(ptr.cast(), TypeTag::DateTime))
134 }
135 }
136
137 #[cfg(feature = "alloc")]
139 #[must_use]
140 pub fn new_local_datetime(
141 year: i32,
142 month: u8,
143 day: u8,
144 hour: u8,
145 minute: u8,
146 second: u8,
147 nanos: u32,
148 ) -> Self {
149 unsafe {
150 let ptr = Self::alloc();
151 (*ptr).year = year;
152 (*ptr).month = month;
153 (*ptr).day = day;
154 (*ptr).hour = hour;
155 (*ptr).minute = minute;
156 (*ptr).second = second;
157 (*ptr)._pad = [0; 3];
158 (*ptr).nanos = nanos;
159 (*ptr).kind = DateTimeKind::LocalDateTime;
160 VDateTime(Value::new_ptr(ptr.cast(), TypeTag::DateTime))
161 }
162 }
163
164 #[cfg(feature = "alloc")]
166 #[must_use]
167 pub fn new_local_date(year: i32, month: u8, day: u8) -> Self {
168 unsafe {
169 let ptr = Self::alloc();
170 (*ptr).year = year;
171 (*ptr).month = month;
172 (*ptr).day = day;
173 (*ptr).hour = 0;
174 (*ptr).minute = 0;
175 (*ptr).second = 0;
176 (*ptr)._pad = [0; 3];
177 (*ptr).nanos = 0;
178 (*ptr).kind = DateTimeKind::LocalDate;
179 VDateTime(Value::new_ptr(ptr.cast(), TypeTag::DateTime))
180 }
181 }
182
183 #[cfg(feature = "alloc")]
185 #[must_use]
186 pub fn new_local_time(hour: u8, minute: u8, second: u8, nanos: u32) -> Self {
187 unsafe {
188 let ptr = Self::alloc();
189 (*ptr).year = 0;
190 (*ptr).month = 0;
191 (*ptr).day = 0;
192 (*ptr).hour = hour;
193 (*ptr).minute = minute;
194 (*ptr).second = second;
195 (*ptr)._pad = [0; 3];
196 (*ptr).nanos = nanos;
197 (*ptr).kind = DateTimeKind::LocalTime;
198 VDateTime(Value::new_ptr(ptr.cast(), TypeTag::DateTime))
199 }
200 }
201
202 #[must_use]
204 pub fn kind(&self) -> DateTimeKind {
205 self.header().kind
206 }
207
208 #[must_use]
210 pub fn year(&self) -> i32 {
211 self.header().year
212 }
213
214 #[must_use]
216 pub fn month(&self) -> u8 {
217 self.header().month
218 }
219
220 #[must_use]
222 pub fn day(&self) -> u8 {
223 self.header().day
224 }
225
226 #[must_use]
228 pub fn hour(&self) -> u8 {
229 self.header().hour
230 }
231
232 #[must_use]
234 pub fn minute(&self) -> u8 {
235 self.header().minute
236 }
237
238 #[must_use]
240 pub fn second(&self) -> u8 {
241 self.header().second
242 }
243
244 #[must_use]
246 pub fn nanos(&self) -> u32 {
247 self.header().nanos
248 }
249
250 #[must_use]
252 pub fn offset_minutes(&self) -> Option<i16> {
253 match self.kind() {
254 DateTimeKind::Offset { offset_minutes } => Some(offset_minutes),
255 _ => None,
256 }
257 }
258
259 #[must_use]
261 pub fn has_date(&self) -> bool {
262 !matches!(self.kind(), DateTimeKind::LocalTime)
263 }
264
265 #[must_use]
267 pub fn has_time(&self) -> bool {
268 !matches!(self.kind(), DateTimeKind::LocalDate)
269 }
270
271 #[must_use]
273 pub fn has_offset(&self) -> bool {
274 matches!(self.kind(), DateTimeKind::Offset { .. })
275 }
276
277 pub(crate) fn clone_impl(&self) -> Value {
280 #[cfg(feature = "alloc")]
281 {
282 let h = self.header();
283 match h.kind {
284 DateTimeKind::Offset { offset_minutes } => {
285 Self::new_offset(
286 h.year,
287 h.month,
288 h.day,
289 h.hour,
290 h.minute,
291 h.second,
292 h.nanos,
293 offset_minutes,
294 )
295 .0
296 }
297 DateTimeKind::LocalDateTime => {
298 Self::new_local_datetime(
299 h.year, h.month, h.day, h.hour, h.minute, h.second, h.nanos,
300 )
301 .0
302 }
303 DateTimeKind::LocalDate => Self::new_local_date(h.year, h.month, h.day).0,
304 DateTimeKind::LocalTime => {
305 Self::new_local_time(h.hour, h.minute, h.second, h.nanos).0
306 }
307 }
308 }
309 #[cfg(not(feature = "alloc"))]
310 {
311 panic!("cannot clone VDateTime without alloc feature")
312 }
313 }
314
315 pub(crate) fn drop_impl(&mut self) {
316 #[cfg(feature = "alloc")]
317 unsafe {
318 Self::dealloc(self.0.heap_ptr_mut().cast());
319 }
320 }
321}
322
323impl PartialEq for VDateTime {
326 fn eq(&self, other: &Self) -> bool {
327 let (h1, h2) = (self.header(), other.header());
328 h1.kind == h2.kind
329 && h1.year == h2.year
330 && h1.month == h2.month
331 && h1.day == h2.day
332 && h1.hour == h2.hour
333 && h1.minute == h2.minute
334 && h1.second == h2.second
335 && h1.nanos == h2.nanos
336 }
337}
338
339impl Eq for VDateTime {}
340
341impl PartialOrd for VDateTime {
344 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
345 let (h1, h2) = (self.header(), other.header());
346
347 match (&h1.kind, &h2.kind) {
349 (
350 DateTimeKind::Offset { offset_minutes: o1 },
351 DateTimeKind::Offset { offset_minutes: o2 },
352 ) => {
353 let to_comparable = |h: &DateTimeHeader, offset: i16| -> (i64, u32) {
356 let days = h.year as i64 * 366 + h.month as i64 * 31 + h.day as i64;
357 let secs = days * 86400
358 + h.hour as i64 * 3600
359 + h.minute as i64 * 60
360 + h.second as i64
361 - offset as i64 * 60;
362 (secs, h.nanos)
363 };
364 let c1 = to_comparable(h1, *o1);
365 let c2 = to_comparable(h2, *o2);
366 c1.partial_cmp(&c2)
367 }
368 (DateTimeKind::LocalDateTime, DateTimeKind::LocalDateTime)
369 | (DateTimeKind::LocalDate, DateTimeKind::LocalDate) => {
370 (
372 h1.year, h1.month, h1.day, h1.hour, h1.minute, h1.second, h1.nanos,
373 )
374 .partial_cmp(&(
375 h2.year, h2.month, h2.day, h2.hour, h2.minute, h2.second, h2.nanos,
376 ))
377 }
378 (DateTimeKind::LocalTime, DateTimeKind::LocalTime) => {
379 (h1.hour, h1.minute, h1.second, h1.nanos)
380 .partial_cmp(&(h2.hour, h2.minute, h2.second, h2.nanos))
381 }
382 _ => None, }
384 }
385}
386
387impl Hash for VDateTime {
390 fn hash<H: Hasher>(&self, state: &mut H) {
391 let h = self.header();
392 h.kind.hash(state);
393 h.year.hash(state);
394 h.month.hash(state);
395 h.day.hash(state);
396 h.hour.hash(state);
397 h.minute.hash(state);
398 h.second.hash(state);
399 h.nanos.hash(state);
400 }
401}
402
403impl Debug for VDateTime {
406 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
407 let h = self.header();
408 match h.kind {
409 DateTimeKind::Offset { offset_minutes } => {
410 write!(
411 f,
412 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
413 h.year, h.month, h.day, h.hour, h.minute, h.second
414 )?;
415 if h.nanos > 0 {
416 write!(f, ".{:09}", h.nanos)?;
417 }
418 if offset_minutes == 0 {
419 write!(f, "Z")
420 } else {
421 let sign = if offset_minutes >= 0 { '+' } else { '-' };
422 let abs = offset_minutes.abs();
423 write!(f, "{}{:02}:{:02}", sign, abs / 60, abs % 60)
424 }
425 }
426 DateTimeKind::LocalDateTime => {
427 write!(
428 f,
429 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
430 h.year, h.month, h.day, h.hour, h.minute, h.second
431 )?;
432 if h.nanos > 0 {
433 write!(f, ".{:09}", h.nanos)?;
434 }
435 Ok(())
436 }
437 DateTimeKind::LocalDate => {
438 write!(f, "{:04}-{:02}-{:02}", h.year, h.month, h.day)
439 }
440 DateTimeKind::LocalTime => {
441 write!(f, "{:02}:{:02}:{:02}", h.hour, h.minute, h.second)?;
442 if h.nanos > 0 {
443 write!(f, ".{:09}", h.nanos)?;
444 }
445 Ok(())
446 }
447 }
448 }
449}
450
451#[cfg(feature = "alloc")]
454impl From<VDateTime> for Value {
455 fn from(dt: VDateTime) -> Self {
456 dt.0
457 }
458}