Skip to main content

tiberius/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    /// Construct a new `DateTime` instance.
56    pub fn new(days: i32, seconds_fragments: u32) -> Self {
57        Self {
58            days,
59            seconds_fragments,
60        }
61    }
62
63    /// Days since 1st of January, 1900 (including the negative range until 1st
64    /// of January, 1753).
65    pub fn days(self) -> i32 {
66        self.days
67    }
68
69    /// 1/300 of a second, so a value of 300 equals 1 second (since midnight).
70    pub fn seconds_fragments(self) -> u32 {
71        self.seconds_fragments
72    }
73
74    pub(crate) async fn decode<R>(src: &mut R) -> crate::Result<Self>
75    where
76        R: SqlReadBytes + Unpin,
77    {
78        let days = src.read_i32_le().await?;
79        let seconds_fragments = src.read_u32_le().await?;
80
81        Ok(Self {
82            days,
83            seconds_fragments,
84        })
85    }
86}
87
88impl Encode<BytesMut> for DateTime {
89    fn encode(self, dst: &mut BytesMut) -> crate::Result<()> {
90        dst.put_i32_le(self.days);
91        dst.put_u32_le(self.seconds_fragments);
92
93        Ok(())
94    }
95}
96
97/// A presentation of `smalldatetime` type in the server.
98///
99/// # Warning
100///
101/// It isn't recommended to use this type directly. For dealing with
102/// `smalldatetime`, use the `time` feature of this crate and its
103/// `PrimitiveDateTime` type.
104#[derive(Copy, Clone, Debug, Eq, PartialEq)]
105#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
106pub struct SmallDateTime {
107    days: u16,
108    seconds_fragments: u16,
109}
110
111impl SmallDateTime {
112    /// Construct a new `SmallDateTime` instance.
113    pub fn new(days: u16, seconds_fragments: u16) -> Self {
114        Self {
115            days,
116            seconds_fragments,
117        }
118    }
119    /// Days since 1st of January, 1900.
120    pub fn days(self) -> u16 {
121        self.days
122    }
123
124    /// 1/300 of a second, so a value of 300 equals 1 second (since midnight)
125    pub fn seconds_fragments(self) -> u16 {
126        self.seconds_fragments
127    }
128
129    pub(crate) async fn decode<R>(src: &mut R) -> crate::Result<Self>
130    where
131        R: SqlReadBytes + Unpin,
132    {
133        let days = src.read_u16_le().await?;
134        let seconds_fragments = src.read_u16_le().await?;
135
136        Ok(Self {
137            days,
138            seconds_fragments,
139        })
140    }
141}
142
143impl Encode<BytesMut> for SmallDateTime {
144    fn encode(self, dst: &mut BytesMut) -> crate::Result<()> {
145        dst.put_u16_le(self.days);
146        dst.put_u16_le(self.seconds_fragments);
147
148        Ok(())
149    }
150}
151
152/// A presentation of `date` type in the server.
153///
154/// # Warning
155///
156/// It isn't recommended to use this type directly. If you want to deal with
157/// `date`, use the `time` feature of this crate and its `Date` type.
158#[derive(Copy, Clone, Debug, Eq, PartialEq)]
159#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
160#[cfg(feature = "tds73")]
161#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
162pub struct Date(u32);
163
164#[cfg(feature = "tds73")]
165#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
166impl Date {
167    #[inline]
168    /// Construct a new `Date`
169    ///
170    /// # Panics
171    /// max value of 3 bytes (`u32::max_value() > 8`)
172    pub fn new(days: u32) -> Date {
173        assert_eq!(days >> 24, 0);
174        Date(days)
175    }
176
177    #[inline]
178    /// The number of days from 1st of January, year 1.
179    pub fn days(self) -> u32 {
180        self.0
181    }
182
183    pub(crate) async fn decode<R>(src: &mut R) -> crate::Result<Self>
184    where
185        R: SqlReadBytes + Unpin,
186    {
187        let mut bytes = [0u8; 4];
188        src.read_exact(&mut bytes[..3]).await?;
189        Ok(Self::new(LittleEndian::read_u32(&bytes)))
190    }
191}
192
193#[cfg(feature = "tds73")]
194#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
195impl Encode<BytesMut> for Date {
196    fn encode(self, dst: &mut BytesMut) -> crate::Result<()> {
197        let mut tmp = [0u8; 4];
198        LittleEndian::write_u32(&mut tmp, self.days());
199        assert_eq!(tmp[3], 0);
200        dst.extend_from_slice(&tmp[0..3]);
201
202        Ok(())
203    }
204}
205
206/// A presentation of `time` type in the server.
207///
208/// # Warning
209///
210/// It isn't recommended to use this type directly. If you want to deal with
211/// `time`, use the `time` feature of this crate and its `Time` type.
212#[derive(Copy, Clone, Debug)]
213#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
214#[cfg(feature = "tds73")]
215#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
216pub struct Time {
217    increments: u64,
218    scale: u8,
219}
220
221#[cfg(feature = "tds73")]
222#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
223impl PartialEq for Time {
224    fn eq(&self, t: &Time) -> bool {
225        self.increments as f64 / 10f64.powi(self.scale as i32)
226            == t.increments as f64 / 10f64.powi(t.scale as i32)
227    }
228}
229
230#[cfg(feature = "tds73")]
231#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
232impl Time {
233    /// Construct a new `Time`
234    pub fn new(increments: u64, scale: u8) -> Self {
235        Self { increments, scale }
236    }
237
238    #[inline]
239    /// Number of 10^-n second increments since midnight, where `n` is defined
240    /// in [`scale`].
241    ///
242    /// [`scale`]: #method.scale
243    pub fn increments(self) -> u64 {
244        self.increments
245    }
246
247    #[inline]
248    /// The accuracy of the increments.
249    pub fn scale(self) -> u8 {
250        self.scale
251    }
252
253    #[inline]
254    /// Length of the field in number of bytes.
255    pub(crate) fn len(self) -> crate::Result<u8> {
256        Ok(match self.scale {
257            0..=2 => 3,
258            3..=4 => 4,
259            5..=7 => 5,
260            _ => {
261                return Err(crate::Error::Protocol(
262                    format!("timen: invalid scale {}", self.scale).into(),
263                ))
264            }
265        })
266    }
267
268    pub(crate) async fn decode<R>(src: &mut R, n: usize, rlen: usize) -> crate::Result<Time>
269    where
270        R: SqlReadBytes + Unpin,
271    {
272        let val = match (n, rlen) {
273            (0..=2, 3) => {
274                let hi = src.read_u16_le().await? as u64;
275                let lo = src.read_u8().await? as u64;
276
277                hi | lo << 16
278            }
279            (3..=4, 4) => src.read_u32_le().await? as u64,
280            (5..=7, 5) => {
281                let hi = src.read_u32_le().await? as u64;
282                let lo = src.read_u8().await? as u64;
283
284                hi | lo << 32
285            }
286            _ => {
287                return Err(crate::Error::Protocol(
288                    format!("timen: invalid length {}", n).into(),
289                ))
290            }
291        };
292
293        Ok(Time {
294            increments: val,
295            scale: n as u8,
296        })
297    }
298}
299
300#[cfg(feature = "tds73")]
301#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
302impl Encode<BytesMut> for Time {
303    fn encode(self, dst: &mut BytesMut) -> crate::Result<()> {
304        match self.len()? {
305            3 => {
306                assert_eq!(self.increments >> 24, 0);
307                dst.put_u16_le(self.increments as u16);
308                dst.put_u8((self.increments >> 16) as u8);
309            }
310            4 => {
311                assert_eq!(self.increments >> 32, 0);
312                dst.put_u32_le(self.increments as u32);
313            }
314            5 => {
315                assert_eq!(self.increments >> 40, 0);
316                dst.put_u32_le(self.increments as u32);
317                dst.put_u8((self.increments >> 32) as u8);
318            }
319            _ => unreachable!(),
320        }
321
322        Ok(())
323    }
324}
325
326#[derive(Copy, Clone, Debug, PartialEq)]
327#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
328#[cfg(feature = "tds73")]
329#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
330/// A presentation of `datetime2` type in the server.
331///
332/// # Warning
333///
334/// It isn't recommended to use this type directly. For dealing with
335/// `datetime2`, use the `time` feature of this crate and its `PrimitiveDateTime`
336/// type.
337pub struct DateTime2 {
338    date: Date,
339    time: Time,
340}
341
342#[cfg(feature = "tds73")]
343#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
344impl DateTime2 {
345    /// Construct a new `DateTime2` from the date and time components.
346    pub fn new(date: Date, time: Time) -> Self {
347        Self { date, time }
348    }
349
350    /// The date component.
351    pub fn date(self) -> Date {
352        self.date
353    }
354
355    /// The time component.
356    pub fn time(self) -> Time {
357        self.time
358    }
359
360    pub(crate) async fn decode<R>(src: &mut R, n: usize, rlen: usize) -> crate::Result<Self>
361    where
362        R: SqlReadBytes + Unpin,
363    {
364        let time = Time::decode(src, n, rlen).await?;
365
366        let mut bytes = [0u8; 4];
367        src.read_exact(&mut bytes[..3]).await?;
368        let date = Date::new(LittleEndian::read_u32(&bytes));
369
370        Ok(Self::new(date, time))
371    }
372}
373
374#[cfg(feature = "tds73")]
375#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
376impl Encode<BytesMut> for DateTime2 {
377    fn encode(self, dst: &mut BytesMut) -> crate::Result<()> {
378        self.time.encode(dst)?;
379
380        let mut tmp = [0u8; 4];
381        LittleEndian::write_u32(&mut tmp, self.date.days());
382        assert_eq!(tmp[3], 0);
383        dst.extend_from_slice(&tmp[0..3]);
384
385        Ok(())
386    }
387}
388
389#[derive(Copy, Clone, Debug, PartialEq)]
390#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
391#[cfg(feature = "tds73")]
392#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
393/// A presentation of `datetimeoffset` type in the server.
394///
395/// # Warning
396///
397/// It isn't recommended to use this type directly. For dealing with
398/// `datetimeoffset`, use the `time` feature of this crate and its `OffsetDateTime`
399/// type with the correct timezone.
400pub struct DateTimeOffset {
401    datetime2: DateTime2,
402    offset: i16,
403}
404
405#[cfg(feature = "tds73")]
406#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
407impl DateTimeOffset {
408    /// Construct a new `DateTimeOffset` from a `datetime2`, offset marking
409    /// number of minutes from UTC.
410    pub fn new(datetime2: DateTime2, offset: i16) -> Self {
411        Self { datetime2, offset }
412    }
413
414    /// The date and time part.
415    pub fn datetime2(self) -> DateTime2 {
416        self.datetime2
417    }
418
419    /// Number of minutes from UTC.
420    pub fn offset(self) -> i16 {
421        self.offset
422    }
423
424    pub(crate) async fn decode<R>(src: &mut R, n: usize, rlen: u8) -> crate::Result<Self>
425    where
426        R: SqlReadBytes + Unpin,
427    {
428        let datetime2 = DateTime2::decode(src, n, rlen as usize).await?;
429        let offset = src.read_i16_le().await?;
430
431        Ok(Self { datetime2, offset })
432    }
433}
434
435#[cfg(feature = "tds73")]
436#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
437impl Encode<BytesMut> for DateTimeOffset {
438    fn encode(self, dst: &mut BytesMut) -> crate::Result<()> {
439        self.datetime2.encode(dst)?;
440        dst.put_i16_le(self.offset);
441
442        Ok(())
443    }
444}
445
446#[cfg(test)]
447mod tests {
448    use super::*;
449    use crate::sql_read_bytes::test_utils::IntoSqlReadBytes;
450
451    #[test]
452    fn datetime_accessors() {
453        let dt = DateTime::new(-100, 12345);
454        assert_eq!(dt.days(), -100);
455        assert_eq!(dt.seconds_fragments(), 12345);
456    }
457
458    #[tokio::test]
459    async fn datetime_round_trip_including_pre_1900() {
460        for dt in [
461            DateTime::new(0, 0),
462            DateTime::new(200, 3000),
463            DateTime::new(-53690, 25920000),
464        ] {
465            let mut buf = BytesMut::new();
466            dt.encode(&mut buf).unwrap();
467            let decoded = DateTime::decode(&mut buf.into_sql_read_bytes())
468                .await
469                .unwrap();
470            assert_eq!(decoded, dt);
471        }
472    }
473
474    #[test]
475    fn smalldatetime_accessors() {
476        let dt = SmallDateTime::new(100, 200);
477        assert_eq!(dt.days(), 100);
478        assert_eq!(dt.seconds_fragments(), 200);
479    }
480
481    #[tokio::test]
482    async fn smalldatetime_round_trip() {
483        let dt = SmallDateTime::new(65535, 1439);
484        let mut buf = BytesMut::new();
485        dt.encode(&mut buf).unwrap();
486        let decoded = SmallDateTime::decode(&mut buf.into_sql_read_bytes())
487            .await
488            .unwrap();
489        assert_eq!(decoded, dt);
490    }
491
492    #[cfg(feature = "tds73")]
493    #[test]
494    fn date_accessor_and_new() {
495        let date = Date::new(730119);
496        assert_eq!(date.days(), 730119);
497    }
498
499    #[cfg(feature = "tds73")]
500    #[test]
501    #[should_panic]
502    fn date_new_panics_on_overflow() {
503        // Anything not representable in three bytes must panic.
504        Date::new(0x0100_0000);
505    }
506
507    #[cfg(feature = "tds73")]
508    #[tokio::test]
509    async fn date_round_trip() {
510        for days in [0u32, 1, 730119, 0x00ff_ffff] {
511            let date = Date::new(days);
512            let mut buf = BytesMut::new();
513            date.encode(&mut buf).unwrap();
514            assert_eq!(buf.len(), 3);
515            let decoded = Date::decode(&mut buf.into_sql_read_bytes()).await.unwrap();
516            assert_eq!(decoded, date);
517        }
518    }
519
520    #[cfg(feature = "tds73")]
521    #[test]
522    fn time_accessors_and_len() {
523        let time = Time::new(1234, 5);
524        assert_eq!(time.increments(), 1234);
525        assert_eq!(time.scale(), 5);
526        assert_eq!(time.len().unwrap(), 5);
527
528        assert_eq!(Time::new(0, 0).len().unwrap(), 3);
529        assert_eq!(Time::new(0, 3).len().unwrap(), 4);
530        assert!(Time::new(0, 8).len().is_err());
531    }
532
533    #[cfg(feature = "tds73")]
534    #[test]
535    fn time_partial_eq_across_scales() {
536        // 1 second expressed at two different scales must compare equal.
537        assert_eq!(Time::new(100, 2), Time::new(10_000_000, 7));
538        assert_ne!(Time::new(100, 2), Time::new(200, 2));
539    }
540
541    #[cfg(feature = "tds73")]
542    #[tokio::test]
543    async fn time_round_trip_all_len_buckets() {
544        for (increments, scale) in [(255u64, 2u8), (65535, 4), (16_777_215, 7)] {
545            let time = Time::new(increments, scale);
546            let rlen = time.len().unwrap();
547            let mut buf = BytesMut::new();
548            time.encode(&mut buf).unwrap();
549            let decoded = Time::decode(
550                &mut buf.into_sql_read_bytes(),
551                scale as usize,
552                rlen as usize,
553            )
554            .await
555            .unwrap();
556            assert_eq!(decoded, time);
557        }
558    }
559
560    #[cfg(feature = "tds73")]
561    #[tokio::test]
562    async fn time_round_trip_high_bytes_set() {
563        // Values whose most-significant byte (the byte handled by the
564        // `lo << 16` / `lo << 32` shift in `decode` and the `>> 16` / `>> 32`
565        // shift in `encode`) is non-zero. This distinguishes:
566        //   * decode `<< N` from `>> N` (the latter zeroes an `u8`), and
567        //   * encode `>> N` from `<< N` (the latter zeroes the byte written).
568        // The 16-bit / 32-bit low halves and the shifted high byte occupy
569        // disjoint bit ranges, so `|` vs `^` cannot be distinguished here.
570        for (increments, scale) in [(0x00FF_1234u64, 2u8), (0x00AB_1234_5678u64, 7)] {
571            let time = Time::new(increments, scale);
572            let rlen = time.len().unwrap();
573
574            let mut buf = BytesMut::new();
575            time.encode(&mut buf).unwrap();
576
577            let decoded = Time::decode(
578                &mut buf.into_sql_read_bytes(),
579                scale as usize,
580                rlen as usize,
581            )
582            .await
583            .unwrap();
584
585            assert_eq!(decoded, time);
586            assert_eq!(decoded.increments(), increments);
587        }
588    }
589
590    #[cfg(feature = "tds73")]
591    #[tokio::test]
592    async fn time_decode_invalid_length_errors() {
593        let mut buf = BytesMut::new();
594        buf.put_u8(0);
595        // scale/length combination not one of the accepted pairs.
596        let err = Time::decode(&mut buf.into_sql_read_bytes(), 0, 4).await;
597        assert!(err.is_err());
598    }
599
600    #[cfg(feature = "tds73")]
601    #[tokio::test]
602    async fn datetime2_round_trip_and_accessors() {
603        let dt2 = DateTime2::new(Date::new(730119), Time::new(222, 7));
604        assert_eq!(dt2.date(), Date::new(730119));
605        assert_eq!(dt2.time(), Time::new(222, 7));
606
607        let rlen = dt2.time().len().unwrap();
608        let mut buf = BytesMut::new();
609        dt2.encode(&mut buf).unwrap();
610        let decoded = DateTime2::decode(&mut buf.into_sql_read_bytes(), 7, rlen as usize)
611            .await
612            .unwrap();
613        assert_eq!(decoded, dt2);
614    }
615
616    #[cfg(feature = "tds73")]
617    #[tokio::test]
618    async fn datetimeoffset_round_trip_and_accessors() {
619        let dt2 = DateTime2::new(Date::new(730119), Time::new(222, 7));
620        let dto = DateTimeOffset::new(dt2, -120);
621        assert_eq!(dto.datetime2(), dt2);
622        assert_eq!(dto.offset(), -120);
623
624        let rlen = dto.datetime2().time().len().unwrap();
625        let mut buf = BytesMut::new();
626        dto.encode(&mut buf).unwrap();
627        let decoded = DateTimeOffset::decode(&mut buf.into_sql_read_bytes(), 7, rlen)
628            .await
629            .unwrap();
630        assert_eq!(decoded, dto);
631    }
632}