Lightweight 0.1.0
Loading...
Searching...
No Matches
SqlDateTime.hpp
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5#include "../SqlColumnTypeDefinitions.hpp"
6#include "Core.hpp"
7
8#include <chrono>
9#include <format>
10#include <string_view>
11
12#include <sql.h>
13#include <sqlext.h>
14#include <sqltypes.h>
15
16/// Represents a date and time to efficiently write to or read from a database.
17///
18/// @see SqlDate, SqlTime
19/// @ingroup DataTypes
21{
22 using native_type = std::chrono::system_clock::time_point;
23 using duration_type = std::chrono::system_clock::duration;
24
25 /// Returns the current date and time.
26 [[nodiscard]] static LIGHTWEIGHT_FORCE_INLINE SqlDateTime Now() noexcept
27 {
28 return SqlDateTime { std::chrono::system_clock::now() };
29 }
30
31 constexpr SqlDateTime() noexcept = default;
32 constexpr SqlDateTime(SqlDateTime&&) noexcept = default;
33 constexpr SqlDateTime& operator=(SqlDateTime&&) noexcept = default;
34 constexpr SqlDateTime(SqlDateTime const&) noexcept = default;
35 constexpr SqlDateTime& operator=(SqlDateTime const& other) noexcept = default;
36 constexpr ~SqlDateTime() noexcept = default;
37
38 constexpr std::weak_ordering operator<=>(SqlDateTime const& other) const noexcept
39 {
40 return value() <=> other.value();
41 }
42 constexpr bool operator==(SqlDateTime const& other) const noexcept
43 {
44 return (*this <=> other) == std::weak_ordering::equivalent;
45 }
46
47 constexpr bool operator!=(SqlDateTime const& other) const noexcept
48 {
49 return !(*this == other);
50 }
51
52 /// Constructs a date and time from individual components.
53 LIGHTWEIGHT_FORCE_INLINE constexpr SqlDateTime(std::chrono::year_month_day ymd,
54 std::chrono::hh_mm_ss<duration_type> time) noexcept:
55 sqlValue {
56 .year = (SQLSMALLINT) (int) ymd.year(),
57 .month = (SQLUSMALLINT) (unsigned) ymd.month(),
58 .day = (SQLUSMALLINT) (unsigned) ymd.day(),
59 .hour = (SQLUSMALLINT) time.hours().count(),
60 .minute = (SQLUSMALLINT) time.minutes().count(),
61 .second = (SQLUSMALLINT) time.seconds().count(),
62 .fraction =
63 (SQLUINTEGER) (std::chrono::duration_cast<std::chrono::nanoseconds>(time.to_duration()).count() / 100) * 100,
64 }
65 {
66 }
67
68 /// Constructs a date and time from individual components.
69 LIGHTWEIGHT_FORCE_INLINE constexpr SqlDateTime(
70 std::chrono::year year,
71 std::chrono::month month,
72 std::chrono::day day,
73 std::chrono::hours hour,
74 std::chrono::minutes minute,
75 std::chrono::seconds second,
76 std::chrono::nanoseconds nanosecond = std::chrono::nanoseconds(0)) noexcept:
77 sqlValue {
78 .year = (SQLSMALLINT) (int) year,
79 .month = (SQLUSMALLINT) (unsigned) month,
80 .day = (SQLUSMALLINT) (unsigned) day,
81 .hour = (SQLUSMALLINT) hour.count(),
82 .minute = (SQLUSMALLINT) minute.count(),
83 .second = (SQLUSMALLINT) second.count(),
84 .fraction = (SQLUINTEGER) (nanosecond.count() / 100) * 100,
85 }
86 {
87 }
88
89 /// Constructs a date and time from a time point.
90 LIGHTWEIGHT_FORCE_INLINE constexpr SqlDateTime(std::chrono::system_clock::time_point value) noexcept:
91 sqlValue { ConvertToSqlValue(value) }
92 {
93 }
94
95 /// Returns the year of this date-time object.
96 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE std::chrono::year year() const noexcept
97 {
98 return std::chrono::year(static_cast<int>(sqlValue.year));
99 }
100
101 /// Returns the month of this date-time object.
102 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE std::chrono::month month() const noexcept
103 {
104 return std::chrono::month(static_cast<unsigned>(sqlValue.month));
105 }
106
107 /// Returns the day of this date-time object.
108 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE std::chrono::day day() const noexcept
109 {
110 return std::chrono::day(static_cast<unsigned>(sqlValue.day));
111 }
112
113 /// Returns the hour of this date-time object.
114 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE std::chrono::hours hour() const noexcept
115 {
116 return std::chrono::hours(static_cast<unsigned>(sqlValue.hour));
117 }
118
119 /// Returns the minute of this date-time object.
120 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE std::chrono::minutes minute() const noexcept
121 {
122 return std::chrono::minutes(static_cast<unsigned>(sqlValue.minute));
123 }
124
125 /// Returns the second of this date-time object.
126 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE std::chrono::seconds second() const noexcept
127 {
128 return std::chrono::seconds(static_cast<unsigned>(sqlValue.second));
129 }
130
131 /// Returns the nanosecond of this date-time object.
132 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE std::chrono::nanoseconds nanosecond() const noexcept
133 {
134 return std::chrono::nanoseconds(static_cast<unsigned>(sqlValue.fraction));
135 }
136
137 LIGHTWEIGHT_FORCE_INLINE constexpr operator native_type() const noexcept
138 {
139 return value();
140 }
141
142 static LIGHTWEIGHT_FORCE_INLINE SQL_TIMESTAMP_STRUCT constexpr ConvertToSqlValue(native_type value) noexcept
143 {
144 using namespace std::chrono;
145 auto const totalDays = floor<days>(value);
146 auto const ymd = year_month_day { totalDays };
147 auto const hms =
148 hh_mm_ss<duration_type> { std::chrono::duration_cast<duration_type>(floor<nanoseconds>(value - totalDays)) };
149 return ConvertToSqlValue(ymd, hms);
150 }
151
152 static LIGHTWEIGHT_FORCE_INLINE SQL_TIMESTAMP_STRUCT constexpr ConvertToSqlValue(
153 std::chrono::year_month_day ymd, std::chrono::hh_mm_ss<duration_type> hms) noexcept
154 {
155 // clang-format off
156 // NB: The fraction field is in 100ns units.
157 return SQL_TIMESTAMP_STRUCT {
158 .year = (SQLSMALLINT) (int) ymd.year(),
159 .month = (SQLUSMALLINT) (unsigned) ymd.month(),
160 .day = (SQLUSMALLINT) (unsigned) ymd.day(),
161 .hour = (SQLUSMALLINT) hms.hours().count(),
162 .minute = (SQLUSMALLINT) hms.minutes().count(),
163 .second = (SQLUSMALLINT) hms.seconds().count(),
164 .fraction = (SQLUINTEGER) (((std::chrono::duration_cast<std::chrono::nanoseconds>(hms.to_duration()).count() % 1'000'000'000llu) / 100) * 100)
165 };
166 // clang-format on
167 }
168
169 static LIGHTWEIGHT_FORCE_INLINE native_type constexpr ConvertToNative(SQL_TIMESTAMP_STRUCT const& time) noexcept
170 {
171 // clang-format off
172 using namespace std::chrono;
173 auto const ymd = year_month_day { std::chrono::year { time.year } / std::chrono::month { time.month } / std::chrono::day { time.day } };
174 auto const hms = hh_mm_ss<duration_type> {
175 duration_cast<duration_type>(
176 hours { time.hour }
177 + minutes { time.minute }
178 + seconds { time.second }
179 + nanoseconds { time.fraction }
180 )
181 };
182 return sys_days { ymd } + hms.to_duration();
183 // clang-format on
184 }
185
186 /// Returns the current date and time.
187 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE native_type value() const noexcept
188 {
189 return ConvertToNative(sqlValue);
190 }
191
192 LIGHTWEIGHT_FORCE_INLINE SqlDateTime& operator+=(duration_type duration) noexcept
193 {
194 *this = SqlDateTime { value() + duration };
195 return *this;
196 }
197
198 LIGHTWEIGHT_FORCE_INLINE SqlDateTime& operator-=(duration_type duration) noexcept
199 {
200 *this = SqlDateTime { value() - duration };
201 return *this;
202 }
203
204 friend LIGHTWEIGHT_FORCE_INLINE SqlDateTime operator+(SqlDateTime dateTime, duration_type duration) noexcept
205 {
206 auto tmp = dateTime.value() + duration;
207 return SqlDateTime(tmp);
208 // return SqlDateTime { dateTime.value() + duration };
209 }
210
211 friend LIGHTWEIGHT_FORCE_INLINE SqlDateTime operator-(SqlDateTime dateTime, duration_type duration) noexcept
212 {
213 return SqlDateTime { dateTime.value() - duration };
214 }
215
216 SQL_TIMESTAMP_STRUCT sqlValue {};
217};
218
219template <>
220struct std::formatter<SqlDateTime>: std::formatter<std::string>
221{
222 LIGHTWEIGHT_FORCE_INLINE auto format(SqlDateTime const& value, std::format_context& ctx) const
223 -> std::format_context::iterator
224 {
225 // This can be used manually to format the date and time inside sql query builder,
226 // that is why we need to use iso standard 8601, also millisecond precision is used for the formatting
227 //
228 // internally fraction is stored in nanoseconds, here we need to get it in milliseconds
229 auto const milliseconds = value.sqlValue.fraction / 1'000'000;
230 return std::formatter<std::string>::format(std::format("{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}",
231 value.sqlValue.year,
232 value.sqlValue.month,
233 value.sqlValue.day,
234 value.sqlValue.hour,
235 value.sqlValue.minute,
236 value.sqlValue.second,
237 milliseconds),
238 ctx);
239 }
240};
241
242template <>
243struct SqlDataBinder<SqlDateTime::native_type>
244{
245 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN GetColumn(SQLHSTMT stmt,
246 SQLUSMALLINT column,
247 SqlDateTime::native_type* result,
248 SQLLEN* indicator,
249 SqlDataBinderCallback const& /*cb*/) noexcept
250 {
251 SQL_TIMESTAMP_STRUCT sqlValue {};
252 auto const rc = SQLGetData(stmt, column, SQL_C_TYPE_TIMESTAMP, &sqlValue, sizeof(sqlValue), indicator);
253 if (SQL_SUCCEEDED(rc))
254 *result = SqlDateTime::ConvertToNative(sqlValue);
255 return rc;
256 }
257};
258
259template <>
260struct LIGHTWEIGHT_API SqlDataBinder<SqlDateTime>
261{
262 static constexpr auto ColumnType = SqlColumnTypeDefinitions::DateTime {};
263
264 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN InputParameter(SQLHSTMT stmt,
265 SQLUSMALLINT column,
266 SqlDateTime const& value,
267 [[maybe_unused]] SqlDataBinderCallback const& cb) noexcept
268 {
269#if defined(_WIN32) || defined(_WIN64)
270 // Microsoft Windows also chips with SQLSRV32.DLL, which is legacy, but seems to be used sometimes.
271 // See: https://learn.microsoft.com/en-us/sql/connect/connect-history
272 using namespace std::string_view_literals;
273 if (cb.ServerType() == SqlServerType::MICROSOFT_SQL && cb.DriverName() == "SQLSRV32.DLL"sv)
274 {
275 struct
276 {
277 SQLSMALLINT sqlType { SQL_TYPE_TIMESTAMP };
278 SQLULEN paramSize { 23 };
279 SQLSMALLINT decimalDigits { 3 };
280 SQLSMALLINT nullable {};
281 } hints;
282 auto const sqlDescribeParamResult =
283 SQLDescribeParam(stmt, column, &hints.sqlType, &hints.paramSize, &hints.decimalDigits, &hints.nullable);
284 if (SQL_SUCCEEDED(sqlDescribeParamResult))
285 {
286 return SQLBindParameter(stmt,
287 column,
288 SQL_PARAM_INPUT,
289 SQL_C_TIMESTAMP,
290 hints.sqlType,
291 hints.paramSize,
292 hints.decimalDigits,
293 (SQLPOINTER) &value.sqlValue,
294 sizeof(value),
295 nullptr);
296 }
297 }
298#endif
299
300 return SQLBindParameter(stmt,
301 column,
302 SQL_PARAM_INPUT,
303 SQL_C_TIMESTAMP,
304 SQL_TYPE_TIMESTAMP,
305 27,
306 7,
307 (SQLPOINTER) &value.sqlValue,
308 sizeof(value),
309 nullptr);
310 }
311
312 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN OutputColumn(
313 SQLHSTMT stmt, SQLUSMALLINT column, SqlDateTime* result, SQLLEN* indicator, SqlDataBinderCallback& /*cb*/) noexcept
314 {
315 // TODO: handle indicator to check for NULL values
316 *indicator = sizeof(result->sqlValue);
317 return SQLBindCol(stmt, column, SQL_C_TYPE_TIMESTAMP, &result->sqlValue, 0, indicator);
318 }
319
320 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN GetColumn(SQLHSTMT stmt,
321 SQLUSMALLINT column,
322 SqlDateTime* result,
323 SQLLEN* indicator,
324 SqlDataBinderCallback const& /*cb*/) noexcept
325 {
326 return SQLGetData(stmt, column, SQL_C_TYPE_TIMESTAMP, &result->sqlValue, sizeof(result->sqlValue), indicator);
327 }
328
329 static LIGHTWEIGHT_FORCE_INLINE std::string Inspect(SqlDateTime const& value) noexcept
330 {
331 return std::format("{}", value);
332 }
333};
constexpr LIGHTWEIGHT_FORCE_INLINE std::chrono::month month() const noexcept
Returns the month of this date-time object.
constexpr LIGHTWEIGHT_FORCE_INLINE std::chrono::minutes minute() const noexcept
Returns the minute of this date-time object.
LIGHTWEIGHT_FORCE_INLINE constexpr SqlDateTime(std::chrono::system_clock::time_point value) noexcept
Constructs a date and time from a time point.
constexpr LIGHTWEIGHT_FORCE_INLINE std::chrono::day day() const noexcept
Returns the day of this date-time object.
constexpr LIGHTWEIGHT_FORCE_INLINE std::chrono::nanoseconds nanosecond() const noexcept
Returns the nanosecond of this date-time object.
LIGHTWEIGHT_FORCE_INLINE constexpr SqlDateTime(std::chrono::year year, std::chrono::month month, std::chrono::day day, std::chrono::hours hour, std::chrono::minutes minute, std::chrono::seconds second, std::chrono::nanoseconds nanosecond=std::chrono::nanoseconds(0)) noexcept
Constructs a date and time from individual components.
constexpr LIGHTWEIGHT_FORCE_INLINE native_type value() const noexcept
Returns the current date and time.
LIGHTWEIGHT_FORCE_INLINE constexpr SqlDateTime(std::chrono::year_month_day ymd, std::chrono::hh_mm_ss< duration_type > time) noexcept
Constructs a date and time from individual components.
constexpr LIGHTWEIGHT_FORCE_INLINE std::chrono::year year() const noexcept
Returns the year of this date-time object.
constexpr LIGHTWEIGHT_FORCE_INLINE std::chrono::hours hour() const noexcept
Returns the hour of this date-time object.
constexpr LIGHTWEIGHT_FORCE_INLINE std::chrono::seconds second() const noexcept
Returns the second of this date-time object.
static LIGHTWEIGHT_FORCE_INLINE SqlDateTime Now() noexcept
Returns the current date and time.