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