1use std::num::TryFromIntError;
2use std::sync::Arc;
3
4use chrono::{Duration, FixedOffset, NaiveDate, TimeZone, Utc};
5use chrono_tz::{Tz, UTC};
6
7use crate::{Error, FromSql, Result, ToSql, Type, Value, unexpected_type};
8
9#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd, Debug, Default)]
11pub struct Date(pub u16);
12
13#[expect(clippy::cast_sign_loss)]
14#[expect(clippy::cast_possible_truncation)]
15impl Date {
16 pub fn from_days(days: i32) -> Self {
20 assert!(!(days < 0 || days > i32::from(u16::MAX)), "Date out of range for u16: {days}");
21 Date(days as u16)
22 }
23
24 pub fn from_millis(ms: i64) -> Self {
28 let days = ms / 86_400_000; assert!(!(days < 0 || days > i64::from(u16::MAX)), "Date out of range for u16: {days}");
30 Date(days as u16)
31 }
32}
33
34#[cfg(feature = "serde")]
35impl serde::Serialize for Date {
36 fn serialize<S: serde::Serializer>(
37 &self,
38 serializer: S,
39 ) -> std::result::Result<S::Ok, S::Error> {
40 let date: NaiveDate = (*self).into();
41 date.serialize(serializer)
42 }
43}
44
45#[cfg(feature = "serde")]
46impl<'de> serde::Deserialize<'de> for Date {
47 fn deserialize<D: serde::Deserializer<'de>>(
48 deserializer: D,
49 ) -> std::result::Result<Self, D::Error> {
50 let date: NaiveDate = NaiveDate::deserialize(deserializer)?;
51 Ok(date.into())
52 }
53}
54
55impl ToSql for Date {
56 fn to_sql(self, _type_hint: Option<&Type>) -> Result<Value> { Ok(Value::Date(self)) }
57}
58
59impl FromSql for Date {
60 fn from_sql(type_: &Type, value: Value) -> Result<Self> {
61 if !matches!(type_, Type::Date) {
62 return Err(unexpected_type(type_));
63 }
64 match value {
65 Value::Date(x) => Ok(x),
66 _ => Err(unexpected_type(type_)),
67 }
68 }
69}
70
71impl From<NaiveDate> for Date {
72 fn from(other: NaiveDate) -> Self {
73 #[expect(clippy::cast_possible_truncation)]
74 #[expect(clippy::cast_sign_loss)]
75 Self(other.signed_duration_since(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()).num_days()
76 as u16)
77 }
78}
79
80impl From<Date> for NaiveDate {
81 fn from(date: Date) -> Self {
82 NaiveDate::from_ymd_opt(1970, 1, 1).unwrap() + Duration::days(i64::from(date.0))
83 }
84}
85
86#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd, Debug, Default)]
88pub struct Date32(pub i32);
89
90#[expect(clippy::cast_possible_truncation)]
91impl Date32 {
92 pub fn from_days(days: i32) -> Self { Date32(days) }
94
95 pub fn from_millis(ms: i64) -> Self {
97 const DAYS_1900_TO_1970: i64 = 25_567; let days = ms / 86_400_000; Date32((days - DAYS_1900_TO_1970) as i32)
100 }
101}
102
103#[cfg(feature = "serde")]
104impl serde::Serialize for Date32 {
105 fn serialize<S: serde::Serializer>(
106 &self,
107 serializer: S,
108 ) -> std::result::Result<S::Ok, S::Error> {
109 let date: NaiveDate = (*self).into();
110 date.serialize(serializer)
111 }
112}
113
114#[cfg(feature = "serde")]
115impl<'de> serde::Deserialize<'de> for Date32 {
116 fn deserialize<D: serde::Deserializer<'de>>(
117 deserializer: D,
118 ) -> std::result::Result<Self, D::Error> {
119 let date: NaiveDate = NaiveDate::deserialize(deserializer)?;
120 Ok(date.into())
121 }
122}
123
124impl ToSql for Date32 {
125 fn to_sql(self, _type_hint: Option<&Type>) -> Result<Value> { Ok(Value::Date32(self)) }
126}
127
128impl FromSql for Date32 {
129 fn from_sql(type_: &Type, value: Value) -> Result<Self> {
130 if !matches!(type_, Type::Date32) {
131 return Err(unexpected_type(type_));
132 }
133 match value {
134 Value::Date32(x) => Ok(x),
135 _ => Err(unexpected_type(&Type::Date32)),
136 }
137 }
138}
139
140impl From<NaiveDate> for Date32 {
141 fn from(other: NaiveDate) -> Self {
142 let days =
143 other.signed_duration_since(NaiveDate::from_ymd_opt(1900, 1, 1).unwrap()).num_days();
144 #[expect(clippy::cast_possible_truncation)]
145 Date32(days as i32)
146 }
147}
148
149impl From<Date32> for NaiveDate {
150 fn from(date: Date32) -> Self {
151 NaiveDate::from_ymd_opt(1900, 1, 1).unwrap() + Duration::days(i64::from(date.0))
152 }
153}
154
155#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
157pub struct DateTime(pub Tz, pub u32);
158
159#[expect(clippy::cast_sign_loss)]
160#[expect(clippy::cast_possible_truncation)]
161impl DateTime {
162 #[must_use]
167 pub fn from_chrono_infallible(other: chrono::DateTime<Tz>) -> Self {
168 let timestamp = other.timestamp() as u32;
170 Self(other.timezone(), timestamp)
171 }
172
173 #[must_use]
178 pub fn from_chrono_infallible_utc(other: chrono::DateTime<Utc>) -> Self {
179 let timestamp = other.timestamp() as u32;
181 Self(UTC, timestamp)
182 }
183
184 pub fn from_seconds(seconds: i64, tz: Option<Arc<str>>) -> Self {
188 let tz = tz.map_or(UTC, |s| s.parse::<Tz>().unwrap());
189 assert!(
190 !(seconds < 0 || seconds > i64::from(u32::MAX)),
191 "DateTime out of range for u32: {seconds}"
192 );
193 DateTime(tz, seconds as u32)
194 }
195
196 pub fn from_millis(ms: i64, tz: Option<Arc<str>>) -> Self {
200 let seconds = ms / 1000;
201 assert!(
202 !(seconds < 0 || seconds > i64::from(u32::MAX)),
203 "DateTime out of range for u32: {seconds}"
204 );
205 DateTime(tz.map_or(UTC, |s| s.parse::<Tz>().unwrap()), seconds as u32)
206 }
207
208 pub fn from_micros(us: i64, tz: Option<Arc<str>>) -> Self {
212 let seconds = us / 1_000_000;
213 assert!(
214 !(seconds < 0 || seconds > i64::from(u32::MAX)),
215 "DateTime out of range for u32: {seconds}"
216 );
217 DateTime(tz.map_or(UTC, |s| s.parse::<Tz>().unwrap()), seconds as u32)
218 }
219
220 pub fn from_nanos(ns: i64, tz: Option<Arc<str>>) -> Self {
224 let seconds = ns / 1_000_000_000;
225 assert!(
226 !(seconds < 0 || seconds > i64::from(u32::MAX)),
227 "DateTime out of range for u32: {seconds}"
228 );
229 DateTime(tz.map_or(UTC, |s| s.parse::<Tz>().unwrap()), seconds as u32)
230 }
231}
232
233#[cfg(feature = "serde")]
234impl serde::Serialize for DateTime {
235 fn serialize<S: serde::Serializer>(
236 &self,
237 serializer: S,
238 ) -> std::result::Result<S::Ok, S::Error> {
239 let date: chrono::DateTime<Tz> = (*self)
240 .try_into()
241 .map_err(|e: TryFromIntError| serde::ser::Error::custom(e.to_string()))?;
242 date.to_rfc3339().serialize(serializer)
243 }
244}
245
246#[cfg(feature = "serde")]
247impl<'de> serde::Deserialize<'de> for DateTime {
248 fn deserialize<D: serde::Deserializer<'de>>(
249 deserializer: D,
250 ) -> std::result::Result<Self, D::Error> {
251 let raw: String = String::deserialize(deserializer)?;
252 let date: chrono::DateTime<FixedOffset> =
253 chrono::DateTime::<FixedOffset>::parse_from_rfc3339(&raw)
254 .map_err(|e: chrono::ParseError| serde::de::Error::custom(e.to_string()))?;
255
256 date.try_into().map_err(|e: TryFromIntError| serde::de::Error::custom(e.to_string()))
257 }
258}
259
260impl ToSql for DateTime {
261 fn to_sql(self, _type_hint: Option<&Type>) -> Result<Value> { Ok(Value::DateTime(self)) }
262}
263
264impl FromSql for DateTime {
265 fn from_sql(type_: &Type, value: Value) -> Result<Self> {
266 if !matches!(type_, Type::DateTime(_)) {
267 return Err(unexpected_type(type_));
268 }
269 match value {
270 Value::DateTime(x) => Ok(x),
271 _ => unimplemented!(),
272 }
273 }
274}
275
276impl Default for DateTime {
277 fn default() -> Self { Self(UTC, 0) }
278}
279
280impl TryFrom<DateTime> for chrono::DateTime<Tz> {
281 type Error = TryFromIntError;
282
283 fn try_from(date: DateTime) -> Result<Self, TryFromIntError> {
284 Ok(date.0.timestamp_opt(date.1.into(), 0).unwrap())
285 }
286}
287
288impl TryFrom<DateTime> for chrono::DateTime<FixedOffset> {
289 type Error = TryFromIntError;
290
291 fn try_from(date: DateTime) -> Result<Self, TryFromIntError> {
292 Ok(date.0.timestamp_opt(date.1.into(), 0).unwrap().fixed_offset())
293 }
294}
295
296impl TryFrom<DateTime> for chrono::DateTime<Utc> {
297 type Error = TryFromIntError;
298
299 fn try_from(date: DateTime) -> Result<Self, TryFromIntError> {
300 Ok(date.0.timestamp_opt(date.1.into(), 0).unwrap().with_timezone(&Utc))
301 }
302}
303
304impl TryFrom<chrono::DateTime<Tz>> for DateTime {
305 type Error = TryFromIntError;
306
307 fn try_from(other: chrono::DateTime<Tz>) -> Result<Self, TryFromIntError> {
308 let timestamp = u32::try_from(other.timestamp())?;
310 Ok(Self(other.timezone(), timestamp))
311 }
312}
313
314impl TryFrom<chrono::DateTime<FixedOffset>> for DateTime {
315 type Error = TryFromIntError;
316
317 fn try_from(other: chrono::DateTime<FixedOffset>) -> Result<Self, TryFromIntError> {
318 let timestamp = u32::try_from(other.timestamp())?;
320
321 Ok(Self(Tz::UTC, timestamp))
325 }
326}
327
328impl TryFrom<chrono::DateTime<Utc>> for DateTime {
329 type Error = TryFromIntError;
330
331 fn try_from(other: chrono::DateTime<Utc>) -> Result<Self, TryFromIntError> {
332 Ok(Self(UTC, other.timestamp().try_into()?))
333 }
334}
335
336#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
338pub struct DateTime64<const PRECISION: usize>(pub Tz, pub u64);
339
340#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
342pub struct DynDateTime64(pub Tz, pub u64, pub usize);
343
344#[expect(clippy::cast_sign_loss)]
346impl DynDateTime64 {
347 pub fn from_seconds(seconds: i64, tz: Option<Arc<str>>) -> Self {
351 let tz = tz.map_or(UTC, |s| s.parse::<Tz>().unwrap());
352 assert!(seconds >= 0, "DynDateTime64 does not support negative seconds: {seconds}");
353 DynDateTime64(tz, seconds as u64, 0) }
355
356 pub fn from_millis(ms: i64, tz: Option<Arc<str>>) -> Self {
360 let tz = tz.map_or(UTC, |s| s.parse::<Tz>().unwrap());
361 assert!(ms >= 0, "DynDateTime64 does not support negative milliseconds: {ms}");
362 DynDateTime64(tz, ms as u64, 3) }
364
365 pub fn from_micros(us: i64, tz: Option<Arc<str>>) -> Self {
369 let tz = tz.map_or(UTC, |s| s.parse::<Tz>().unwrap());
370 assert!(us >= 0, "DynDateTime64 does not support negative microseconds: {us}");
371 DynDateTime64(tz, us as u64, 6) }
373
374 pub fn from_nanos(ns: i64, tz: Option<Arc<str>>) -> Self {
378 let tz = tz.map_or(UTC, |s| s.parse::<Tz>().unwrap());
379 assert!(ns >= 0, "DynDateTime64 does not support negative nanoseconds: {ns}");
380 DynDateTime64(tz, ns as u64, 9) }
382}
383
384impl<const PRECISION: usize> From<DateTime64<PRECISION>> for DynDateTime64 {
385 fn from(value: DateTime64<PRECISION>) -> Self { Self(value.0, value.1, PRECISION) }
386}
387
388#[cfg(feature = "serde")]
389impl serde::Serialize for DynDateTime64 {
390 fn serialize<S: serde::Serializer>(
391 &self,
392 serializer: S,
393 ) -> std::result::Result<S::Ok, S::Error> {
394 let date: chrono::DateTime<Tz> = (*self)
395 .try_into()
396 .map_err(|e: TryFromIntError| serde::ser::Error::custom(e.to_string()))?;
397 date.to_rfc3339().serialize(serializer)
398 }
399}
400
401#[cfg(feature = "serde")]
402impl<'de> serde::Deserialize<'de> for DynDateTime64 {
403 fn deserialize<D: serde::Deserializer<'de>>(
404 deserializer: D,
405 ) -> std::result::Result<Self, D::Error> {
406 let raw: String = String::deserialize(deserializer)?;
407 let date: chrono::DateTime<Utc> = Utc.from_utc_datetime(
408 &chrono::DateTime::<FixedOffset>::parse_from_rfc3339(&raw)
409 .map_err(|e: chrono::ParseError| serde::de::Error::custom(e.to_string()))?
410 .naive_utc(),
411 );
412
413 DynDateTime64::try_from_utc(date, 6)
414 .map_err(|e: TryFromIntError| serde::de::Error::custom(e.to_string()))
415 }
416}
417
418#[cfg(feature = "serde")]
419impl<const PRECISION: usize> serde::Serialize for DateTime64<PRECISION> {
420 fn serialize<S: serde::Serializer>(
421 &self,
422 serializer: S,
423 ) -> std::result::Result<S::Ok, S::Error> {
424 let date: chrono::DateTime<Tz> = (*self)
425 .try_into()
426 .map_err(|e: TryFromIntError| serde::ser::Error::custom(e.to_string()))?;
427 date.to_rfc3339().serialize(serializer)
428 }
429}
430
431#[cfg(feature = "serde")]
432impl<'de, const PRECISION: usize> serde::Deserialize<'de> for DateTime64<PRECISION> {
433 fn deserialize<D: serde::Deserializer<'de>>(
434 deserializer: D,
435 ) -> std::result::Result<Self, D::Error> {
436 let raw: String = String::deserialize(deserializer)?;
437 let date: chrono::DateTime<Utc> = Utc.from_utc_datetime(
438 &chrono::DateTime::<FixedOffset>::parse_from_rfc3339(&raw)
439 .map_err(|e: chrono::ParseError| serde::de::Error::custom(e.to_string()))?
440 .naive_utc(),
441 );
442
443 date.try_into().map_err(|e: TryFromIntError| serde::de::Error::custom(e.to_string()))
444 }
445}
446
447impl<const PRECISION: usize> ToSql for DateTime64<PRECISION> {
448 fn to_sql(self, _type_hint: Option<&Type>) -> Result<Value> {
449 Ok(Value::DateTime64(self.into()))
450 }
451}
452
453impl<const PRECISION: usize> FromSql for DateTime64<PRECISION> {
454 fn from_sql(type_: &Type, value: Value) -> Result<Self> {
455 if !matches!(type_, Type::DateTime64(x, _) if *x == PRECISION) {
456 return Err(unexpected_type(type_));
457 }
458 match value {
459 Value::DateTime64(datetime) => Ok(Self(datetime.0, datetime.1)),
460 _ => unimplemented!(),
461 }
462 }
463}
464
465impl<const PRECISION: usize> Default for DateTime64<PRECISION> {
466 fn default() -> Self { Self(UTC, 0) }
467}
468
469impl ToSql for chrono::DateTime<Utc> {
470 fn to_sql(self, _type_hint: Option<&Type>) -> Result<Value> {
471 Ok(Value::DateTime64(DynDateTime64(
472 UTC,
473 self.timestamp_micros().try_into().map_err(|e| {
474 Error::DeserializeError(format!("failed to convert DateTime64: {e:?}"))
475 })?,
476 6,
477 )))
478 }
479}
480
481impl FromSql for chrono::DateTime<Utc> {
482 fn from_sql(type_: &Type, value: Value) -> Result<Self> {
483 if !matches!(type_, Type::DateTime64(_, _) | Type::DateTime(_)) {
484 return Err(unexpected_type(type_));
485 }
486 match value {
487 Value::DateTime64(datetime) => {
488 #[expect(clippy::cast_possible_truncation)]
489 let datetime_2 = datetime.2 as u32;
490 let seconds = datetime.1 / 10u64.pow(datetime_2);
491 let units = datetime.1 % 10u64.pow(datetime_2);
492 let units_ns = units * 10u64.pow(9 - datetime_2);
493 let (seconds, units_ns): (i64, u32) =
494 seconds.try_into().and_then(|k| Ok((k, units_ns.try_into()?))).map_err(
495 |e| Error::DeserializeError(format!("failed to convert DateTime: {e:?}")),
496 )?;
497 Ok(datetime.0.timestamp_opt(seconds, units_ns).unwrap().with_timezone(&Utc))
498 }
499 Value::DateTime(date) => Ok(date.try_into().map_err(|e| {
500 Error::DeserializeError(format!("failed to convert DateTime: {e:?}"))
501 })?),
502 _ => unimplemented!(),
503 }
504 }
505}
506
507impl<const PRECISION: usize> TryFrom<DateTime64<PRECISION>> for chrono::DateTime<Utc> {
508 type Error = TryFromIntError;
509
510 fn try_from(date: DateTime64<PRECISION>) -> Result<Self, TryFromIntError> {
511 #[expect(clippy::cast_possible_truncation)]
512 let precision = PRECISION as u32;
513 let seconds = date.1 / 10u64.pow(precision);
514 let units = date.1 % 10u64.pow(precision);
515 let units_ns = units * 10u64.pow(9 - precision);
516 Ok(date
517 .0
518 .timestamp_opt(seconds.try_into()?, units_ns.try_into()?)
519 .unwrap()
520 .with_timezone(&Utc))
521 }
522}
523
524impl TryFrom<DynDateTime64> for chrono::DateTime<Utc> {
525 type Error = TryFromIntError;
526
527 fn try_from(date: DynDateTime64) -> Result<Self, TryFromIntError> {
528 #[expect(clippy::cast_possible_truncation)]
529 let date_2 = date.2 as u32;
530 let seconds = date.1 / 10u64.pow(date_2);
531 let units = date.1 % 10u64.pow(date_2);
532 let units_ns = units * 10u64.pow(9 - date_2);
533 Ok(date
534 .0
535 .timestamp_opt(seconds.try_into()?, units_ns.try_into()?)
536 .unwrap()
537 .with_timezone(&Utc))
538 }
539}
540
541impl ToSql for chrono::DateTime<Tz> {
542 fn to_sql(self, _type_hint: Option<&Type>) -> Result<Value> {
543 Ok(Value::DateTime64(DynDateTime64(
544 self.timezone(),
545 self.timestamp_micros().try_into().map_err(|e| {
546 Error::DeserializeError(format!("failed to convert DateTime64: {e:?}"))
547 })?,
548 6,
549 )))
550 }
551}
552
553impl FromSql for chrono::DateTime<Tz> {
554 fn from_sql(type_: &Type, value: Value) -> Result<Self> {
555 if !matches!(type_, Type::DateTime64(_, _) | Type::DateTime(_)) {
556 return Err(unexpected_type(type_));
557 }
558 match value {
559 Value::DateTime64(datetime) => {
560 #[expect(clippy::cast_possible_truncation)]
561 let datetime_2 = datetime.2 as u32;
562 let seconds = datetime.1 / 10u64.pow(datetime_2);
563 let units = datetime.1 % 10u64.pow(datetime_2);
564 let units_ns = units * 10u64.pow(9 - datetime_2);
565 let (seconds, units_ns): (i64, u32) =
566 seconds.try_into().and_then(|k| Ok((k, units_ns.try_into()?))).map_err(
567 |e| Error::DeserializeError(format!("failed to convert DateTime: {e:?}")),
568 )?;
569 Ok(datetime.0.timestamp_opt(seconds, units_ns).unwrap())
570 }
571 Value::DateTime(date) => Ok(date.try_into().map_err(|e| {
572 Error::DeserializeError(format!("failed to convert DateTime: {e:?}"))
573 })?),
574 _ => unimplemented!(),
575 }
576 }
577}
578
579impl<const PRECISION: usize> TryFrom<chrono::DateTime<Utc>> for DateTime64<PRECISION> {
580 type Error = TryFromIntError;
581
582 fn try_from(other: chrono::DateTime<Utc>) -> Result<Self, TryFromIntError> {
583 #[expect(clippy::cast_possible_truncation)]
584 let precision = PRECISION as u32;
585 let seconds: u64 = other.timestamp().try_into()?;
586 let sub_seconds: u64 = u64::from(other.timestamp_subsec_nanos());
587 let total = seconds * 10u64.pow(precision) + sub_seconds / 10u64.pow(9 - precision);
588 Ok(Self(UTC, total))
589 }
590}
591
592impl DynDateTime64 {
593 pub fn try_from_utc(
597 other: chrono::DateTime<Utc>,
598 precision: usize,
599 ) -> Result<Self, TryFromIntError> {
600 #[expect(clippy::cast_possible_truncation)]
601 let precision_u32 = precision as u32;
602 let seconds: u64 = other.timestamp().try_into()?;
603 let sub_seconds: u64 = u64::from(other.timestamp_subsec_nanos());
604 let total = seconds * 10u64.pow(precision_u32) + sub_seconds / 10u64.pow(9 - precision_u32);
605 Ok(Self(UTC, total, precision))
606 }
607}
608
609impl<const PRECISION: usize> TryFrom<DateTime64<PRECISION>> for chrono::DateTime<Tz> {
610 type Error = TryFromIntError;
611
612 fn try_from(date: DateTime64<PRECISION>) -> Result<Self, TryFromIntError> {
613 #[expect(clippy::cast_possible_truncation)]
614 let precision = PRECISION as u32;
615 let seconds = date.1 / 10u64.pow(precision);
616 let units = date.1 % 10u64.pow(precision);
617 let units_ns = units * 10u64.pow(9 - precision);
618 Ok(date.0.timestamp_opt(seconds.try_into()?, units_ns.try_into()?).unwrap())
619 }
620}
621
622impl TryFrom<DynDateTime64> for chrono::DateTime<Tz> {
623 type Error = TryFromIntError;
624
625 fn try_from(date: DynDateTime64) -> Result<Self, TryFromIntError> {
626 #[expect(clippy::cast_possible_truncation)]
627 let date_2 = date.2 as u32;
628 let seconds = date.1 / 10u64.pow(date_2);
629 let units = date.1 % 10u64.pow(date_2);
630 let units_ns = units * 10u64.pow(9 - date_2);
631 Ok(date.0.timestamp_opt(seconds.try_into()?, units_ns.try_into()?).unwrap())
632 }
633}
634
635impl<const PRECISION: usize> TryFrom<chrono::DateTime<Tz>> for DateTime64<PRECISION> {
636 type Error = TryFromIntError;
637
638 fn try_from(other: chrono::DateTime<Tz>) -> Result<Self, TryFromIntError> {
639 let seconds: u64 = other.timestamp().try_into()?;
640 let sub_seconds: u64 = u64::from(other.timestamp_subsec_nanos());
641 #[expect(clippy::cast_possible_truncation)]
642 let total =
643 seconds * 10u64.pow(PRECISION as u32) + sub_seconds / 10u64.pow(9 - PRECISION as u32);
644 Ok(Self(other.timezone(), total))
645 }
646}
647
648impl DynDateTime64 {
649 pub fn try_from_tz(
653 other: chrono::DateTime<Tz>,
654 precision: usize,
655 ) -> Result<Self, TryFromIntError> {
656 let seconds: u64 = other.timestamp().try_into()?;
657 let sub_seconds: u64 = u64::from(other.timestamp_subsec_nanos());
658 #[expect(clippy::cast_possible_truncation)]
659 let total =
660 seconds * 10u64.pow(precision as u32) + sub_seconds / 10u64.pow(9 - precision as u32);
661 Ok(Self(other.timezone(), total, precision))
662 }
663}
664
665impl<const PRECISION: usize> TryFrom<DateTime64<PRECISION>> for chrono::DateTime<FixedOffset> {
666 type Error = TryFromIntError;
667
668 fn try_from(date: DateTime64<PRECISION>) -> Result<Self, TryFromIntError> {
669 #[expect(clippy::cast_possible_truncation)]
670 let precision = PRECISION as u32;
671 let seconds = date.1 / 10u64.pow(precision);
672 let units = date.1 % 10u64.pow(precision);
673 let units_ns = units * 10u64.pow(9 - precision);
674 Ok(date.0.timestamp_opt(seconds.try_into()?, units_ns.try_into()?).unwrap().fixed_offset())
675 }
676}
677
678impl TryFrom<DynDateTime64> for chrono::DateTime<FixedOffset> {
679 type Error = TryFromIntError;
680
681 fn try_from(date: DynDateTime64) -> Result<Self, TryFromIntError> {
682 #[expect(clippy::cast_possible_truncation)]
683 let date_2 = date.2 as u32;
684 let seconds = date.1 / 10u64.pow(date_2);
685 let units = date.1 % 10u64.pow(date_2);
686 let units_ns = units * 10u64.pow(9 - date_2);
687 Ok(date.0.timestamp_opt(seconds.try_into()?, units_ns.try_into()?).unwrap().fixed_offset())
688 }
689}
690
691#[cfg(test)]
692mod chrono_tests {
693 use chrono::TimeZone;
694 use chrono_tz::UTC;
695
696 use super::*;
697 use crate::deserialize::DAYS_1900_TO_1970;
698
699 #[test]
700 fn test_naivedate() {
701 for i in 0..30000u16 {
702 let date = Date(i);
703 let chrono_date: NaiveDate = date.into();
704 let new_date = Date::from(chrono_date);
705 assert_eq!(new_date, date);
706 }
707 }
708
709 #[test]
710 fn test_date_from_millis() {
711 let millis = 86_400_000_i64;
712 let dt = Date::from_millis(millis);
713 assert_eq!(dt.0, 1_u16);
714 }
715
716 #[test]
717 #[should_panic(expected = "Date out of range for u16: -1")]
718 fn test_date_from_millis_invalid() {
719 let millis = -86_400_000_i64;
720 let _dt = Date::from_millis(millis);
721 }
722
723 #[cfg(feature = "serde")]
724 #[test]
725 fn test_date_roundtrip() {
726 use serde_json;
727
728 let original_date = Date(1);
729 let serialized = serde_json::to_string(&original_date).unwrap();
730 let deserialized: Date = serde_json::from_str(&serialized).unwrap();
731
732 assert_eq!(original_date, deserialized);
733 }
734
735 #[test]
736 fn test_date_wrong_type() {
737 let type_ = Type::String;
738 let result = Date::from_sql(&type_, Value::Date(Date(1)));
739 assert!(result.is_err());
740 }
741
742 #[test]
743 fn test_date_wrong_value() {
744 let type_ = Type::Date;
745 let result = Date::from_sql(&type_, Value::Null);
746 assert!(result.is_err());
747 }
748
749 #[test]
750 fn test_date32_from_millis() {
751 let days = 0_i32;
752 let ms = i64::from(days + DAYS_1900_TO_1970) * 86_400_000_i64;
753 let date1 = Date32::from_days(days);
754 let date2 = Date32::from_millis(ms);
755 assert_eq!(date1, date2);
756 }
757
758 #[cfg(feature = "serde")]
759 #[test]
760 fn test_date32_roundtrip() {
761 use serde_json;
762
763 let original_date = Date32(1);
764 let serialized = serde_json::to_string(&original_date).unwrap();
765 let deserialized: Date32 = serde_json::from_str(&serialized).unwrap();
766
767 assert_eq!(original_date, deserialized);
768 }
769
770 #[test]
771 fn test_date32() {
772 let value = Date32(1).to_sql(None);
773 assert!(value.is_ok());
774 let type_ = Date32::from_sql(&Type::Date32, value.unwrap());
775 assert!(type_.is_ok());
776 let invalid = Date32::from_sql(&Type::String, Value::Null);
777 assert!(invalid.is_err());
778 let invalid = Date32::from_sql(&Type::Date32, Value::Null);
779 assert!(invalid.is_err());
780
781 let date = Date32(1);
782 let chrono_date: NaiveDate = date.into();
783 let new_date = Date32::from(chrono_date);
784 assert_eq!(new_date, date);
785 }
786
787 #[test]
788 fn test_datetime() {
789 for i in (0..30000u32).map(|x| x * 10000) {
790 let date = DateTime(UTC, i);
791 let chrono_date: chrono::DateTime<Tz> = date.try_into().unwrap();
792 let new_date = DateTime::try_from(chrono_date).unwrap();
793 let other_date = DateTime::from_chrono_infallible(chrono_date);
794 assert_eq!(new_date, date);
795 assert_eq!(new_date, other_date);
796 }
797
798 let expected = DateTime(Tz::UTC, 1);
799 let d1 = DateTime::from_millis(1_000, None);
800 let d2 = DateTime::from_micros(1_000_000, None);
801 let d3 = DateTime::from_micros(1_000_000, Some(Arc::from("UTC")));
802 let d4 = DateTime::from_nanos(1_000_000_000, None);
803 assert_eq!(d1, expected);
804 assert_eq!(d2, expected);
805 assert_eq!(d3, expected);
806 assert_eq!(d4, expected);
807 }
808
809 #[cfg(feature = "serde")]
810 #[test]
811 fn test_datetime_roundtrip() {
812 let original_date = DateTime(Tz::UTC, 1_640_995_200); let serialized = serde_json::to_string(&original_date).unwrap();
815 let deserialized: DateTime = serde_json::from_str(&serialized).unwrap();
816
817 assert_eq!(deserialized.1, original_date.1);
819 assert_eq!(deserialized.0, Tz::UTC);
820 }
821
822 #[cfg(feature = "serde")]
823 #[test]
824 fn test_invalid_rfc3339_string() {
825 let invalid_json = "\"not-a-valid-date\"";
827 let result: Result<DateTime, _> = serde_json::from_str(invalid_json);
828 assert!(result.is_err());
829 }
830
831 #[test]
832 fn test_conversion_functions_directly() {
833 let dt = DateTime(Tz::UTC, 1_640_995_200);
835
836 let chrono_tz: chrono::DateTime<Tz> = dt.try_into().unwrap();
838 let back_to_dt: DateTime = chrono_tz.try_into().unwrap();
839 assert_eq!(dt, back_to_dt);
840
841 let chrono_fixed: chrono::DateTime<FixedOffset> = dt.try_into().unwrap();
843 let back_from_fixed: DateTime = chrono_fixed.try_into().unwrap();
844 assert_eq!(back_from_fixed.1, dt.1); assert_eq!(back_from_fixed.0, Tz::UTC); let chrono_utc: chrono::DateTime<Utc> = dt.try_into().unwrap();
850 let back_from_utc: DateTime = chrono_utc.try_into().unwrap();
851 assert_eq!(dt, back_from_utc);
852 }
853
854 #[test]
855 #[should_panic(expected = "DateTime out of range for u32: -1")]
856 fn test_datetime_panic_secs() { let _d = DateTime::from_seconds(-1, None); }
857
858 #[test]
859 #[should_panic(expected = "DateTime out of range for u32: -1")]
860 fn test_datetime_panic_millis() { let _d = DateTime::from_millis(-1_000, None); }
861
862 #[test]
863 #[should_panic(expected = "DateTime out of range for u32: -1")]
864 fn test_datetime_panic_micros() { let _d = DateTime::from_micros(-1_000_000, None); }
865
866 #[test]
867 fn test_datetime64() {
868 for i in (0..30000u64).map(|x| x * 10000) {
869 let date = DateTime64::<6>(UTC, i);
870 let chrono_date: chrono::DateTime<Tz> = date.try_into().unwrap();
871 let new_date = DateTime64::try_from(chrono_date).unwrap();
872 assert_eq!(new_date, date);
873 }
874 }
875
876 #[test]
877 fn test_datetime64_precision() {
878 for i in (0..30000u64).map(|x| x * 10000) {
879 let date = DateTime64::<6>(UTC, i);
880 let date_value = date.to_sql(None).unwrap();
881 assert_eq!(date_value, Value::DateTime64(DynDateTime64(UTC, i, 6)));
882 let chrono_date: chrono::DateTime<Utc> =
883 FromSql::from_sql(&Type::DateTime64(6, UTC), date_value).unwrap();
884 let new_date = DateTime64::try_from(chrono_date).unwrap();
885 assert_eq!(new_date, date);
886 }
887 }
888
889 #[test]
890 fn test_datetime64_precision2() {
891 for i in (0..300u64).map(|x| x * 1_000_000) {
892 #[expect(clippy::cast_possible_wrap)]
893 #[expect(clippy::cast_possible_truncation)]
894 let chrono_time = Utc.timestamp_opt(i as i64, i as u32).unwrap();
895 let date = chrono_time.to_sql(None).unwrap();
896 let out_time: chrono::DateTime<Utc> =
897 FromSql::from_sql(&Type::DateTime64(9, UTC), date.clone()).unwrap();
898 assert_eq!(chrono_time, out_time);
899 let date = match date {
900 Value::DateTime64(mut datetime) => {
901 datetime.2 -= 3;
902 datetime.1 /= 1000;
903 Value::DateTime64(datetime)
904 }
905 _ => unimplemented!(),
906 };
907 let out_time: chrono::DateTime<Utc> =
908 FromSql::from_sql(&Type::DateTime64(9, UTC), date.clone()).unwrap();
909
910 assert_eq!(chrono_time, out_time);
911 }
912 }
913
914 #[test]
915 fn test_from_seconds() {
916 let dt = DynDateTime64::from_seconds(1000, Some(Arc::from("UTC")));
917 assert_eq!(dt.0, Tz::UTC);
918 assert_eq!(dt.1, 1000);
919 assert_eq!(dt.2, 0);
920 }
921
922 #[test]
923 fn test_from_millis() {
924 let dt = DynDateTime64::from_millis(1000, Some(Arc::from("UTC")));
925 assert_eq!(dt.0, Tz::UTC);
926 assert_eq!(dt.1, 1000); assert_eq!(dt.2, 3);
928 }
929
930 #[test]
931 fn test_from_micros() {
932 let dt = DynDateTime64::from_micros(1_000_000, Some(Arc::from("UTC")));
933 assert_eq!(dt.0, Tz::UTC);
934 assert_eq!(dt.1, 1_000_000); assert_eq!(dt.2, 6);
936 }
937
938 #[test]
939 fn test_from_nanos() {
940 let dt = DynDateTime64::from_nanos(1_000_000_000, Some(Arc::from("UTC")));
941 assert_eq!(dt.0, Tz::UTC);
942 assert_eq!(dt.1, 1_000_000_000); assert_eq!(dt.2, 9);
944 }
945
946 #[test]
947 fn test_from_seconds_zero() {
948 let dt = DynDateTime64::from_seconds(0, None);
949 assert_eq!(dt.0, Tz::UTC);
950 assert_eq!(dt.1, 0);
951 assert_eq!(dt.2, 0);
952 }
953
954 #[test]
955 #[should_panic(expected = "DynDateTime64 does not support negative seconds: -1")]
956 fn test_from_seconds_negative() {
957 let _ = DynDateTime64::from_seconds(-1, Some(Arc::from("UTC")));
958 }
959
960 #[test]
961 #[should_panic(expected = "DynDateTime64 does not support negative milliseconds: -1000")]
962 fn test_from_millis_negative() {
963 let _ = DynDateTime64::from_millis(-1000, Some(Arc::from("UTC")));
964 }
965
966 #[test]
967 #[should_panic(expected = "DynDateTime64 does not support negative microseconds: -1000000")]
968 fn test_from_micros_negative() {
969 let _ = DynDateTime64::from_micros(-1_000_000, Some(Arc::from("UTC")));
970 }
971
972 #[test]
973 #[should_panic(expected = "DynDateTime64 does not support negative nanoseconds: -1000000000")]
974 fn test_from_nanos_negative() {
975 let _ = DynDateTime64::from_nanos(-1_000_000_000, Some(Arc::from("UTC")));
976 }
977
978 #[test]
979 fn test_from_millis_custom_tz() {
980 let dt = DynDateTime64::from_millis(1000, Some(Arc::from("America/New_York")));
981 assert_eq!(dt.0, Tz::America__New_York);
982 assert_eq!(dt.1, 1000);
983 assert_eq!(dt.2, 3);
984 }
985
986 #[test]
987 #[allow(deprecated)]
988 fn test_consistency_with_convert_for_str() {
989 let test_date = "2022-04-22 00:00:00";
990
991 let dt = chrono::NaiveDateTime::parse_from_str(test_date, "%Y-%m-%d %H:%M:%S").unwrap();
992
993 let chrono_date =
994 chrono::DateTime::<Tz>::from_utc(dt, chrono_tz::UTC.offset_from_utc_datetime(&dt));
995
996 #[expect(clippy::cast_possible_truncation)]
997 #[expect(clippy::cast_sign_loss)]
998 let date = DateTime(UTC, dt.timestamp() as u32);
999
1000 let new_chrono_date: chrono::DateTime<Tz> = date.try_into().unwrap();
1001
1002 assert_eq!(new_chrono_date, chrono_date);
1003 }
1004}