clickhouse_arrow/native/values/
date.rs

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/// Wrapper type for `ClickHouse` `Date` type.
10#[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    /// # Panics
17    ///
18    /// Panics if the number of days is out of range for a `u16`.
19    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    /// # Panics
25    ///
26    /// Panics if the number of milliseconds is out of range for a `u16`.
27    pub fn from_millis(ms: i64) -> Self {
28        let days = ms / 86_400_000; // Milliseconds per day
29        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/// Wrapper type for `ClickHouse` `Date32` type.
87#[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    /// Creates a `Date32` from days since 1900-01-01.
93    pub fn from_days(days: i32) -> Self { Date32(days) }
94
95    /// Creates a `Date32` from milliseconds since 1970-01-01, adjusting to 1900-01-01 epoch.
96    pub fn from_millis(ms: i64) -> Self {
97        const DAYS_1900_TO_1970: i64 = 25_567; // Days from 1900-01-01 to 1970-01-01
98        let days = ms / 86_400_000; // Milliseconds per day
99        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/// Wrapper type for `ClickHouse` `DateTime` type.
156#[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    /// Infallible from implemenation for [`chrono::DateTime<Tz>`].
163    ///
164    /// IMPORTANT: This will truncate a value that is too large for the target type.
165    /// Use `TryFrom` for the fallible version.
166    #[must_use]
167    pub fn from_chrono_infallible(other: chrono::DateTime<Tz>) -> Self {
168        // Get the timestamp and convert directly to u32, letting the error bubble up
169        let timestamp = other.timestamp() as u32;
170        Self(other.timezone(), timestamp)
171    }
172
173    /// Infallible from implemenation for [`chrono::DateTime<Utc>`].
174    ///
175    /// IMPORTANT: This will truncate a value that is too large for the target type.
176    /// Use `TryFrom` for the fallible version.
177    #[must_use]
178    pub fn from_chrono_infallible_utc(other: chrono::DateTime<Utc>) -> Self {
179        // Get the timestamp and convert directly to u32, letting the error bubble up
180        let timestamp = other.timestamp() as u32;
181        Self(UTC, timestamp)
182    }
183
184    /// # Panics
185    ///
186    /// Panics if the number of seconds is out of range for a `u32`.
187    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    /// # Panics
197    ///
198    /// Panics if the number of milliseconds is out of range for a `u32`.
199    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    /// # Panics
209    ///
210    /// Panics if the number of microseconds is out of range for a `u32`.
211    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    /// # Panics
221    ///
222    /// Panics if the number of nanoseconds is out of range for a `u32`.
223    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        // Get the timestamp and convert directly to u32, letting the error bubble up
309        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        // Convert directly without recursion
319        let timestamp = u32::try_from(other.timestamp())?;
320
321        // For deserialization from RFC3339, we normalize to UTC
322        // This is the standard approach since RFC3339 represents a specific moment in time
323        // and we store that moment as a UTC timestamp with UTC timezone
324        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/// Wrapper type for `ClickHouse` `DateTime64` type.
337#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
338pub struct DateTime64<const PRECISION: usize>(pub Tz, pub u64);
339
340/// Wrapper type for `ClickHouse` `DateTime64` type with dynamic precision.
341#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
342pub struct DynDateTime64(pub Tz, pub u64, pub usize);
343
344// TODO: Remove all panics, return error
345#[expect(clippy::cast_sign_loss)]
346impl DynDateTime64 {
347    /// # Panics
348    ///
349    /// Panics if seconds is negative.
350    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) // Precision 0 for seconds
354    }
355
356    /// # Panics
357    ///
358    /// Panics if milliseconds is negative.
359    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) // Precision 3 for milliseconds
363    }
364
365    /// # Panics
366    ///
367    /// Panics if micros is negative.
368    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) // Precision 6 for microseconds
372    }
373
374    /// # Panics
375    ///
376    /// Panics if nanos is negative.
377    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) // Precision 9, adjust for ClickHouse
381    }
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    /// # Errors
594    ///
595    /// Returns an error if the timestamp cannot be converted to a u64.
596    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    /// # Errors
650    ///
651    /// Returns an error if the timestamp cannot be converted to a u64.
652    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        // Use a more realistic timestamp (Unix epoch + some time)
813        let original_date = DateTime(Tz::UTC, 1_640_995_200); // 2022-01-01 00:00:00 UTC
814        let serialized = serde_json::to_string(&original_date).unwrap();
815        let deserialized: DateTime = serde_json::from_str(&serialized).unwrap();
816
817        // The timestamp should be preserved, timezone normalized to UTC
818        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        // Test deserialization with invalid RFC3339 string
826        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        // Test the TryFrom implementations directly to ensure coverage
834        let dt = DateTime(Tz::UTC, 1_640_995_200);
835
836        // Test conversion to chrono::DateTime<Tz>
837        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        // Test conversion to chrono::DateTime<FixedOffset>
842        let chrono_fixed: chrono::DateTime<FixedOffset> = dt.try_into().unwrap();
843        let back_from_fixed: DateTime = chrono_fixed.try_into().unwrap();
844        // Note: timezone is normalized to UTC from FixedOffset
845        assert_eq!(back_from_fixed.1, dt.1); // timestamp should be same
846        assert_eq!(back_from_fixed.0, Tz::UTC); // timezone normalized
847
848        // Test conversion to chrono::DateTime<Utc>
849        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); // 1000 ms
927        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); // 1,000,000 µs
935        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); // 1,000,000,000 ns
943        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}