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