Lightweight 0.20260625.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#include "SqlDate.hpp"
8#include "SqlTime.hpp"
9
10#include <chrono>
11#include <format>
12
13#include <sql.h>
14#include <sqlext.h>
15#include <sqltypes.h>
16
17namespace Lightweight
18{
19
20/// Represents a date and time to efficiently write to or read from a database.
21///
22/// @see SqlDate, SqlTime
23/// @ingroup DataTypes
25{
26 /// The native C++ type representing a date-time value.
27 using native_type = std::chrono::system_clock::time_point;
28 /// The duration type used for arithmetic operations.
29 using duration_type = std::chrono::system_clock::duration;
30
31 /// Returns the current date and time.
32 [[nodiscard]] static LIGHTWEIGHT_FORCE_INLINE SqlDateTime Now() noexcept
33 {
34 return SqlDateTime { std::chrono::system_clock::now() };
35 }
36
37#if !defined(LIGHTWEIGHT_CXX26_REFLECTION)
38 /// Return the current date and time in UTC.
39 [[nodiscard]] static LIGHTWEIGHT_FORCE_INLINE SqlDateTime NowUTC() noexcept
40 {
41 // `std::chrono::system_clock` already represents Unix time (UTC, no leap seconds) and is what
42 // `SqlDateTime` stores, so it yields the correct UTC value directly and identically on every
43 // platform. We deliberately avoid `utc_clock::now().time_since_epoch()` here: it counts leap
44 // seconds, so reinterpreting that duration as a `system_clock::time_point` would shift the value
45 // ~27s into the future, and `utc_clock` is unavailable on libc++ without a built-in tz database
46 // (the Homebrew toolchain on macOS) — both branches would otherwise disagree.
47 return SqlDateTime { std::chrono::system_clock::now() };
48 }
49#endif
50
51 /// Default constructor.
52 constexpr SqlDateTime() noexcept = default;
53 /// Default move constructor.
54 constexpr SqlDateTime(SqlDateTime&&) noexcept = default;
55 /// Default move assignment operator.
56 constexpr SqlDateTime& operator=(SqlDateTime&&) noexcept = default;
57 /// Default copy constructor.
58 constexpr SqlDateTime(SqlDateTime const&) noexcept = default;
59 /// Default copy assignment operator.
60 constexpr SqlDateTime& operator=(SqlDateTime const& other) noexcept = default;
61 constexpr ~SqlDateTime() noexcept = default;
62
63 /// Three-way comparison operator.
64 constexpr std::weak_ordering operator<=>(SqlDateTime const& other) const noexcept
65 {
66 return value() <=> other.value();
67 }
68 /// Equality comparison operator.
69 constexpr bool operator==(SqlDateTime const& other) const noexcept
70 {
71 return (*this <=> other) == std::weak_ordering::equivalent;
72 }
73
74 /// Inequality comparison operator.
75 constexpr bool operator!=(SqlDateTime const& other) const noexcept
76 {
77 return !(*this == other);
78 }
79
80 /// Constructs a date and time from individual components.
81 LIGHTWEIGHT_FORCE_INLINE constexpr SqlDateTime(std::chrono::year_month_day ymd,
82 std::chrono::hh_mm_ss<duration_type> time) noexcept:
83 sqlValue {
84 .year = (SQLSMALLINT) (int) ymd.year(),
85 .month = (SQLUSMALLINT) (unsigned) ymd.month(),
86 .day = (SQLUSMALLINT) (unsigned) ymd.day(),
87 .hour = (SQLUSMALLINT) time.hours().count(),
88 .minute = (SQLUSMALLINT) time.minutes().count(),
89 .second = (SQLUSMALLINT) time.seconds().count(),
90 .fraction =
91 (SQLUINTEGER) (std::chrono::duration_cast<std::chrono::nanoseconds>(time.to_duration()).count() / 100) * 100,
92 }
93 {
94 }
95
96 /// Constructs a date and time from individual components.
97 LIGHTWEIGHT_FORCE_INLINE constexpr SqlDateTime(
98 std::chrono::year year,
99 std::chrono::month month,
100 std::chrono::day day,
101 std::chrono::hours hour,
102 std::chrono::minutes minute,
103 std::chrono::seconds second,
104 std::chrono::nanoseconds nanosecond = std::chrono::nanoseconds(0)) noexcept:
105 sqlValue {
106 .year = (SQLSMALLINT) (int) year,
107 .month = (SQLUSMALLINT) (unsigned) month,
108 .day = (SQLUSMALLINT) (unsigned) day,
109 .hour = (SQLUSMALLINT) hour.count(),
110 .minute = (SQLUSMALLINT) minute.count(),
111 .second = (SQLUSMALLINT) second.count(),
112 .fraction = (SQLUINTEGER) (nanosecond.count() / 100) * 100,
113 }
114 {
115 }
116
117 /// Constructs a date and time from a time point.
118 LIGHTWEIGHT_FORCE_INLINE constexpr SqlDateTime(std::chrono::system_clock::time_point value) noexcept:
120 {
121 }
122
123 // NOLINTBEGIN(readability-identifier-naming)
124
125 /// Returns the year of this date-time object.
126 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE std::chrono::year year() const noexcept
127 {
128 return std::chrono::year(static_cast<int>(sqlValue.year));
129 }
130
131 /// Returns the month of this date-time object.
132 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE std::chrono::month month() const noexcept
133 {
134 return std::chrono::month(static_cast<unsigned>(sqlValue.month));
135 }
136
137 /// Returns the day of this date-time object.
138 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE std::chrono::day day() const noexcept
139 {
140 return std::chrono::day(static_cast<unsigned>(sqlValue.day));
141 }
142
143 /// Returns the hour of this date-time object.
144 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE std::chrono::hours hour() const noexcept
145 {
146 return std::chrono::hours(static_cast<unsigned>(sqlValue.hour));
147 }
148
149 /// Returns the minute of this date-time object.
150 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE std::chrono::minutes minute() const noexcept
151 {
152 return std::chrono::minutes(static_cast<unsigned>(sqlValue.minute));
153 }
154
155 /// Returns the second of this date-time object.
156 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE std::chrono::seconds second() const noexcept
157 {
158 return std::chrono::seconds(static_cast<unsigned>(sqlValue.second));
159 }
160
161 /// Returns the nanosecond of this date-time object.
162 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE std::chrono::nanoseconds nanosecond() const noexcept
163 {
164 return std::chrono::nanoseconds(static_cast<unsigned>(sqlValue.fraction));
165 }
166
167 // NOLINTEND(readability-identifier-naming)
168
169 /// Converts this SqlDateTime to its native time_point representation.
170 LIGHTWEIGHT_FORCE_INLINE constexpr operator native_type() const noexcept
171 {
172 return value();
173 }
174
175 /// @brief Constructs only a date from a SQL date-time structure.
176 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE SqlDate Date() const noexcept
177 {
178 return SqlDate { year(), month(), day() };
179 }
180
181 /// @brief Constructs only a time from a SQL date-time structure.
182 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE SqlTime Time() const noexcept
183 {
184 return SqlTime { std::chrono::hours { hour() },
185 std::chrono::minutes { minute() },
186 std::chrono::seconds { second() },
187 std::chrono::microseconds { std::chrono::duration_cast<std::chrono::microseconds>(nanosecond()) } };
188 }
189
190 /// Converts a native time_point to the underlying SQL timestamp structure.
191 static LIGHTWEIGHT_FORCE_INLINE SQL_TIMESTAMP_STRUCT constexpr ConvertToSqlValue(native_type value) noexcept
192 {
193 using namespace std::chrono;
194 auto const totalDays = floor<days>(value);
195 auto const ymd = year_month_day { totalDays };
196 auto const hms =
197 hh_mm_ss<duration_type> { std::chrono::duration_cast<duration_type>(floor<nanoseconds>(value - totalDays)) };
198 return ConvertToSqlValue(ymd, hms);
199 }
200
201 /// Converts year_month_day and hh_mm_ss components to the underlying SQL timestamp structure.
202 static LIGHTWEIGHT_FORCE_INLINE SQL_TIMESTAMP_STRUCT constexpr ConvertToSqlValue(
203 std::chrono::year_month_day ymd, std::chrono::hh_mm_ss<duration_type> hms) noexcept
204 {
205 // clang-format off
206 // NB: The fraction field is in 100ns units.
207 return SQL_TIMESTAMP_STRUCT {
208 .year = (SQLSMALLINT) (int) ymd.year(),
209 .month = (SQLUSMALLINT) (unsigned) ymd.month(),
210 .day = (SQLUSMALLINT) (unsigned) ymd.day(),
211 .hour = (SQLUSMALLINT) hms.hours().count(),
212 .minute = (SQLUSMALLINT) hms.minutes().count(),
213 .second = (SQLUSMALLINT) hms.seconds().count(),
214 .fraction = (SQLUINTEGER) (((static_cast<unsigned long long>(std::chrono::duration_cast<std::chrono::nanoseconds>(hms.to_duration()).count()) % 1'000'000'000LLU) / 100) * 100)
215 };
216 // clang-format on
217 }
218
219 /// Converts a SQL timestamp structure to the native time_point representation.
220 static LIGHTWEIGHT_FORCE_INLINE native_type constexpr ConvertToNative(SQL_TIMESTAMP_STRUCT const& time) noexcept
221 {
222 // clang-format off
223 using namespace std::chrono;
224 auto const ymd = year_month_day { std::chrono::year { time.year } / std::chrono::month { time.month } / std::chrono::day { time.day } };
225 auto const hms = hh_mm_ss<duration_type> {
226 duration_cast<duration_type>(
227 hours { time.hour }
228 + minutes { time.minute }
229 + seconds { time.second }
230 + nanoseconds { time.fraction }
231 )
232 };
233 return sys_days { ymd } + hms.to_duration();
234 // clang-format on
235 }
236
237 /// Returns the current date and time.
238 [[nodiscard]] constexpr LIGHTWEIGHT_FORCE_INLINE native_type value() const noexcept
239 {
241 }
242
243 /// Adds a duration to this date-time.
244 LIGHTWEIGHT_FORCE_INLINE SqlDateTime& operator+=(duration_type duration) noexcept
245 {
246 *this = SqlDateTime { value() + duration };
247 return *this;
248 }
249
250 /// Subtracts a duration from this date-time.
251 LIGHTWEIGHT_FORCE_INLINE SqlDateTime& operator-=(duration_type duration) noexcept
252 {
253 *this = SqlDateTime { value() - duration };
254 return *this;
255 }
256
257 friend LIGHTWEIGHT_FORCE_INLINE SqlDateTime operator+(SqlDateTime dateTime, duration_type duration) noexcept
258 {
259 return SqlDateTime { dateTime.value() + duration };
260 }
261
262 friend LIGHTWEIGHT_FORCE_INLINE SqlDateTime operator-(SqlDateTime dateTime, duration_type duration) noexcept
263 {
264 return SqlDateTime { dateTime.value() - duration };
265 }
266
267 /// Holds the underlying SQL timestamp structure.
268 SQL_TIMESTAMP_STRUCT sqlValue {};
269};
270
271} // namespace Lightweight
272
273template <>
274struct std::formatter<Lightweight::SqlDateTime>: std::formatter<std::string>
275{
276 LIGHTWEIGHT_FORCE_INLINE auto format(Lightweight::SqlDateTime const& value, std::format_context& ctx) const
277 -> std::format_context::iterator
278 {
279 // This can be used manually to format the date and time inside sql query builder,
280 // that is why we need to use iso standard 8601, also millisecond precision is used for the formatting
281 //
282 // internally fraction is stored in nanoseconds, here we need to get it in milliseconds
283 auto const milliseconds = value.sqlValue.fraction / 1'000'000;
284 return std::formatter<std::string>::format(std::format("{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}",
285 value.sqlValue.year,
286 value.sqlValue.month,
287 value.sqlValue.day,
288 value.sqlValue.hour,
289 value.sqlValue.minute,
290 value.sqlValue.second,
291 milliseconds),
292 ctx);
293 }
294};
295
296template <>
297struct std::formatter<std::optional<Lightweight::SqlDateTime>>: std::formatter<std::string>
298{
299 LIGHTWEIGHT_FORCE_INLINE auto format(std::optional<Lightweight::SqlDateTime> const& value,
300 std::format_context& ctx) const -> std::format_context::iterator
301 {
302 if (!value.has_value())
303 return std::formatter<std::string>::format("nullopt", ctx);
304 return std::formatter<std::string>::format(std::format("{}", value.value()), ctx);
305 }
306};
307
308namespace Lightweight
309{
310
311template <>
312struct SqlDataBinder<SqlDateTime::native_type>
313{
314 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN GetColumn(SQLHSTMT stmt,
315 SQLUSMALLINT column,
317 SQLLEN* indicator,
318 SqlDataBinderCallback const& /*cb*/) noexcept
319 {
320 SQL_TIMESTAMP_STRUCT sqlValue {};
321 auto const rc = SQLGetData(stmt, column, SQL_C_TYPE_TIMESTAMP, &sqlValue, sizeof(sqlValue), indicator);
322 if (SQL_SUCCEEDED(rc))
323 *result = SqlDateTime::ConvertToNative(sqlValue);
324 return rc;
325 }
326};
327
328template <>
329struct LIGHTWEIGHT_API SqlDataBinder<SqlDateTime>
330{
331 static constexpr auto ColumnType = SqlColumnTypeDefinitions::DateTime {};
332
333 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN InputParameter(SQLHSTMT stmt,
334 SQLUSMALLINT column,
335 SqlDateTime const& value,
336 [[maybe_unused]] SqlDataBinderCallback const& cb) noexcept
337 {
338#if defined(_WIN32) || defined(_WIN64)
339 // Microsoft Windows also chips with SQLSRV32.DLL, which is legacy, but seems to be used sometimes.
340 // See: https://learn.microsoft.com/en-us/sql/connect/connect-history
341 using namespace std::string_view_literals;
342 if (cb.ServerType() == SqlServerType::MICROSOFT_SQL && cb.DriverName() == "SQLSRV32.DLL"sv)
343 {
344 struct
345 {
346 SQLSMALLINT sqlType { SQL_TYPE_TIMESTAMP };
347 SQLULEN paramSize { 23 };
348 SQLSMALLINT decimalDigits { 3 };
349 SQLSMALLINT nullable {};
350 } hints;
351 auto const sqlDescribeParamResult =
352 SQLDescribeParam(stmt, column, &hints.sqlType, &hints.paramSize, &hints.decimalDigits, &hints.nullable);
353 if (SQL_SUCCEEDED(sqlDescribeParamResult))
354 {
355 return SQLBindParameter(stmt,
356 column,
357 SQL_PARAM_INPUT,
358 SQL_C_TIMESTAMP,
359 hints.sqlType,
360 hints.paramSize,
361 hints.decimalDigits,
362 (SQLPOINTER) &value.sqlValue,
363 sizeof(value),
364 nullptr);
365 }
366 }
367#endif
368
369 return SQLBindParameter(stmt,
370 column,
371 SQL_PARAM_INPUT,
372 SQL_C_TIMESTAMP,
373 SQL_TYPE_TIMESTAMP,
374 27,
375 7,
376 (SQLPOINTER) &value.sqlValue,
377 sizeof(value),
378 nullptr);
379 }
380
381 /// Binds an array of timestamps as an input parameter for native (row-wise or column-wise) batch
382 /// execution. @p values points at the first element; the driver strides by the statement's
383 /// SQL_ATTR_PARAM_BIND_TYPE. @p indicators optionally supplies per-row NULL flags.
384 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN BatchInputParameter(SQLHSTMT stmt,
385 SQLUSMALLINT column,
386 SqlDateTime const* values,
387 size_t /*rowCount*/,
388 [[maybe_unused]] SqlDataBinderCallback& cb,
389 SQLLEN* indicators = nullptr) noexcept
390 {
391#if defined(_WIN32) || defined(_WIN64)
392 using namespace std::string_view_literals;
393 if (cb.ServerType() == SqlServerType::MICROSOFT_SQL && cb.DriverName() == "SQLSRV32.DLL"sv)
394 {
395 struct
396 {
397 SQLSMALLINT sqlType { SQL_TYPE_TIMESTAMP };
398 SQLULEN paramSize { 23 };
399 SQLSMALLINT decimalDigits { 3 };
400 SQLSMALLINT nullable {};
401 } hints;
402 auto const sqlDescribeParamResult =
403 SQLDescribeParam(stmt, column, &hints.sqlType, &hints.paramSize, &hints.decimalDigits, &hints.nullable);
404 if (SQL_SUCCEEDED(sqlDescribeParamResult))
405 {
406 return SQLBindParameter(stmt,
407 column,
408 SQL_PARAM_INPUT,
409 SQL_C_TIMESTAMP,
410 hints.sqlType,
411 hints.paramSize,
412 hints.decimalDigits,
413 (SQLPOINTER) &values->sqlValue,
414 sizeof(*values),
415 indicators);
416 }
417 }
418#endif
419
420 return SQLBindParameter(stmt,
421 column,
422 SQL_PARAM_INPUT,
423 SQL_C_TIMESTAMP,
424 SQL_TYPE_TIMESTAMP,
425 27,
426 7,
427 (SQLPOINTER) &values->sqlValue,
428 sizeof(*values),
429 indicators);
430 }
431
432 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN OutputColumn(
433 SQLHSTMT stmt, SQLUSMALLINT column, SqlDateTime* result, SQLLEN* indicator, SqlDataBinderCallback& /*cb*/) noexcept
434 {
435 // TODO: handle indicator to check for NULL values
436 *indicator = sizeof(result->sqlValue);
437 return SQLBindCol(stmt, column, SQL_C_TYPE_TIMESTAMP, &result->sqlValue, 0, indicator);
438 }
439
440 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN GetColumn(SQLHSTMT stmt,
441 SQLUSMALLINT column,
442 SqlDateTime* result,
443 SQLLEN* indicator,
444 SqlDataBinderCallback const& /*cb*/) noexcept
445 {
446 return SQLGetData(stmt, column, SQL_C_TYPE_TIMESTAMP, &result->sqlValue, sizeof(result->sqlValue), indicator);
447 }
448
449 static LIGHTWEIGHT_FORCE_INLINE std::string Inspect(SqlDateTime const& value) noexcept
450 {
451 return std::format("{}", value);
452 }
453};
454
455template <>
456inline constexpr bool SqlIsNativeRowBindableValue<SqlDateTime> = true;
457
458} // namespace Lightweight
constexpr LIGHTWEIGHT_FORCE_INLINE std::chrono::year year() const noexcept
Returns the year of this date-time object.
constexpr LIGHTWEIGHT_FORCE_INLINE std::chrono::day day() const noexcept
Returns the day of this date-time object.
static LIGHTWEIGHT_FORCE_INLINE SQL_TIMESTAMP_STRUCT constexpr ConvertToSqlValue(std::chrono::year_month_day ymd, std::chrono::hh_mm_ss< duration_type > hms) noexcept
Converts year_month_day and hh_mm_ss components to the underlying SQL timestamp structure.
static LIGHTWEIGHT_FORCE_INLINE SqlDateTime Now() noexcept
Returns the current date and time.
constexpr bool operator==(SqlDateTime const &other) const noexcept
Equality comparison operator.
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 SqlDate Date() const noexcept
Constructs only a date from a SQL date-time structure.
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.
std::chrono::system_clock::time_point native_type
The native C++ type representing a date-time value.
constexpr LIGHTWEIGHT_FORCE_INLINE std::chrono::month month() const noexcept
Returns the month of this date-time object.
SQL_TIMESTAMP_STRUCT sqlValue
Holds the underlying SQL timestamp structure.
constexpr LIGHTWEIGHT_FORCE_INLINE std::chrono::seconds second() const noexcept
Returns the second of this date-time object.
constexpr LIGHTWEIGHT_FORCE_INLINE std::chrono::hours hour() const noexcept
Returns the hour of this date-time object.
std::chrono::system_clock::duration duration_type
The duration type used for arithmetic operations.
constexpr LIGHTWEIGHT_FORCE_INLINE std::chrono::minutes minute() const noexcept
Returns the minute of this date-time object.
LIGHTWEIGHT_FORCE_INLINE SqlDateTime & operator-=(duration_type duration) noexcept
Subtracts a duration from this date-time.
constexpr LIGHTWEIGHT_FORCE_INLINE std::chrono::nanoseconds nanosecond() const noexcept
Returns the nanosecond of this date-time object.
constexpr SqlDateTime() noexcept=default
Default constructor.
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.
static LIGHTWEIGHT_FORCE_INLINE SqlDateTime NowUTC() noexcept
Return the current date and time in UTC.
constexpr LIGHTWEIGHT_FORCE_INLINE SqlTime Time() const noexcept
Constructs only a time from a SQL date-time structure.
static LIGHTWEIGHT_FORCE_INLINE SQL_TIMESTAMP_STRUCT constexpr ConvertToSqlValue(native_type value) noexcept
Converts a native time_point to the underlying SQL timestamp structure.
LIGHTWEIGHT_FORCE_INLINE SqlDateTime & operator+=(duration_type duration) noexcept
Adds a duration to this date-time.
constexpr LIGHTWEIGHT_FORCE_INLINE native_type value() const noexcept
Returns the current date and time.
constexpr bool operator!=(SqlDateTime const &other) const noexcept
Inequality comparison operator.
static LIGHTWEIGHT_FORCE_INLINE native_type constexpr ConvertToNative(SQL_TIMESTAMP_STRUCT const &time) noexcept
Converts a SQL timestamp structure to the native time_point representation.