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 _ => unimplemented!(),
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 _ => unimplemented!(),
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 Tz::UTC.from_utc_datetime(&other.naive_utc()).with_timezone(&other.timezone()).try_into()
319 }
320}
321
322impl TryFrom<chrono::DateTime<Utc>> for DateTime {
323 type Error = TryFromIntError;
324
325 fn try_from(other: chrono::DateTime<Utc>) -> Result<Self, TryFromIntError> {
326 Ok(Self(UTC, other.timestamp().try_into()?))
327 }
328}
329
330#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
332pub struct DateTime64<const PRECISION: usize>(pub Tz, pub u64);
333
334#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
336pub struct DynDateTime64(pub Tz, pub u64, pub usize);
337
338#[expect(clippy::cast_sign_loss)]
340impl DynDateTime64 {
341 pub fn from_seconds(seconds: i64, tz: Option<Arc<str>>) -> Self {
345 let tz = tz.map_or(UTC, |s| s.parse::<Tz>().unwrap());
346 assert!(seconds >= 0, "DynDateTime64 does not support negative seconds: {seconds}");
347 DynDateTime64(tz, seconds as u64, 0) }
349
350 pub fn from_millis(ms: i64, tz: Option<Arc<str>>) -> Self {
354 let tz = tz.map_or(UTC, |s| s.parse::<Tz>().unwrap());
355 assert!(ms >= 0, "DynDateTime64 does not support negative milliseconds: {ms}");
356 DynDateTime64(tz, ms as u64, 3) }
358
359 pub fn from_micros(us: i64, tz: Option<Arc<str>>) -> Self {
363 let tz = tz.map_or(UTC, |s| s.parse::<Tz>().unwrap());
364 assert!(us >= 0, "DynDateTime64 does not support negative microseconds: {us}");
365 DynDateTime64(tz, us as u64, 6) }
367
368 pub fn from_nanos(ns: i64, tz: Option<Arc<str>>) -> Self {
372 let tz = tz.map_or(UTC, |s| s.parse::<Tz>().unwrap());
373 assert!(ns >= 0, "DynDateTime64 does not support negative nanoseconds: {ns}");
374 DynDateTime64(tz, ns as u64, 9) }
376}
377
378impl<const PRECISION: usize> From<DateTime64<PRECISION>> for DynDateTime64 {
379 fn from(value: DateTime64<PRECISION>) -> Self { Self(value.0, value.1, PRECISION) }
380}
381
382#[cfg(feature = "serde")]
383impl serde::Serialize for DynDateTime64 {
384 fn serialize<S: serde::Serializer>(
385 &self,
386 serializer: S,
387 ) -> std::result::Result<S::Ok, S::Error> {
388 let date: chrono::DateTime<Tz> = (*self)
389 .try_into()
390 .map_err(|e: TryFromIntError| serde::ser::Error::custom(e.to_string()))?;
391 date.to_rfc3339().serialize(serializer)
392 }
393}
394
395#[cfg(feature = "serde")]
396impl<'de> serde::Deserialize<'de> for DynDateTime64 {
397 fn deserialize<D: serde::Deserializer<'de>>(
398 deserializer: D,
399 ) -> std::result::Result<Self, D::Error> {
400 let raw: String = String::deserialize(deserializer)?;
401 let date: chrono::DateTime<Utc> = Utc.from_utc_datetime(
402 &chrono::DateTime::<FixedOffset>::parse_from_rfc3339(&raw)
403 .map_err(|e: chrono::ParseError| serde::de::Error::custom(e.to_string()))?
404 .naive_utc(),
405 );
406
407 DynDateTime64::try_from_utc(date, 6)
408 .map_err(|e: TryFromIntError| serde::de::Error::custom(e.to_string()))
409 }
410}
411
412#[cfg(feature = "serde")]
413impl<const PRECISION: usize> serde::Serialize for DateTime64<PRECISION> {
414 fn serialize<S: serde::Serializer>(
415 &self,
416 serializer: S,
417 ) -> std::result::Result<S::Ok, S::Error> {
418 let date: chrono::DateTime<Tz> = (*self)
419 .try_into()
420 .map_err(|e: TryFromIntError| serde::ser::Error::custom(e.to_string()))?;
421 date.to_rfc3339().serialize(serializer)
422 }
423}
424
425#[cfg(feature = "serde")]
426impl<'de, const PRECISION: usize> serde::Deserialize<'de> for DateTime64<PRECISION> {
427 fn deserialize<D: serde::Deserializer<'de>>(
428 deserializer: D,
429 ) -> std::result::Result<Self, D::Error> {
430 let raw: String = String::deserialize(deserializer)?;
431 let date: chrono::DateTime<Utc> = Utc.from_utc_datetime(
432 &chrono::DateTime::<FixedOffset>::parse_from_rfc3339(&raw)
433 .map_err(|e: chrono::ParseError| serde::de::Error::custom(e.to_string()))?
434 .naive_utc(),
435 );
436
437 date.try_into().map_err(|e: TryFromIntError| serde::de::Error::custom(e.to_string()))
438 }
439}
440
441impl<const PRECISION: usize> ToSql for DateTime64<PRECISION> {
442 fn to_sql(self, _type_hint: Option<&Type>) -> Result<Value> {
443 Ok(Value::DateTime64(self.into()))
444 }
445}
446
447impl<const PRECISION: usize> FromSql for DateTime64<PRECISION> {
448 fn from_sql(type_: &Type, value: Value) -> Result<Self> {
449 if !matches!(type_, Type::DateTime64(x, _) if *x == PRECISION) {
450 return Err(unexpected_type(type_));
451 }
452 match value {
453 Value::DateTime64(datetime) => Ok(Self(datetime.0, datetime.1)),
454 _ => unimplemented!(),
455 }
456 }
457}
458
459impl<const PRECISION: usize> Default for DateTime64<PRECISION> {
460 fn default() -> Self { Self(UTC, 0) }
461}
462
463impl ToSql for chrono::DateTime<Utc> {
464 fn to_sql(self, _type_hint: Option<&Type>) -> Result<Value> {
465 Ok(Value::DateTime64(DynDateTime64(
466 UTC,
467 self.timestamp_micros().try_into().map_err(|e| {
468 Error::DeserializeError(format!("failed to convert DateTime64: {e:?}"))
469 })?,
470 6,
471 )))
472 }
473}
474
475impl FromSql for chrono::DateTime<Utc> {
476 fn from_sql(type_: &Type, value: Value) -> Result<Self> {
477 if !matches!(type_, Type::DateTime64(_, _) | Type::DateTime(_)) {
478 return Err(unexpected_type(type_));
479 }
480 match value {
481 Value::DateTime64(datetime) => {
482 #[expect(clippy::cast_possible_truncation)]
483 let datetime_2 = datetime.2 as u32;
484 let seconds = datetime.1 / 10u64.pow(datetime_2);
485 let units = datetime.1 % 10u64.pow(datetime_2);
486 let units_ns = units * 10u64.pow(9 - datetime_2);
487 let (seconds, units_ns): (i64, u32) =
488 seconds.try_into().and_then(|k| Ok((k, units_ns.try_into()?))).map_err(
489 |e| Error::DeserializeError(format!("failed to convert DateTime: {e:?}")),
490 )?;
491 Ok(datetime.0.timestamp_opt(seconds, units_ns).unwrap().with_timezone(&Utc))
492 }
493 Value::DateTime(date) => Ok(date.try_into().map_err(|e| {
494 Error::DeserializeError(format!("failed to convert DateTime: {e:?}"))
495 })?),
496 _ => unimplemented!(),
497 }
498 }
499}
500
501impl<const PRECISION: usize> TryFrom<DateTime64<PRECISION>> for chrono::DateTime<Utc> {
502 type Error = TryFromIntError;
503
504 fn try_from(date: DateTime64<PRECISION>) -> Result<Self, TryFromIntError> {
505 #[expect(clippy::cast_possible_truncation)]
506 let precision = PRECISION as u32;
507 let seconds = date.1 / 10u64.pow(precision);
508 let units = date.1 % 10u64.pow(precision);
509 let units_ns = units * 10u64.pow(9 - precision);
510 Ok(date
511 .0
512 .timestamp_opt(seconds.try_into()?, units_ns.try_into()?)
513 .unwrap()
514 .with_timezone(&Utc))
515 }
516}
517
518impl TryFrom<DynDateTime64> for chrono::DateTime<Utc> {
519 type Error = TryFromIntError;
520
521 fn try_from(date: DynDateTime64) -> Result<Self, TryFromIntError> {
522 #[expect(clippy::cast_possible_truncation)]
523 let date_2 = date.2 as u32;
524 let seconds = date.1 / 10u64.pow(date_2);
525 let units = date.1 % 10u64.pow(date_2);
526 let units_ns = units * 10u64.pow(9 - date_2);
527 Ok(date
528 .0
529 .timestamp_opt(seconds.try_into()?, units_ns.try_into()?)
530 .unwrap()
531 .with_timezone(&Utc))
532 }
533}
534
535impl ToSql for chrono::DateTime<Tz> {
536 fn to_sql(self, _type_hint: Option<&Type>) -> Result<Value> {
537 Ok(Value::DateTime64(DynDateTime64(
538 self.timezone(),
539 self.timestamp_micros().try_into().map_err(|e| {
540 Error::DeserializeError(format!("failed to convert DateTime64: {e:?}"))
541 })?,
542 6,
543 )))
544 }
545}
546
547impl FromSql for chrono::DateTime<Tz> {
548 fn from_sql(type_: &Type, value: Value) -> Result<Self> {
549 if !matches!(type_, Type::DateTime64(_, _) | Type::DateTime(_)) {
550 return Err(unexpected_type(type_));
551 }
552 match value {
553 Value::DateTime64(datetime) => {
554 #[expect(clippy::cast_possible_truncation)]
555 let datetime_2 = datetime.2 as u32;
556 let seconds = datetime.1 / 10u64.pow(datetime_2);
557 let units = datetime.1 % 10u64.pow(datetime_2);
558 let units_ns = units * 10u64.pow(9 - datetime_2);
559 let (seconds, units_ns): (i64, u32) =
560 seconds.try_into().and_then(|k| Ok((k, units_ns.try_into()?))).map_err(
561 |e| Error::DeserializeError(format!("failed to convert DateTime: {e:?}")),
562 )?;
563 Ok(datetime.0.timestamp_opt(seconds, units_ns).unwrap())
564 }
565 Value::DateTime(date) => Ok(date.try_into().map_err(|e| {
566 Error::DeserializeError(format!("failed to convert DateTime: {e:?}"))
567 })?),
568 _ => unimplemented!(),
569 }
570 }
571}
572
573impl<const PRECISION: usize> TryFrom<chrono::DateTime<Utc>> for DateTime64<PRECISION> {
574 type Error = TryFromIntError;
575
576 fn try_from(other: chrono::DateTime<Utc>) -> Result<Self, TryFromIntError> {
577 #[expect(clippy::cast_possible_truncation)]
578 let precision = PRECISION as u32;
579 let seconds: u64 = other.timestamp().try_into()?;
580 let sub_seconds: u64 = u64::from(other.timestamp_subsec_nanos());
581 let total = seconds * 10u64.pow(precision) + sub_seconds / 10u64.pow(9 - precision);
582 Ok(Self(UTC, total))
583 }
584}
585
586impl DynDateTime64 {
587 pub fn try_from_utc(
591 other: chrono::DateTime<Utc>,
592 precision: usize,
593 ) -> Result<Self, TryFromIntError> {
594 #[expect(clippy::cast_possible_truncation)]
595 let precision_u32 = precision as u32;
596 let seconds: u64 = other.timestamp().try_into()?;
597 let sub_seconds: u64 = u64::from(other.timestamp_subsec_nanos());
598 let total = seconds * 10u64.pow(precision_u32) + sub_seconds / 10u64.pow(9 - precision_u32);
599 Ok(Self(UTC, total, precision))
600 }
601}
602
603impl<const PRECISION: usize> TryFrom<DateTime64<PRECISION>> for chrono::DateTime<Tz> {
604 type Error = TryFromIntError;
605
606 fn try_from(date: DateTime64<PRECISION>) -> Result<Self, TryFromIntError> {
607 #[expect(clippy::cast_possible_truncation)]
608 let precision = PRECISION as u32;
609 let seconds = date.1 / 10u64.pow(precision);
610 let units = date.1 % 10u64.pow(precision);
611 let units_ns = units * 10u64.pow(9 - precision);
612 Ok(date.0.timestamp_opt(seconds.try_into()?, units_ns.try_into()?).unwrap())
613 }
614}
615
616impl TryFrom<DynDateTime64> for chrono::DateTime<Tz> {
617 type Error = TryFromIntError;
618
619 fn try_from(date: DynDateTime64) -> Result<Self, TryFromIntError> {
620 #[expect(clippy::cast_possible_truncation)]
621 let date_2 = date.2 as u32;
622 let seconds = date.1 / 10u64.pow(date_2);
623 let units = date.1 % 10u64.pow(date_2);
624 let units_ns = units * 10u64.pow(9 - date_2);
625 Ok(date.0.timestamp_opt(seconds.try_into()?, units_ns.try_into()?).unwrap())
626 }
627}
628
629impl<const PRECISION: usize> TryFrom<chrono::DateTime<Tz>> for DateTime64<PRECISION> {
630 type Error = TryFromIntError;
631
632 fn try_from(other: chrono::DateTime<Tz>) -> Result<Self, TryFromIntError> {
633 let seconds: u64 = other.timestamp().try_into()?;
634 let sub_seconds: u64 = u64::from(other.timestamp_subsec_nanos());
635 #[expect(clippy::cast_possible_truncation)]
636 let total =
637 seconds * 10u64.pow(PRECISION as u32) + sub_seconds / 10u64.pow(9 - PRECISION as u32);
638 Ok(Self(other.timezone(), total))
639 }
640}
641
642impl DynDateTime64 {
643 pub fn try_from_tz(
647 other: chrono::DateTime<Tz>,
648 precision: usize,
649 ) -> Result<Self, TryFromIntError> {
650 let seconds: u64 = other.timestamp().try_into()?;
651 let sub_seconds: u64 = u64::from(other.timestamp_subsec_nanos());
652 #[expect(clippy::cast_possible_truncation)]
653 let total =
654 seconds * 10u64.pow(precision as u32) + sub_seconds / 10u64.pow(9 - precision as u32);
655 Ok(Self(other.timezone(), total, precision))
656 }
657}
658
659impl<const PRECISION: usize> TryFrom<DateTime64<PRECISION>> for chrono::DateTime<FixedOffset> {
660 type Error = TryFromIntError;
661
662 fn try_from(date: DateTime64<PRECISION>) -> Result<Self, TryFromIntError> {
663 #[expect(clippy::cast_possible_truncation)]
664 let precision = PRECISION as u32;
665 let seconds = date.1 / 10u64.pow(precision);
666 let units = date.1 % 10u64.pow(precision);
667 let units_ns = units * 10u64.pow(9 - precision);
668 Ok(date.0.timestamp_opt(seconds.try_into()?, units_ns.try_into()?).unwrap().fixed_offset())
669 }
670}
671
672impl TryFrom<DynDateTime64> for chrono::DateTime<FixedOffset> {
673 type Error = TryFromIntError;
674
675 fn try_from(date: DynDateTime64) -> Result<Self, TryFromIntError> {
676 #[expect(clippy::cast_possible_truncation)]
677 let date_2 = date.2 as u32;
678 let seconds = date.1 / 10u64.pow(date_2);
679 let units = date.1 % 10u64.pow(date_2);
680 let units_ns = units * 10u64.pow(9 - date_2);
681 Ok(date.0.timestamp_opt(seconds.try_into()?, units_ns.try_into()?).unwrap().fixed_offset())
682 }
683}
684
685#[cfg(test)]
686mod chrono_tests {
687 use chrono::TimeZone;
688 use chrono_tz::UTC;
689
690 use super::*;
691
692 #[test]
693 fn test_naivedate() {
694 for i in 0..30000u16 {
695 let date = Date(i);
696 let chrono_date: NaiveDate = date.into();
697 let new_date = Date::from(chrono_date);
698 assert_eq!(new_date, date);
699 }
700 }
701
702 #[test]
703 fn test_datetime() {
704 for i in (0..30000u32).map(|x| x * 10000) {
705 let date = DateTime(UTC, i);
706 let chrono_date: chrono::DateTime<Tz> = date.try_into().unwrap();
707 let new_date = DateTime::try_from(chrono_date).unwrap();
708 assert_eq!(new_date, date);
709 }
710 }
711
712 #[test]
713 fn test_datetime64() {
714 for i in (0..30000u64).map(|x| x * 10000) {
715 let date = DateTime64::<6>(UTC, i);
716 let chrono_date: chrono::DateTime<Tz> = date.try_into().unwrap();
717 let new_date = DateTime64::try_from(chrono_date).unwrap();
718 assert_eq!(new_date, date);
719 }
720 }
721
722 #[test]
723 fn test_datetime64_precision() {
724 for i in (0..30000u64).map(|x| x * 10000) {
725 let date = DateTime64::<6>(UTC, i);
726 let date_value = date.to_sql(None).unwrap();
727 assert_eq!(date_value, Value::DateTime64(DynDateTime64(UTC, i, 6)));
728 let chrono_date: chrono::DateTime<Utc> =
729 FromSql::from_sql(&Type::DateTime64(6, UTC), date_value).unwrap();
730 let new_date = DateTime64::try_from(chrono_date).unwrap();
731 assert_eq!(new_date, date);
732 }
733 }
734
735 #[test]
736 fn test_datetime64_precision2() {
737 for i in (0..300u64).map(|x| x * 1_000_000) {
738 #[expect(clippy::cast_possible_wrap)]
739 #[expect(clippy::cast_possible_truncation)]
740 let chrono_time = Utc.timestamp_opt(i as i64, i as u32).unwrap();
741 let date = chrono_time.to_sql(None).unwrap();
742 let out_time: chrono::DateTime<Utc> =
743 FromSql::from_sql(&Type::DateTime64(9, UTC), date.clone()).unwrap();
744 assert_eq!(chrono_time, out_time);
745 let date = match date {
746 Value::DateTime64(mut datetime) => {
747 datetime.2 -= 3;
748 datetime.1 /= 1000;
749 Value::DateTime64(datetime)
750 }
751 _ => unimplemented!(),
752 };
753 let out_time: chrono::DateTime<Utc> =
754 FromSql::from_sql(&Type::DateTime64(9, UTC), date.clone()).unwrap();
755
756 assert_eq!(chrono_time, out_time);
757 }
758 }
759
760 #[test]
761 fn test_from_seconds() {
762 let dt = DynDateTime64::from_seconds(1000, Some(Arc::from("UTC")));
763 assert_eq!(dt.0, Tz::UTC);
764 assert_eq!(dt.1, 1000);
765 assert_eq!(dt.2, 0);
766 }
767
768 #[test]
769 fn test_from_millis() {
770 let dt = DynDateTime64::from_millis(1000, Some(Arc::from("UTC")));
771 assert_eq!(dt.0, Tz::UTC);
772 assert_eq!(dt.1, 1000); assert_eq!(dt.2, 3);
774 }
775
776 #[test]
777 fn test_from_micros() {
778 let dt = DynDateTime64::from_micros(1_000_000, Some(Arc::from("UTC")));
779 assert_eq!(dt.0, Tz::UTC);
780 assert_eq!(dt.1, 1_000_000); assert_eq!(dt.2, 6);
782 }
783
784 #[test]
785 fn test_from_nanos() {
786 let dt = DynDateTime64::from_nanos(1_000_000_000, Some(Arc::from("UTC")));
787 assert_eq!(dt.0, Tz::UTC);
788 assert_eq!(dt.1, 1_000_000_000); assert_eq!(dt.2, 9);
790 }
791
792 #[test]
793 fn test_from_seconds_zero() {
794 let dt = DynDateTime64::from_seconds(0, None);
795 assert_eq!(dt.0, Tz::UTC);
796 assert_eq!(dt.1, 0);
797 assert_eq!(dt.2, 0);
798 }
799
800 #[test]
801 #[should_panic(expected = "DynDateTime64 does not support negative seconds: -1")]
802 fn test_from_seconds_negative() {
803 let _ = DynDateTime64::from_seconds(-1, Some(Arc::from("UTC")));
804 }
805
806 #[test]
807 #[should_panic(expected = "DynDateTime64 does not support negative milliseconds: -1000")]
808 fn test_from_millis_negative() {
809 let _ = DynDateTime64::from_millis(-1000, Some(Arc::from("UTC")));
810 }
811
812 #[test]
813 #[should_panic(expected = "DynDateTime64 does not support negative microseconds: -1000000")]
814 fn test_from_micros_negative() {
815 let _ = DynDateTime64::from_micros(-1_000_000, Some(Arc::from("UTC")));
816 }
817
818 #[test]
819 #[should_panic(expected = "DynDateTime64 does not support negative nanoseconds: -1000000000")]
820 fn test_from_nanos_negative() {
821 let _ = DynDateTime64::from_nanos(-1_000_000_000, Some(Arc::from("UTC")));
822 }
823
824 #[test]
825 fn test_from_millis_custom_tz() {
826 let dt = DynDateTime64::from_millis(1000, Some(Arc::from("America/New_York")));
827 assert_eq!(dt.0, Tz::America__New_York);
828 assert_eq!(dt.1, 1000);
829 assert_eq!(dt.2, 3);
830 }
831
832 #[test]
833 #[allow(deprecated)]
834 fn test_consistency_with_convert_for_str() {
835 let test_date = "2022-04-22 00:00:00";
836
837 let dt = chrono::NaiveDateTime::parse_from_str(test_date, "%Y-%m-%d %H:%M:%S").unwrap();
838
839 let chrono_date =
840 chrono::DateTime::<Tz>::from_utc(dt, chrono_tz::UTC.offset_from_utc_datetime(&dt));
841
842 #[expect(clippy::cast_possible_truncation)]
843 #[expect(clippy::cast_sign_loss)]
844 let date = DateTime(UTC, dt.timestamp() as u32);
845
846 let new_chrono_date: chrono::DateTime<Tz> = date.try_into().unwrap();
847
848 assert_eq!(new_chrono_date, chrono_date);
849 }
850}