1use std::time::Duration;
8pub use time::{Date, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset};
9
10use crate::tds::codec::ColumnData;
11
12#[inline]
13fn from_days(days: i64, start_year: i32) -> Date {
14 let base = Date::from_calendar_date(start_year, Month::January, 1).unwrap();
24 base.checked_add(time::Duration::days(days))
25 .unwrap_or(if days < 0 { Date::MIN } else { Date::MAX })
26}
27
28#[inline]
33#[cfg(feature = "tds73")]
34fn nanos_from_increments(increments: u64, scale: u8) -> u64 {
35 let pow = 9u32.saturating_sub(scale as u32);
36 increments.saturating_mul(10u64.saturating_pow(pow))
37}
38
39#[inline]
40#[cfg(feature = "tds73")]
41fn from_secs(secs: u64) -> Time {
42 Time::from_hms(0, 0, 0).unwrap() + Duration::from_secs(secs)
43}
44
45#[inline]
46fn from_sec_fragments(sec_fragments: u64) -> Time {
47 Time::from_hms(0, 0, 0).unwrap() + Duration::from_nanos(sec_fragments * (1e9 as u64) / 300)
48}
49
50#[inline]
51fn to_days(date: Date, start_year: i32) -> i64 {
52 (date - Date::from_calendar_date(start_year, Month::January, 1).unwrap()).whole_days()
53}
54
55#[inline]
56#[cfg(not(feature = "tds73"))]
57fn to_sec_fragments(from: Time) -> i64 {
58 let nanos: i64 = (from - Time::from_hms(0, 0, 0).unwrap())
59 .whole_nanoseconds()
60 .try_into()
61 .unwrap();
62
63 nanos * 300 / (1e9 as i64)
64}
65
66#[cfg(feature = "tds73")]
67from_sql!(
68 PrimitiveDateTime:
69 ColumnData::SmallDateTime(ref dt) => dt.map(|dt| PrimitiveDateTime::new(
70 from_days(dt.days as i64, 1900),
71 from_secs(dt.seconds_fragments as u64 * 60),
72 )),
73 ColumnData::DateTime2(ref dt) => dt.map(|dt| PrimitiveDateTime::new(
74 from_days(dt.date.days() as i64, 1),
75 Time::from_hms(0,0,0).unwrap() + Duration::from_nanos(nanos_from_increments(dt.time.increments, dt.time.scale))
76 )),
77 ColumnData::DateTime(ref dt) => dt.map(|dt| PrimitiveDateTime::new(
78 from_days(dt.days as i64, 1900),
79 from_sec_fragments(dt.seconds_fragments as u64)
80 ));
81 Time:
82 ColumnData::Time(ref time) => time.map(|time| {
83 let ns = nanos_from_increments(time.increments, time.scale);
84 Time::from_hms(0,0,0).unwrap() + Duration::from_nanos(ns)
85 });
86 Date:
87 ColumnData::Date(ref date) => date.map(|date| from_days(date.days() as i64, 1));
88 OffsetDateTime:
89 ColumnData::DateTimeOffset(ref dto) => dto.map(|dto| {
90 let date = from_days(dto.datetime2.date.days() as i64, 1);
91 let dt = dto.datetime2;
92
93 let time = Time::from_hms(0,0,0).unwrap()
94 + Duration::from_nanos(nanos_from_increments(dt.time.increments, dt.time.scale));
95
96 let offset = UtcOffset::from_whole_seconds(dto.offset as i32 * 60)
99 .unwrap_or(UtcOffset::UTC);
100
101 date.with_time(time).assume_utc().to_offset(offset)
102 })
103);
104
105#[cfg(feature = "tds73")]
106to_sql!(self_,
107 Date: (ColumnData::Date, super::Date::new(to_days(*self_, 1) as u32));
108 Time: (ColumnData::Time, {
109 let nanos: u64 = (*self_ - Time::from_hms(0, 0, 0).unwrap()).whole_nanoseconds().try_into().unwrap();
110 let increments = nanos / 100;
111
112 super::Time {increments, scale: 7}
113 });
114 PrimitiveDateTime: (ColumnData::DateTime2, {
115 let time = self_.time();
116 let nanos: u64 = (time - Time::from_hms(0, 0, 0).unwrap()).whole_nanoseconds().try_into().unwrap();
117 let increments = nanos / 100;
118
119 let date = super::Date::new(to_days(self_.date(), 1) as u32);
120 let time = super::Time {increments, scale: 7};
121
122 super::DateTime2::new(date, time)
123 });
124 OffsetDateTime: (ColumnData::DateTimeOffset, {
125 let tz = self_.offset();
126 let offset = (tz.whole_seconds() / 60) as i16;
127
128 let utc_date = self_.to_offset(UtcOffset::UTC);
129
130 let nanos: u64 = (utc_date.time() - Time::from_hms(0, 0, 0).unwrap()).whole_nanoseconds().try_into().unwrap();
131
132 let date = super::Date::new(to_days(utc_date.date(), 1) as u32);
133 let time = super::Time { increments: nanos / 100, scale: 7 };
134
135 super::DateTimeOffset::new(super::DateTime2::new(date, time), offset)
136 });
137);
138
139#[cfg(not(feature = "tds73"))]
140to_sql!(self_,
141 PrimitiveDateTime: (ColumnData::DateTime, {
142 let date = self_.date();
143 let time = self_.time();
144
145 let days = to_days(date, 1900) as i32;
146 let seconds_fragments = to_sec_fragments(time);
147
148 super::DateTime::new(days, seconds_fragments as u32)
149 });
150);
151
152#[cfg(not(feature = "tds73"))]
153from_sql!(
154 PrimitiveDateTime:
155 ColumnData::DateTime(ref dt) => dt.map(|dt| {
156 from_days(dt.days as i64, 1900).with_time(from_sec_fragments(dt.seconds_fragments as u64))
157 })
158);
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163
164 #[test]
168 fn from_days_handles_negative_offsets() {
169 assert_eq!(
171 from_days(-1, 1900),
172 Date::from_calendar_date(1899, Month::December, 31).unwrap()
173 );
174
175 let expected = Date::from_calendar_date(1850, Month::January, 1).unwrap();
177 let days = to_days(expected, 1900);
178 assert!(
179 days < 0,
180 "expected a negative day offset for pre-1900 dates"
181 );
182
183 assert_eq!(from_days(days, 1900), expected);
185 }
186
187 #[test]
190 fn datetime_before_1900_decodes() {
191 let expected_date = Date::from_calendar_date(1850, Month::January, 1).unwrap();
192 let days = to_days(expected_date, 1900) as i32;
193
194 let dt = crate::tds::time::DateTime::new(days, 0);
196 let decoded = from_days(dt.days() as i64, 1900)
197 .with_time(from_sec_fragments(dt.seconds_fragments() as u64));
198
199 assert_eq!(decoded.date(), expected_date);
200 assert_eq!(decoded.time(), Time::from_hms(0, 0, 0).unwrap());
201 }
202
203 #[test]
204 fn from_days_clamps_on_overflow() {
205 assert_eq!(from_days(10_000_000, 1), Date::MAX);
209 assert_eq!(from_days(-10_000_000, 1), Date::MIN);
210 }
211
212 #[cfg(feature = "tds73")]
213 #[test]
214 fn from_secs_converts() {
215 assert_eq!(from_secs(3600), Time::from_hms(1, 0, 0).unwrap());
217 }
218
219 #[test]
220 fn from_sec_fragments_converts() {
221 assert_eq!(from_sec_fragments(300), Time::from_hms(0, 0, 1).unwrap());
223 }
224
225 #[cfg(not(feature = "tds73"))]
226 #[test]
227 fn to_sec_fragments_converts() {
228 assert_eq!(to_sec_fragments(Time::from_hms(0, 0, 1).unwrap()), 300);
230 }
231
232 #[cfg(feature = "tds73")]
233 #[test]
234 fn time_from_sql_and_back() {
235 use crate::FromSql;
236
237 let expected = Time::from_hms(12, 34, 56).unwrap();
239 let nanos: u64 = (expected - Time::from_hms(0, 0, 0).unwrap())
240 .whole_nanoseconds()
241 .try_into()
242 .unwrap();
243 let increments: u64 = nanos / 100;
244
245 let tds_time = super::super::Time::new(increments, 7);
246 let data = ColumnData::Time(Some(tds_time));
247
248 let decoded = Time::from_sql(&data).unwrap().unwrap();
249 assert_eq!(decoded, expected);
250
251 use crate::ToSql;
253 let round_tripped = decoded.to_sql();
254 match round_tripped {
255 ColumnData::Time(Some(t)) => assert_eq!(t.increments, increments),
256 other => panic!("unexpected: {:?}", other),
257 }
258 }
259
260 #[cfg(feature = "tds73")]
261 #[test]
262 fn date_from_sql_and_back() {
263 use crate::{FromSql, ToSql};
264
265 let expected = Date::from_calendar_date(2020, Month::June, 15).unwrap();
266 let days = to_days(expected, 1) as u32;
267
268 let data = ColumnData::Date(Some(super::super::Date::new(days)));
269 let decoded = Date::from_sql(&data).unwrap().unwrap();
270 assert_eq!(decoded, expected);
271
272 match decoded.to_sql() {
273 ColumnData::Date(Some(d)) => assert_eq!(d.days(), days),
274 other => panic!("unexpected: {:?}", other),
275 }
276 }
277
278 #[cfg(feature = "tds73")]
279 #[test]
280 fn primitive_datetime_from_datetime2_and_back() {
281 use crate::{FromSql, ToSql};
282
283 let date = Date::from_calendar_date(2020, Month::June, 15).unwrap();
284 let time = Time::from_hms(1, 2, 3).unwrap();
285 let expected = PrimitiveDateTime::new(date, time);
286
287 let days = to_days(date, 1) as u32;
288 let nanos: u64 = (time - Time::from_hms(0, 0, 0).unwrap())
289 .whole_nanoseconds()
290 .try_into()
291 .unwrap();
292 let increments = nanos / 100;
293
294 let dt2 = super::super::DateTime2::new(
295 super::super::Date::new(days),
296 super::super::Time::new(increments, 7),
297 );
298 let data = ColumnData::DateTime2(Some(dt2));
299
300 let decoded = PrimitiveDateTime::from_sql(&data).unwrap().unwrap();
301 assert_eq!(decoded, expected);
302
303 match decoded.to_sql() {
304 ColumnData::DateTime2(Some(dt)) => {
305 assert_eq!(dt.date.days(), days);
306 }
307 other => panic!("unexpected: {:?}", other),
308 }
309 }
310
311 #[cfg(feature = "tds73")]
312 #[test]
313 fn primitive_datetime_from_smalldatetime_and_datetime() {
314 use crate::FromSql;
315
316 let sdt = crate::tds::time::SmallDateTime::new(1, 30); let data = ColumnData::SmallDateTime(Some(sdt));
319 let decoded = PrimitiveDateTime::from_sql(&data).unwrap().unwrap();
320 assert_eq!(decoded.date(), from_days(1, 1900));
321
322 let dt = crate::tds::time::DateTime::new(1, 0);
324 let data = ColumnData::DateTime(Some(dt));
325 let decoded = PrimitiveDateTime::from_sql(&data).unwrap().unwrap();
326 assert_eq!(decoded.date(), from_days(1, 1900));
327 }
328
329 #[cfg(feature = "tds73")]
330 #[test]
331 fn offset_date_time_from_sql_and_back() {
332 use crate::{FromSql, ToSql};
333
334 let date = Date::from_calendar_date(2020, Month::June, 15).unwrap();
335 let time = Time::from_hms(1, 2, 3).unwrap();
336 let days = to_days(date, 1) as u32;
337 let nanos: u64 = (time - Time::from_hms(0, 0, 0).unwrap())
338 .whole_nanoseconds()
339 .try_into()
340 .unwrap();
341 let increments = nanos / 100;
342
343 let dt2 = super::super::DateTime2::new(
344 super::super::Date::new(days),
345 super::super::Time::new(increments, 7),
346 );
347 let dto = super::super::DateTimeOffset::new(dt2, 60); let data = ColumnData::DateTimeOffset(Some(dto));
350 let decoded = OffsetDateTime::from_sql(&data).unwrap().unwrap();
351
352 assert_eq!(
353 decoded.offset(),
354 UtcOffset::from_whole_seconds(3600).unwrap()
355 );
356
357 match decoded.to_sql() {
358 ColumnData::DateTimeOffset(Some(round_tripped)) => {
359 assert_eq!(round_tripped.offset, 60);
360 }
361 other => panic!("unexpected: {:?}", other),
362 }
363 }
364
365 #[cfg(feature = "tds73")]
368 #[test]
369 fn from_sql_null_variants_return_none_tds73() {
370 use crate::FromSql;
371
372 assert_eq!(Time::from_sql(&ColumnData::Time(None)).unwrap(), None);
373 assert_eq!(Date::from_sql(&ColumnData::Date(None)).unwrap(), None);
374 assert_eq!(
375 OffsetDateTime::from_sql(&ColumnData::DateTimeOffset(None)).unwrap(),
376 None
377 );
378 }
379
380 #[test]
381 fn primitive_datetime_from_sql_null_returns_none() {
382 use crate::FromSql;
383
384 assert_eq!(
385 PrimitiveDateTime::from_sql(&ColumnData::DateTime(None)).unwrap(),
386 None
387 );
388 }
389}