Skip to main content

tds_driver/tds/
time.rs

1//! Date and time handling.
2//!
3//! When using the `tds73` feature flag together with SQL Server 2008 or later,
4//! the following [`time`] mappings to and from the database are available:
5//!
6//! - `Time` -> [`Time`](time/struct.Time.html)
7//! - `Date` -> [`Date`]
8//! - `DateTime` -> [`PrimitiveDateTime`]
9//! - `DateTime2` -> [`PrimitiveDateTime`]
10//! - `SmallDateTime` -> [`PrimitiveDateTime`]
11//! - `DateTimeOffset` -> [`OffsetDateTime`]
12//!
13//! With SQL Server 2005 and the `tds73` feature flag disabled, the mapping is
14//! different:
15//!
16//! - `DateTime` -> [`PrimitiveDateTime`]
17//! - `SmallDateTime` -> [`PrimitiveDateTime`]
18//!
19//! [`time`]: time/index.html
20//! [`Date`]: time/struct.Date.html
21//! [`PrimitiveDateTime`]: time/struct.PrimitiveDateTime.html
22//! [`OffsetDateTime`]: time/struct.OffsetDateTime.html
23
24#[cfg(feature = "chrono")]
25#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
26pub mod chrono;
27
28#[cfg(feature = "time")]
29#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
30// Submodule intentionally shares the name of the `time` feature/crate it wraps.
31#[allow(clippy::module_inception)]
32pub mod time;
33
34use crate::{tds::codec::Encode, SqlReadBytes};
35#[cfg(feature = "tds73")]
36use byteorder::{ByteOrder, LittleEndian};
37use bytes::{BufMut, BytesMut};
38#[cfg(feature = "tds73")]
39use futures_util::io::AsyncReadExt;
40
41/// A presentation of `datetime` type in the server.
42///
43/// # Warning
44///
45/// It isn't recommended to use this type directly. For dealing with `datetime`,
46/// use the `time` feature of this crate and its `PrimitiveDateTime` type.
47#[derive(Copy, Clone, Debug, Eq, PartialEq)]
48#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
49pub struct DateTime {
50    days: i32,
51    seconds_fragments: u32,
52}
53
54impl DateTime {
55    /// What a chrono leap second converts to without `tds73`: `ToSql`
56    /// cannot fail, so the encoder rejects it.
57    pub(crate) const LEAP_SECOND: u32 = u32::MAX;
58
59    /// Construct a new `DateTime` instance.
60    pub fn new(days: i32, seconds_fragments: u32) -> Self {
61        Self {
62            days,
63            seconds_fragments,
64        }
65    }
66
67    /// Days since 1st of January, 1900 (including the negative range until 1st
68    /// of January, 1753).
69    pub fn days(self) -> i32 {
70        self.days
71    }
72
73    /// 1/300 of a second, so a value of 300 equals 1 second (since midnight).
74    pub fn seconds_fragments(self) -> u32 {
75        self.seconds_fragments
76    }
77
78    pub(crate) async fn decode<R>(src: &mut R) -> crate::Result<Self>
79    where
80        R: SqlReadBytes + Unpin,
81    {
82        let days = src.read_i32_le().await?;
83        let seconds_fragments = src.read_u32_le().await?;
84        let dt = Self {
85            days,
86            seconds_fragments,
87        };
88
89        dt.millis_of_day()
90            .map_err(|_| crate::Error::Protocol(format!("invalid datetime {dt:?}").into()))?;
91        Ok(dt)
92    }
93
94    /// Milliseconds since midnight, rounded half up as SqlClient's
95    /// `SqlDateTime` does (so `.997` stays `.997`, not `.996666…`).
96    pub(crate) fn millis_of_day(self) -> crate::Result<u32> {
97        const TICKS_PER_DAY: u32 = 300 * 86_400;
98
99        if self.seconds_fragments >= TICKS_PER_DAY {
100            return Err(crate::Error::Conversion(
101                format!(
102                    "datetime: {} ticks is past midnight",
103                    self.seconds_fragments
104                )
105                .into(),
106            ));
107        }
108
109        // ticks / 0.3: a third is never a tie.
110        Ok((self.seconds_fragments * 10 + 1) / 3)
111    }
112
113    /// Fails unless the server holds the value: under 24 hours.
114    pub(crate) fn validate(self) -> crate::Result<()> {
115        if self.seconds_fragments == Self::LEAP_SECOND {
116            return Err(crate::Error::Conversion(
117                "datetime: SQL Server has no leap seconds (second 60)".into(),
118            ));
119        }
120        self.millis_of_day().map(drop)
121    }
122}
123
124impl Encode<BytesMut> for DateTime {
125    fn encode(self, dst: &mut BytesMut) -> crate::Result<()> {
126        self.validate()?;
127        dst.put_i32_le(self.days);
128        dst.put_u32_le(self.seconds_fragments);
129
130        Ok(())
131    }
132}
133
134/// A presentation of `smalldatetime` type in the server.
135///
136/// # Warning
137///
138/// It isn't recommended to use this type directly. For dealing with
139/// `smalldatetime`, use the `time` feature of this crate and its
140/// `PrimitiveDateTime` type.
141#[derive(Copy, Clone, Debug, Eq, PartialEq)]
142#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
143pub struct SmallDateTime {
144    days: u16,
145    seconds_fragments: u16,
146}
147
148impl SmallDateTime {
149    /// Construct a new `SmallDateTime` instance.
150    pub fn new(days: u16, seconds_fragments: u16) -> Self {
151        Self {
152            days,
153            seconds_fragments,
154        }
155    }
156    /// Days since 1st of January, 1900.
157    pub fn days(self) -> u16 {
158        self.days
159    }
160
161    /// Minutes since midnight, 0 to 1439, despite the name.
162    pub fn seconds_fragments(self) -> u16 {
163        self.seconds_fragments
164    }
165
166    pub(crate) async fn decode<R>(src: &mut R) -> crate::Result<Self>
167    where
168        R: SqlReadBytes + Unpin,
169    {
170        let days = src.read_u16_le().await?;
171        let seconds_fragments = src.read_u16_le().await?;
172        let dt = Self {
173            days,
174            seconds_fragments,
175        };
176
177        dt.minutes_of_day()
178            .map_err(|_| crate::Error::Protocol(format!("invalid smalldatetime {dt:?}").into()))?;
179        Ok(dt)
180    }
181
182    /// Minutes since midnight; the wire field (`seconds_fragments`) holds minutes.
183    pub(crate) fn minutes_of_day(self) -> crate::Result<u32> {
184        match self.seconds_fragments {
185            m @ 0..=1439 => Ok(m as u32),
186            m => Err(crate::Error::Conversion(
187                format!("smalldatetime: {m} minutes is past midnight").into(),
188            )),
189        }
190    }
191
192    /// Fails unless the server holds the value: under 24 hours.
193    pub(crate) fn validate(self) -> crate::Result<()> {
194        self.minutes_of_day().map(drop)
195    }
196}
197
198impl Encode<BytesMut> for SmallDateTime {
199    fn encode(self, dst: &mut BytesMut) -> crate::Result<()> {
200        self.validate()?;
201        dst.put_u16_le(self.days);
202        dst.put_u16_le(self.seconds_fragments);
203
204        Ok(())
205    }
206}
207
208/// A presentation of `date` type in the server.
209///
210/// # Warning
211///
212/// It isn't recommended to use this type directly. If you want to deal with
213/// `date`, use the `time` feature of this crate and its `Date` type.
214#[derive(Copy, Clone, Debug, Eq, PartialEq)]
215#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
216#[cfg(feature = "tds73")]
217#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
218pub struct Date(u32);
219
220#[cfg(feature = "tds73")]
221#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
222impl Date {
223    #[inline]
224    /// Construct a new `Date`
225    ///
226    /// # Panics
227    /// max value of 3 bytes (`u32::max_value() > 8`)
228    pub fn new(days: u32) -> Date {
229        assert_eq!(days >> 24, 0);
230        Date(days)
231    }
232
233    #[inline]
234    /// The number of days from 1st of January, year 1.
235    pub fn days(self) -> u32 {
236        self.0
237    }
238
239    /// For `ToSql`, which cannot fail: a day count before year 1 becomes
240    /// `u32::MAX`, which the encoder rejects like any day past 9999-12-31.
241    #[cfg_attr(not(any(feature = "chrono", feature = "time")), allow(dead_code))]
242    pub(crate) fn from_signed_days(days: i64) -> Date {
243        Date(u32::try_from(days).unwrap_or(u32::MAX))
244    }
245
246    pub(crate) async fn decode<R>(src: &mut R) -> crate::Result<Self>
247    where
248        R: SqlReadBytes + Unpin,
249    {
250        let mut bytes = [0u8; 4];
251        src.read_exact(&mut bytes[..3]).await?;
252        Ok(Self::new(LittleEndian::read_u32(&bytes)))
253    }
254}
255
256#[cfg(feature = "tds73")]
257#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
258impl Encode<BytesMut> for Date {
259    fn encode(self, dst: &mut BytesMut) -> crate::Result<()> {
260        // 9999-12-31. The server rejects later dates, including a
261        // datetimeoffset whose UTC date is later.
262        const MAX_DAYS: u32 = 3_652_058;
263
264        if self.days() > MAX_DAYS {
265            return Err(crate::Error::Conversion(
266                "date out of range: SQL Server dates run from 0001-01-01 to 9999-12-31".into(),
267            ));
268        }
269
270        let mut tmp = [0u8; 4];
271        LittleEndian::write_u32(&mut tmp, self.days());
272        dst.extend_from_slice(&tmp[0..3]);
273
274        Ok(())
275    }
276}
277
278/// A presentation of `time` type in the server.
279///
280/// # Warning
281///
282/// It isn't recommended to use this type directly. If you want to deal with
283/// `time`, use the `time` feature of this crate and its `Time` type.
284#[derive(Copy, Clone, Debug)]
285#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
286#[cfg(feature = "tds73")]
287#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
288pub struct Time {
289    increments: u64,
290    scale: u8,
291}
292
293#[cfg(feature = "tds73")]
294#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
295impl PartialEq for Time {
296    fn eq(&self, t: &Time) -> bool {
297        self.increments as f64 / 10f64.powi(self.scale as i32)
298            == t.increments as f64 / 10f64.powi(t.scale as i32)
299    }
300}
301
302#[cfg(feature = "tds73")]
303#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
304impl Time {
305    /// What a chrono leap second converts to: `ToSql` cannot fail, so the
306    /// encoder rejects it.
307    pub(crate) const LEAP_SECOND: u64 = u64::MAX;
308
309    /// Construct a new `Time`
310    pub fn new(increments: u64, scale: u8) -> Self {
311        Self { increments, scale }
312    }
313
314    /// Fails unless the server holds the value: under 24 hours, scale 0 to 7.
315    pub(crate) fn validate(self) -> crate::Result<()> {
316        self.len()?;
317
318        if self.increments < 86_400 * 10u64.pow(u32::from(self.scale)) {
319            return Ok(());
320        }
321
322        Err(crate::Error::Conversion(match self.increments {
323            Self::LEAP_SECOND => "time: SQL Server has no leap seconds (second 60)".into(),
324            n => format!(
325                "time out of range: {n} increments of 10^-{} s is 24 hours or more",
326                self.scale
327            )
328            .into(),
329        }))
330    }
331
332    #[inline]
333    /// Number of 10^-n second increments since midnight, where `n` is defined
334    /// in [`scale`].
335    ///
336    /// [`scale`]: #method.scale
337    pub fn increments(self) -> u64 {
338        self.increments
339    }
340
341    #[inline]
342    /// The accuracy of the increments.
343    pub fn scale(self) -> u8 {
344        self.scale
345    }
346
347    #[inline]
348    /// Length of the field in number of bytes.
349    pub(crate) fn len(self) -> crate::Result<u8> {
350        Ok(match self.scale {
351            0..=2 => 3,
352            3..=4 => 4,
353            5..=7 => 5,
354            _ => {
355                return Err(crate::Error::Protocol(
356                    format!("timen: invalid scale {}", self.scale).into(),
357                ))
358            }
359        })
360    }
361
362    pub(crate) async fn decode<R>(src: &mut R, n: usize, rlen: usize) -> crate::Result<Time>
363    where
364        R: SqlReadBytes + Unpin,
365    {
366        let val = match (n, rlen) {
367            (0..=2, 3) => {
368                let hi = src.read_u16_le().await? as u64;
369                let lo = src.read_u8().await? as u64;
370
371                hi | lo << 16
372            }
373            (3..=4, 4) => src.read_u32_le().await? as u64,
374            (5..=7, 5) => {
375                let hi = src.read_u32_le().await? as u64;
376                let lo = src.read_u8().await? as u64;
377
378                hi | lo << 32
379            }
380            _ => {
381                return Err(crate::Error::Protocol(
382                    format!("timen: invalid length {}", n).into(),
383                ))
384            }
385        };
386
387        Ok(Time {
388            increments: val,
389            scale: n as u8,
390        })
391    }
392}
393
394#[cfg(feature = "tds73")]
395#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
396impl Encode<BytesMut> for Time {
397    fn encode(self, dst: &mut BytesMut) -> crate::Result<()> {
398        self.validate()?;
399
400        // Under 24 hours, the increments fit the scale's 3, 4 or 5 bytes.
401        let len = usize::from(self.len()?);
402        dst.extend_from_slice(&self.increments.to_le_bytes()[..len]);
403
404        Ok(())
405    }
406}
407
408#[derive(Copy, Clone, Debug, PartialEq)]
409#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
410#[cfg(feature = "tds73")]
411#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
412/// A presentation of `datetime2` type in the server.
413///
414/// # Warning
415///
416/// It isn't recommended to use this type directly. For dealing with
417/// `datetime2`, use the `time` feature of this crate and its `PrimitiveDateTime`
418/// type.
419pub struct DateTime2 {
420    date: Date,
421    time: Time,
422}
423
424#[cfg(feature = "tds73")]
425#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
426impl DateTime2 {
427    /// Construct a new `DateTime2` from the date and time components.
428    pub fn new(date: Date, time: Time) -> Self {
429        Self { date, time }
430    }
431
432    /// The date component.
433    pub fn date(self) -> Date {
434        self.date
435    }
436
437    /// The time component.
438    pub fn time(self) -> Time {
439        self.time
440    }
441
442    pub(crate) async fn decode<R>(src: &mut R, n: usize, rlen: usize) -> crate::Result<Self>
443    where
444        R: SqlReadBytes + Unpin,
445    {
446        let time = Time::decode(src, n, rlen).await?;
447
448        let mut bytes = [0u8; 4];
449        src.read_exact(&mut bytes[..3]).await?;
450        let date = Date::new(LittleEndian::read_u32(&bytes));
451
452        Ok(Self::new(date, time))
453    }
454}
455
456#[cfg(feature = "tds73")]
457#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
458impl Encode<BytesMut> for DateTime2 {
459    fn encode(self, dst: &mut BytesMut) -> crate::Result<()> {
460        self.time.encode(dst)?;
461        self.date.encode(dst)
462    }
463}
464
465#[derive(Copy, Clone, Debug, PartialEq)]
466#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
467#[cfg(feature = "tds73")]
468#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
469/// A presentation of `datetimeoffset` type in the server.
470///
471/// # Warning
472///
473/// It isn't recommended to use this type directly. For dealing with
474/// `datetimeoffset`, use the `time` feature of this crate and its `OffsetDateTime`
475/// type with the correct timezone.
476pub struct DateTimeOffset {
477    datetime2: DateTime2,
478    offset: i16,
479}
480
481#[cfg(feature = "tds73")]
482#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
483impl DateTimeOffset {
484    /// What a chrono or `time` offset with seconds converts to: `ToSql`
485    /// cannot fail, so the encoder rejects it.
486    pub(crate) const OFFSET_WITH_SECONDS: i16 = i16::MIN;
487
488    /// Construct a new `DateTimeOffset` from a `datetime2`, offset marking
489    /// number of minutes from UTC.
490    pub fn new(datetime2: DateTime2, offset: i16) -> Self {
491        Self { datetime2, offset }
492    }
493
494    /// The date and time part.
495    pub fn datetime2(self) -> DateTime2 {
496        self.datetime2
497    }
498
499    /// Number of minutes from UTC.
500    pub fn offset(self) -> i16 {
501        self.offset
502    }
503
504    pub(crate) async fn decode<R>(src: &mut R, n: usize, rlen: u8) -> crate::Result<Self>
505    where
506        R: SqlReadBytes + Unpin,
507    {
508        let datetime2 = DateTime2::decode(src, n, rlen as usize).await?;
509        let offset = src.read_i16_le().await?;
510
511        Ok(Self { datetime2, offset })
512    }
513}
514
515#[cfg(feature = "tds73")]
516#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
517impl Encode<BytesMut> for DateTimeOffset {
518    fn encode(self, dst: &mut BytesMut) -> crate::Result<()> {
519        // MS-TDS 2.2.5.5.1.8: the offset MUST be within -840..=840 minutes.
520        match self.offset {
521            -840..=840 => {}
522            Self::OFFSET_WITH_SECONDS => {
523                return Err(crate::Error::Conversion(
524                    "datetimeoffset: the UTC offset has seconds; SQL Server stores whole minutes"
525                        .into(),
526                ))
527            }
528            o => {
529                return Err(crate::Error::Conversion(
530                    format!("datetimeoffset: offset of {o} minutes is outside -14:00 to +14:00")
531                        .into(),
532                ))
533            }
534        }
535
536        self.datetime2.encode(dst)?;
537        dst.put_i16_le(self.offset);
538
539        Ok(())
540    }
541}
542
543#[cfg(test)]
544mod tests {
545    use super::*;
546    use crate::sql_read_bytes::test_utils::IntoSqlReadBytes;
547
548    #[test]
549    fn datetime_accessors() {
550        let dt = DateTime::new(-100, 12345);
551        assert_eq!(dt.days(), -100);
552        assert_eq!(dt.seconds_fragments(), 12345);
553    }
554
555    #[tokio::test]
556    async fn datetime_round_trip_including_pre_1900() {
557        for dt in [
558            DateTime::new(0, 0),
559            DateTime::new(200, 3000),
560            // 1753-01-01 and 9999-12-31 23:59:59.997, the type's bounds.
561            DateTime::new(-53690, 0),
562            DateTime::new(2958463, 25919999),
563        ] {
564            let mut buf = BytesMut::new();
565            dt.encode(&mut buf).unwrap();
566            let decoded = DateTime::decode(&mut buf.into_sql_read_bytes())
567                .await
568                .unwrap();
569            assert_eq!(decoded, dt);
570        }
571    }
572
573    #[tokio::test]
574    async fn decode_rejects_a_time_past_midnight() {
575        let mut buf = BytesMut::new();
576        buf.put_i32_le(0);
577        buf.put_u32_le(25920000);
578        let err = DateTime::decode(&mut buf.into_sql_read_bytes()).await;
579        assert!(matches!(err, Err(crate::Error::Protocol(_))), "{err:?}");
580
581        let mut buf = BytesMut::new();
582        buf.put_u16_le(0);
583        buf.put_u16_le(1440);
584        let err = SmallDateTime::decode(&mut buf.into_sql_read_bytes()).await;
585        assert!(matches!(err, Err(crate::Error::Protocol(_))), "{err:?}");
586    }
587
588    /// The server rejects them as parameters (8023).
589    #[test]
590    fn encode_rejects_a_time_past_midnight() {
591        for dt in [DateTime::new(0, 25920000), DateTime::new(0, u32::MAX)] {
592            let err = dt.encode(&mut BytesMut::new()).unwrap_err();
593            assert!(matches!(err, crate::Error::Conversion(_)), "{dt:?}");
594        }
595        let err = SmallDateTime::new(0, 1440).encode(&mut BytesMut::new());
596        assert!(matches!(err, Err(crate::Error::Conversion(_))), "{err:?}");
597
598        DateTime::new(0, 25919999)
599            .encode(&mut BytesMut::new())
600            .unwrap();
601        SmallDateTime::new(0, 1439)
602            .encode(&mut BytesMut::new())
603            .unwrap();
604    }
605
606    #[test]
607    fn datetime_millis_round_half_up() {
608        let ms = |ticks| DateTime::new(0, ticks).millis_of_day().unwrap();
609        // .003, .007, .010 and .997 are stored as 1, 2, 3 and 299 ticks.
610        assert_eq!(ms(1), 3);
611        assert_eq!(ms(2), 7);
612        assert_eq!(ms(3), 10);
613        assert_eq!(ms(299), 997);
614        assert_eq!(ms(25919999), 86_399_997);
615        assert!(DateTime::new(0, 25920000).millis_of_day().is_err());
616        assert!(DateTime::new(0, u32::MAX).millis_of_day().is_err());
617    }
618
619    #[test]
620    fn smalldatetime_accessors() {
621        let dt = SmallDateTime::new(100, 200);
622        assert_eq!(dt.days(), 100);
623        assert_eq!(dt.seconds_fragments(), 200);
624    }
625
626    #[tokio::test]
627    async fn smalldatetime_round_trip() {
628        let dt = SmallDateTime::new(65535, 1439);
629        let mut buf = BytesMut::new();
630        dt.encode(&mut buf).unwrap();
631        let decoded = SmallDateTime::decode(&mut buf.into_sql_read_bytes())
632            .await
633            .unwrap();
634        assert_eq!(decoded, dt);
635    }
636
637    #[cfg(feature = "tds73")]
638    #[test]
639    fn date_accessor_and_new() {
640        let date = Date::new(730119);
641        assert_eq!(date.days(), 730119);
642    }
643
644    #[cfg(feature = "tds73")]
645    #[test]
646    #[should_panic]
647    fn date_new_panics_on_overflow() {
648        // Anything not representable in three bytes must panic.
649        Date::new(0x0100_0000);
650    }
651
652    #[cfg(feature = "tds73")]
653    #[tokio::test]
654    async fn date_round_trip() {
655        // 3_652_058 is 9999-12-31.
656        for days in [0u32, 1, 730119, 3_652_058] {
657            let date = Date::new(days);
658            let mut buf = BytesMut::new();
659            date.encode(&mut buf).unwrap();
660            assert_eq!(buf.len(), 3);
661            let decoded = Date::decode(&mut buf.into_sql_read_bytes()).await.unwrap();
662            assert_eq!(decoded, date);
663        }
664    }
665
666    #[cfg(feature = "tds73")]
667    #[test]
668    fn dates_outside_year_1_to_9999_are_encode_errors() {
669        // Past 9999-12-31, the top of three bytes, and a day before year 1.
670        for date in [
671            Date::new(3_652_059),
672            Date::new(0x00ff_ffff),
673            Date::from_signed_days(-1),
674        ] {
675            let err = date.encode(&mut BytesMut::new()).unwrap_err();
676            assert!(matches!(err, crate::Error::Conversion(_)), "{date:?}");
677
678            let dt2 = DateTime2::new(date, Time::new(0, 7));
679            let err = dt2.encode(&mut BytesMut::new()).unwrap_err();
680            assert!(matches!(err, crate::Error::Conversion(_)), "{date:?}");
681        }
682    }
683
684    #[cfg(feature = "tds73")]
685    #[test]
686    fn time_accessors_and_len() {
687        let time = Time::new(1234, 5);
688        assert_eq!(time.increments(), 1234);
689        assert_eq!(time.scale(), 5);
690        assert_eq!(time.len().unwrap(), 5);
691
692        assert_eq!(Time::new(0, 0).len().unwrap(), 3);
693        assert_eq!(Time::new(0, 3).len().unwrap(), 4);
694        assert!(Time::new(0, 8).len().is_err());
695    }
696
697    #[cfg(feature = "tds73")]
698    #[test]
699    fn time_partial_eq_across_scales() {
700        // 1 second expressed at two different scales must compare equal.
701        assert_eq!(Time::new(100, 2), Time::new(10_000_000, 7));
702        assert_ne!(Time::new(100, 2), Time::new(200, 2));
703    }
704
705    #[cfg(feature = "tds73")]
706    #[tokio::test]
707    async fn time_round_trip_all_len_buckets() {
708        for (increments, scale) in [(255u64, 2u8), (65535, 4), (16_777_215, 7)] {
709            let time = Time::new(increments, scale);
710            let rlen = time.len().unwrap();
711            let mut buf = BytesMut::new();
712            time.encode(&mut buf).unwrap();
713            let decoded = Time::decode(
714                &mut buf.into_sql_read_bytes(),
715                scale as usize,
716                rlen as usize,
717            )
718            .await
719            .unwrap();
720            assert_eq!(decoded, time);
721        }
722    }
723
724    #[cfg(feature = "tds73")]
725    #[tokio::test]
726    async fn time_round_trip_high_bytes_set() {
727        // Values whose most-significant byte (the byte handled by the
728        // `lo << 16` / `lo << 32` shift in `decode` and the `>> 16` / `>> 32`
729        // shift in `encode`) is non-zero. This distinguishes:
730        //   * decode `<< N` from `>> N` (the latter zeroes an `u8`), and
731        //   * encode `>> N` from `<< N` (the latter zeroes the byte written).
732        // The 16-bit / 32-bit low halves and the shifted high byte occupy
733        // disjoint bit ranges, so `|` vs `^` cannot be distinguished here.
734        // Both are under 24 hours.
735        for (increments, scale) in [(0x0083_1234u64, 2u8), (0x00AB_1234_5678u64, 7)] {
736            let time = Time::new(increments, scale);
737            let rlen = time.len().unwrap();
738
739            let mut buf = BytesMut::new();
740            time.encode(&mut buf).unwrap();
741
742            let decoded = Time::decode(
743                &mut buf.into_sql_read_bytes(),
744                scale as usize,
745                rlen as usize,
746            )
747            .await
748            .unwrap();
749
750            assert_eq!(decoded, time);
751            assert_eq!(decoded.increments(), increments);
752        }
753    }
754
755    #[cfg(feature = "tds73")]
756    #[test]
757    fn times_from_24_hours_up_are_encode_errors() {
758        // Over the field's 3/4/5 bytes (these asserted), within them but at or
759        // past 24 h, and the chrono leap second.
760        for time in [
761            Time::new(1 << 24, 0),
762            Time::new(1 << 32, 4),
763            Time::new(u64::MAX, 7),
764            Time::new(86_400, 0),
765            Time::new(8_640_000, 2),
766            Time::new(864_000_000_000, 7),
767            Time::new(Time::LEAP_SECOND, 7),
768        ] {
769            let err = time.encode(&mut BytesMut::new()).unwrap_err();
770            assert!(matches!(err, crate::Error::Conversion(_)), "{time:?}");
771
772            let dt2 = DateTime2::new(Date::new(1), time);
773            let err = dt2.encode(&mut BytesMut::new()).unwrap_err();
774            assert!(matches!(err, crate::Error::Conversion(_)), "{time:?}");
775        }
776
777        for scale in 0..=7 {
778            let last = 86_400 * 10u64.pow(scale) - 1;
779            Time::new(last, scale as u8)
780                .encode(&mut BytesMut::new())
781                .unwrap();
782        }
783    }
784
785    #[cfg(feature = "tds73")]
786    #[test]
787    fn datetimeoffset_offsets_beyond_14_hours_are_encode_errors() {
788        let dt2 = DateTime2::new(Date::new(1), Time::new(0, 7));
789        for offset in [-841, 841, i16::MAX, DateTimeOffset::OFFSET_WITH_SECONDS] {
790            let err = DateTimeOffset::new(dt2, offset)
791                .encode(&mut BytesMut::new())
792                .unwrap_err();
793            assert!(matches!(err, crate::Error::Conversion(_)), "{offset}");
794        }
795        for offset in [-840, 0, 840] {
796            DateTimeOffset::new(dt2, offset)
797                .encode(&mut BytesMut::new())
798                .unwrap();
799        }
800    }
801
802    #[cfg(feature = "tds73")]
803    #[tokio::test]
804    async fn time_decode_invalid_length_errors() {
805        let mut buf = BytesMut::new();
806        buf.put_u8(0);
807        // scale/length combination not one of the accepted pairs.
808        let err = Time::decode(&mut buf.into_sql_read_bytes(), 0, 4).await;
809        assert!(err.is_err());
810    }
811
812    #[cfg(feature = "tds73")]
813    #[tokio::test]
814    async fn datetime2_round_trip_and_accessors() {
815        let dt2 = DateTime2::new(Date::new(730119), Time::new(222, 7));
816        assert_eq!(dt2.date(), Date::new(730119));
817        assert_eq!(dt2.time(), Time::new(222, 7));
818
819        let rlen = dt2.time().len().unwrap();
820        let mut buf = BytesMut::new();
821        dt2.encode(&mut buf).unwrap();
822        let decoded = DateTime2::decode(&mut buf.into_sql_read_bytes(), 7, rlen as usize)
823            .await
824            .unwrap();
825        assert_eq!(decoded, dt2);
826    }
827
828    #[cfg(feature = "tds73")]
829    #[tokio::test]
830    async fn datetimeoffset_round_trip_and_accessors() {
831        let dt2 = DateTime2::new(Date::new(730119), Time::new(222, 7));
832        let dto = DateTimeOffset::new(dt2, -120);
833        assert_eq!(dto.datetime2(), dt2);
834        assert_eq!(dto.offset(), -120);
835
836        let rlen = dto.datetime2().time().len().unwrap();
837        let mut buf = BytesMut::new();
838        dto.encode(&mut buf).unwrap();
839        let decoded = DateTimeOffset::decode(&mut buf.into_sql_read_bytes(), 7, rlen)
840            .await
841            .unwrap();
842        assert_eq!(decoded, dto);
843    }
844}