rsfbclient_core/
date_time.rs

1use chrono::{Datelike, NaiveDate, NaiveDateTime, NaiveTime, Timelike};
2
3use crate::{
4    error::{err_column_null, err_type_conv},
5    ibase, Column, ColumnToVal, FbError, IntoParam, SqlType,
6};
7
8const FRACTION_TO_NANOS: u32 = 1e9 as u32 / ibase::ISC_TIME_SECONDS_PRECISION;
9
10/// Convert a numeric day to [day, month, year]. (Ported from the firebird source)
11///
12/// Calenders are divided into 4 year cycles: 3 non-leap years, and 1 leap year.
13/// Each cycle takes 365*4 + 1 == 1461 days.
14/// There is a further cycle of 100 4 year cycles.
15/// Every 100 years, the normally expected leap year is not present. Every 400 years it is.
16/// This cycle takes 100 * 1461 - 3 == 146097 days.
17/// The origin of the constant 2400001 is unknown.
18/// The origin of the constant 1721119 is unknown.
19/// The difference between 2400001 and 1721119 is the
20/// number of days from 0/0/0000 to our base date of 11/xx/1858 (678882)
21/// The origin of the constant 153 is unknown.
22///
23/// This whole routine has problems with ndates less than -678882 (Approx 2/1/0000).
24pub fn decode_date(date: ibase::ISC_DATE) -> NaiveDate {
25    let mut nday = date;
26
27    nday += 2400001 - 1721119;
28
29    let century = (4 * nday - 1) / 146097;
30    nday = 4 * nday - 1 - 146097 * century;
31
32    let mut day = nday / 4;
33    nday = (4 * day + 3) / 1461;
34    day = 4 * day + 3 - 1461 * nday;
35    day = (day + 4) / 4;
36
37    let mut month = (5 * day - 3) / 153;
38    day = 5 * day - 3 - 153 * month;
39    day = (day + 5) / 5;
40
41    let mut year = 100 * century + nday;
42
43    if month < 10 {
44        month += 3;
45    } else {
46        month -= 9;
47        year += 1;
48    };
49
50    chrono::NaiveDate::from_ymd(year, month as u32, day as u32)
51}
52
53/// Convert a [day, month, year] to numeric day (Ported from the firebird source)
54pub fn encode_date(date: NaiveDate) -> ibase::ISC_DATE {
55    let day = date.day() as i64;
56    let mut month = date.month() as i64;
57    let mut year = date.year() as i64;
58
59    if month > 2 {
60        month -= 3;
61    } else {
62        month += 9;
63        year -= 1;
64    }
65
66    let c = year / 100;
67    let ya = year - 100 * c;
68
69    ((146097 * c) as i64 / 4 + (1461 * ya) / 4 + (153 * month + 2) / 5 + day + 1721119 - 2400001)
70        as ibase::ISC_DATE
71}
72
73/// Convert a numeric time to [hours, minutes, seconds] (Ported from the firebird source)
74pub fn decode_time(time: ibase::ISC_TIME) -> NaiveTime {
75    let mut ntime = time;
76
77    let hours = ntime / (3600 * ibase::ISC_TIME_SECONDS_PRECISION);
78    ntime %= 3600 * ibase::ISC_TIME_SECONDS_PRECISION;
79
80    let minutes = ntime / (60 * ibase::ISC_TIME_SECONDS_PRECISION);
81    ntime %= 60 * ibase::ISC_TIME_SECONDS_PRECISION;
82
83    let seconds = ntime / ibase::ISC_TIME_SECONDS_PRECISION;
84
85    let fraction = ntime % ibase::ISC_TIME_SECONDS_PRECISION;
86
87    chrono::NaiveTime::from_hms_nano(hours, minutes, seconds, fraction * FRACTION_TO_NANOS)
88}
89
90/// Convert a [hours, minutes, seconds] to a numeric time (Ported from the firebird source)
91pub fn encode_time(time: chrono::NaiveTime) -> ibase::ISC_TIME {
92    let hours = time.hour();
93    let minutes = time.minute();
94    let seconds = time.second();
95    let fraction = time.nanosecond() / FRACTION_TO_NANOS;
96
97    ((hours * 60 + minutes) * 60 + seconds) * ibase::ISC_TIME_SECONDS_PRECISION + fraction
98}
99
100/// Convert a numeric timestamp to a DateTime
101pub fn decode_timestamp(ts: ibase::ISC_TIMESTAMP) -> NaiveDateTime {
102    decode_date(ts.timestamp_date).and_time(decode_time(ts.timestamp_time))
103}
104
105/// Convert a DateTime to a numeric timestamp
106pub fn encode_timestamp(dt: NaiveDateTime) -> ibase::ISC_TIMESTAMP {
107    ibase::ISC_TIMESTAMP {
108        timestamp_date: encode_date(dt.date()),
109        timestamp_time: encode_time(dt.time()),
110    }
111}
112
113impl IntoParam for NaiveDateTime {
114    fn into_param(self) -> SqlType {
115        SqlType::Timestamp(self)
116    }
117}
118
119impl IntoParam for NaiveDate {
120    fn into_param(self) -> SqlType {
121        // Mimics firebird conversion
122        self.and_time(NaiveTime::from_hms(0, 0, 0)).into_param()
123    }
124}
125
126impl IntoParam for NaiveTime {
127    fn into_param(self) -> SqlType {
128        // Mimics firebird conversion
129        chrono::Utc::today().naive_utc().and_time(self).into_param()
130    }
131}
132
133impl ColumnToVal<chrono::NaiveDate> for Column {
134    fn to_val(self) -> Result<chrono::NaiveDate, FbError> {
135        match self.value {
136            SqlType::Timestamp(ts) => Ok(ts.date()),
137
138            SqlType::Null => Err(err_column_null("NaiveDate")),
139
140            col => err_type_conv(col, "NaiveDate"),
141        }
142    }
143}
144
145impl ColumnToVal<chrono::NaiveTime> for Column {
146    fn to_val(self) -> Result<chrono::NaiveTime, FbError> {
147        match self.value {
148            SqlType::Timestamp(ts) => Ok(ts.time()),
149
150            SqlType::Null => Err(err_column_null("NaiveTime")),
151
152            col => err_type_conv(col, "NaiveTime"),
153        }
154    }
155}
156
157impl ColumnToVal<chrono::NaiveDateTime> for Column {
158    fn to_val(self) -> Result<chrono::NaiveDateTime, FbError> {
159        match self.value {
160            SqlType::Timestamp(ts) => Ok(ts),
161
162            SqlType::Null => Err(err_column_null("NaiveDateTime")),
163
164            col => err_type_conv(col, "NaiveDateTime"),
165        }
166    }
167}