fb-cpp 0.0.1
A modern C++ wrapper for the Firebird database API
Loading...
Searching...
No Matches
CalendarConverter.h
1/*
2 * MIT License
3 *
4 * Copyright (c) 2025 Adriano dos Santos Fernandes
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in all
14 * copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 */
24
25#ifndef FBCPP_CALENDAR_CONVERTER_H
26#define FBCPP_CALENDAR_CONVERTER_H
27
28#include "config.h"
29#include "fb-api.h"
30#include "Client.h"
31#include "Exception.h"
32#include "types.h"
33#include <array>
34#include <charconv>
35#include <chrono>
36#include <cstdlib>
37#include <format>
38#include <regex>
39#include <stdexcept>
40#include <string>
41#include <string_view>
42#include <utility>
43
44
45namespace fbcpp::impl
46{
47 // FIXME: review methods
48 class CalendarConverter final
49 {
50 public:
51 explicit CalendarConverter(Client& client, StatusWrapper* statusWrapper)
52 : client{client},
53 statusWrapper{statusWrapper}
54 {
55 }
56
57 public:
58 OpaqueDate dateToOpaqueDate(const Date& date)
59 {
60 if (!date.ok())
61 throwInvalidDateValue();
62
63 const auto yearValue = static_cast<unsigned>(static_cast<int>(date.year()));
64 const auto monthValue = static_cast<unsigned>(date.month());
65 const auto dayValue = static_cast<unsigned>(date.day());
66
67 if (yearValue <= 0)
68 throwInvalidDateValue();
69
70 return OpaqueDate{client.getUtil()->encodeDate(static_cast<unsigned>(yearValue), monthValue, dayValue)};
71 }
72
73 Date opaqueDateToDate(OpaqueDate date)
74 {
75 unsigned year;
76 unsigned month;
77 unsigned day;
78
79 client.getUtil()->decodeDate(date.value, &year, &month, &day);
80
81 return Date{std::chrono::year{static_cast<int>(year)}, std::chrono::month{month}, std::chrono::day{day}};
82 }
83
84 Date stringToDate(std::string_view value)
85 {
86 static const std::regex pattern(R"(^\s*([0-9]{4})\s*-\s*([0-9]{2})\s*-\s*([0-9]{2})\s*$)");
87
88 const std::string stringValue{value};
89
90 std::smatch matches;
91 if (!std::regex_match(stringValue, matches, pattern))
92 throwConversionErrorFromString(std::string{value});
93
94 const auto makeComponentView = [&](const std::size_t index)
95 {
96 return std::string_view{stringValue.data() + static_cast<std::size_t>(matches.position(index)),
97 static_cast<std::size_t>(matches.length(index))};
98 };
99
100 const auto parseComponent = [&](const std::size_t index)
101 {
102 int result;
103 const auto component = makeComponentView(index);
104 const auto [ptr, ec] =
105 std::from_chars(component.data(), component.data() + component.size(), result, 10);
106
107 if (ec != std::errc{} || ptr != component.data() + component.size())
108 throwConversionErrorFromString(std::string{value});
109
110 return result;
111 };
112
113 const int year = parseComponent(1);
114 const unsigned month = static_cast<unsigned>(parseComponent(2));
115 const unsigned day = static_cast<unsigned>(parseComponent(3));
116
117 const Date date{std::chrono::year{year}, std::chrono::month{month}, std::chrono::day{day}};
118
119 if (!date.ok())
120 throwInvalidDateValue();
121
122 return date;
123 }
124
125 OpaqueDate stringToOpaqueDate(std::string_view value)
126 {
127 return dateToOpaqueDate(stringToDate(value));
128 }
129
130 std::string opaqueDateToString(OpaqueDate date)
131 {
132 return std::format("{:%Y-%m-%d}", std::chrono::local_days{opaqueDateToDate(date)});
133 }
134
135 OpaqueTime timeToOpaqueTime(const Time& time)
136 {
137 const auto hours = static_cast<unsigned>(time.hours().count());
138 const auto minutes = static_cast<unsigned>(time.minutes().count());
139 const auto seconds = static_cast<unsigned>(time.seconds().count());
140 const auto subseconds = static_cast<unsigned>(time.subseconds().count() / 100);
141
142 OpaqueTime opaqueTime;
143 opaqueTime.value = client.getUtil()->encodeTime(hours, minutes, seconds, subseconds);
144
145 return opaqueTime;
146 }
147
148 Time opaqueTimeToTime(OpaqueTime time)
149 {
150 unsigned hours;
151 unsigned minutes;
152 unsigned seconds;
153 unsigned subseconds;
154
155 const auto util = client.getUtil();
156 util->decodeTime(time.value, &hours, &minutes, &seconds, &subseconds);
157
158 const auto timeOfDay = std::chrono::hours{hours} + std::chrono::minutes{minutes} +
159 std::chrono::seconds{seconds} + std::chrono::microseconds{static_cast<std::int64_t>(subseconds) * 100};
160
161 return Time{timeOfDay};
162 }
163
164 Time stringToTime(std::string_view value)
165 {
166 static const std::regex pattern(
167 R"(^\s*([0-9]{2})\s*:\s*([0-9]{2})\s*:\s*([0-9]{2})(?:\s*\.\s*([0-9]{1,4}))?\s*$)");
168
169 const std::string stringValue{value};
170
171 std::smatch matches;
172 if (!std::regex_match(stringValue, matches, pattern))
173 throwConversionErrorFromString(std::string{value});
174
175 const auto makeComponentView = [&](const std::size_t index)
176 {
177 return std::string_view{stringValue.data() + static_cast<std::size_t>(matches.position(index)),
178 static_cast<std::size_t>(matches.length(index))};
179 };
180
181 const auto parseComponent = [&](const std::size_t index)
182 {
183 unsigned result;
184 const auto component = makeComponentView(index);
185 const auto [ptr, ec] =
186 std::from_chars(component.data(), component.data() + component.size(), result, 10);
187
188 if (ec != std::errc{} || ptr != component.data() + component.size())
189 throwConversionErrorFromString(std::string{value});
190
191 return result;
192 };
193
194 const auto hours = parseComponent(1);
195 const auto minutes = parseComponent(2);
196 const auto seconds = parseComponent(3);
197
198 unsigned fractions = 0;
199 if (matches[4].matched && matches.length(4) > 0)
200 {
201 const auto fractionComponent = makeComponentView(4);
202 const auto [ptr, ec] = std::from_chars(
203 fractionComponent.data(), fractionComponent.data() + fractionComponent.size(), fractions, 10);
204
205 if (ec != std::errc{} || ptr != fractionComponent.data() + fractionComponent.size())
206 throwConversionErrorFromString(std::string{value});
207
208 for (auto remaining = 4 - static_cast<int>(fractionComponent.size()); remaining > 0; --remaining)
209 fractions *= 10;
210 }
211
212 if (hours >= 24 || minutes >= 60 || seconds >= 60)
213 throwInvalidTimeValue();
214
215 const auto timeOfDay = std::chrono::hours{hours} + std::chrono::minutes{minutes} +
216 std::chrono::seconds{seconds} + std::chrono::microseconds{static_cast<std::int64_t>(fractions) * 100};
217
218 if (timeOfDay >= std::chrono::hours{24})
219 throwInvalidTimeValue();
220
221 return Time{std::chrono::duration_cast<std::chrono::microseconds>(timeOfDay)};
222 }
223
224 OpaqueTime stringToOpaqueTime(std::string_view value)
225 {
226 return timeToOpaqueTime(stringToTime(value));
227 }
228
229 std::string opaqueTimeToString(OpaqueTime time)
230 {
231 const auto converted = opaqueTimeToTime(time);
232 const auto subseconds = static_cast<unsigned>(converted.subseconds().count() / 100);
233
234 return std::format("{:02}:{:02}:{:02}.{:04}", static_cast<unsigned>(converted.hours().count()),
235 static_cast<unsigned>(converted.minutes().count()), static_cast<unsigned>(converted.seconds().count()),
236 subseconds);
237 }
238
239 OpaqueTimeTz timeTzToOpaqueTimeTz(const TimeTz& timeTz)
240 {
241 const auto duration = timeTz.utcTime.to_duration();
242
243 if (duration.count() < 0)
244 throwInvalidTimeValue();
245
246 const auto dayDuration = std::chrono::hours{24};
247 if (duration >= dayDuration)
248 throwInvalidTimeValue();
249
250 if (duration.count() % 100 != 0)
251 throwInvalidTimeValue();
252
253 OpaqueTimeTz opaque{};
254
255 client.getUtil()->encodeTimeTz(statusWrapper, &opaque.value, 0u, 0u, 0u, 0u, timeTz.zone.c_str());
256
257 opaque.value.utc_time = static_cast<ISC_TIME>(duration.count() / 100);
258
259 return opaque;
260 }
261
262 TimeTz opaqueTimeTzToTimeTz(const OpaqueTimeTz& opaqueTime, std::string* decodedTimeZoneName = nullptr)
263 {
264 const auto ticks = static_cast<std::int64_t>(opaqueTime.value.utc_time) * 100;
265
266 unsigned hours;
267 unsigned minutes;
268 unsigned seconds;
269 unsigned fractions;
270 std::array<char, 128> timeZoneBuffer;
271
272 client.getUtil()->decodeTimeTz(statusWrapper, &opaqueTime.value, &hours, &minutes, &seconds, &fractions,
273 static_cast<unsigned>(timeZoneBuffer.size()), timeZoneBuffer.data());
274
275 TimeTz timeTz;
276 timeTz.utcTime = Time{std::chrono::microseconds{ticks}};
277 timeTz.zone = timeZoneBuffer.data();
278
279 if (decodedTimeZoneName)
280 *decodedTimeZoneName = timeTz.zone;
281
282 return timeTz;
283 }
284
285 OpaqueTimeTz stringToOpaqueTimeTz(std::string_view value)
286 {
287 return timeTzToOpaqueTimeTz(stringToTimeTz(value));
288 }
289
290 std::string opaqueTimeTzToString(const OpaqueTimeTz& time)
291 {
292 unsigned hours;
293 unsigned minutes;
294 unsigned seconds;
295 unsigned fractions;
296 std::array<char, 128> timeZoneBuffer;
297
298 client.getUtil()->decodeTimeTz(statusWrapper, &time.value, &hours, &minutes, &seconds, &fractions,
299 static_cast<unsigned>(timeZoneBuffer.size()), timeZoneBuffer.data());
300
301 return std::format("{:02}:{:02}:{:02}.{:04} {}", hours, minutes, seconds, fractions, timeZoneBuffer.data());
302 }
303
304 TimeTz stringToTimeTz(std::string_view value)
305 {
306 static const std::regex pattern(
307 R"(^\s*([0-9]{2})\s*:\s*([0-9]{2})\s*:\s*([0-9]{2})(?:\s*\.\s*([0-9]{1,4}))?\s+([^\s]+)\s*$)");
308
309 const std::string stringValue{value};
310
311 std::smatch matches;
312 if (!std::regex_match(stringValue, matches, pattern))
313 throwConversionErrorFromString(std::string{value});
314
315 const auto makeComponentView = [&](const std::size_t index)
316 {
317 return std::string_view{stringValue.data() + static_cast<std::size_t>(matches.position(index)),
318 static_cast<std::size_t>(matches.length(index))};
319 };
320
321 const auto parseComponent = [&](const std::size_t index)
322 {
323 unsigned result;
324 const auto component = makeComponentView(index);
325 const auto [ptr, ec] =
326 std::from_chars(component.data(), component.data() + component.size(), result, 10);
327
328 if (ec != std::errc{} || ptr != component.data() + component.size())
329 throwConversionErrorFromString(std::string{value});
330
331 return result;
332 };
333
334 const auto hours = parseComponent(1);
335 const auto minutes = parseComponent(2);
336 const auto seconds = parseComponent(3);
337
338 unsigned fractions = 0;
339 if (matches[4].matched && matches.length(4) > 0)
340 {
341 const auto fractionComponent = makeComponentView(4);
342 const auto [ptr, ec] = std::from_chars(
343 fractionComponent.data(), fractionComponent.data() + fractionComponent.size(), fractions, 10);
344
345 if (ec != std::errc{} || ptr != fractionComponent.data() + fractionComponent.size())
346 throwConversionErrorFromString(std::string{value});
347
348 for (auto remaining = 4 - static_cast<int>(fractionComponent.size()); remaining > 0; --remaining)
349 fractions *= 10;
350 }
351
352 if (hours >= 24 || minutes >= 60 || seconds >= 60)
353 throwInvalidTimeValue();
354
355 const auto timeOfDay = std::chrono::hours{hours} + std::chrono::minutes{minutes} +
356 std::chrono::seconds{seconds} + std::chrono::microseconds{static_cast<std::int64_t>(fractions) * 100};
357
358 if (timeOfDay >= std::chrono::hours{24})
359 throwInvalidTimeValue();
360
361 OpaqueTimeTz encoded;
362 const std::string timeZoneString{makeComponentView(5)};
363 client.getUtil()->encodeTimeTz(
364 statusWrapper, &encoded.value, hours, minutes, seconds, fractions, timeZoneString.c_str());
365
366 return opaqueTimeTzToTimeTz(encoded);
367 }
368
369 // FIXME: review
370 OpaqueTimestamp timestampToOpaqueTimestamp(const Timestamp& timestamp)
371 {
372 const auto& date = timestamp.date;
373 if (!date.ok())
374 throwInvalidTimestampValue();
375
376 const auto opaqueDate = dateToOpaqueDate(date);
377
378 const auto timeOfDay = timestamp.time.to_duration();
379 if (timeOfDay.count() < 0 || timeOfDay >= std::chrono::hours{24})
380 throwInvalidTimestampValue();
381
382 if (timestamp.time.is_negative())
383 throwInvalidTimestampValue();
384
385 const auto subseconds = timestamp.time.subseconds().count();
386 if (subseconds % 100 != 0)
387 throwInvalidTimestampValue();
388
389 OpaqueTimestamp opaqueTimestamp;
390 opaqueTimestamp.value.timestamp_date = opaqueDate.value;
391 opaqueTimestamp.value.timestamp_time =
392 client.getUtil()->encodeTime(static_cast<unsigned>(timestamp.time.hours().count()),
393 static_cast<unsigned>(timestamp.time.minutes().count()),
394 static_cast<unsigned>(timestamp.time.seconds().count()), static_cast<unsigned>(subseconds / 100));
395
396 return opaqueTimestamp;
397 }
398
399 Timestamp opaqueTimestampToTimestamp(OpaqueTimestamp timestamp)
400 {
401 unsigned year;
402 unsigned month;
403 unsigned day;
404 unsigned hours;
405 unsigned minutes;
406 unsigned seconds;
407 unsigned subseconds;
408
409 const auto util = client.getUtil();
410 util->decodeDate(timestamp.value.timestamp_date, &year, &month, &day);
411 util->decodeTime(timestamp.value.timestamp_time, &hours, &minutes, &seconds, &subseconds);
412
413 const auto timeOfDay = std::chrono::hours{hours} + std::chrono::minutes{minutes} +
414 std::chrono::seconds{seconds} + std::chrono::microseconds{static_cast<std::int64_t>(subseconds) * 100};
415
416 const Date date{
417 std::chrono::year{static_cast<int>(year)}, std::chrono::month{month}, std::chrono::day{day}};
418
419 if (!date.ok())
420 throwInvalidTimestampValue();
421
422 return Timestamp{date, Time{timeOfDay}};
423 }
424
425 Timestamp stringToTimestamp(std::string_view value)
426 {
427 static const std::regex pattern(
428 R"(^\s*([0-9]{4})\s*-\s*([0-9]{2})\s*-\s*([0-9]{2})\s+([0-9]{2})\s*:\s*([0-9]{2})\s*:\s*([0-9]{2})(?:\s*\.\s*([0-9]{1,4}))?\s*$)");
429
430 const std::string stringValue{value};
431
432 std::smatch matches;
433 if (!std::regex_match(stringValue, matches, pattern))
434 throwConversionErrorFromString(std::string{value});
435
436 const auto makeComponentView = [&](const std::size_t index)
437 {
438 return std::string_view{stringValue.data() + static_cast<std::size_t>(matches.position(index)),
439 static_cast<std::size_t>(matches.length(index))};
440 };
441
442 const auto parseComponent = [&](const std::size_t index)
443 {
444 unsigned result;
445 const auto component = makeComponentView(index);
446 const auto [ptr, ec] =
447 std::from_chars(component.data(), component.data() + component.size(), result, 10);
448
449 if (ec != std::errc{} || ptr != component.data() + component.size())
450 throwConversionErrorFromString(std::string{value});
451
452 return result;
453 };
454
455 const int year = static_cast<int>(parseComponent(1));
456 const unsigned month = parseComponent(2);
457 const unsigned day = parseComponent(3);
458 const auto hours = parseComponent(4);
459 const auto minutes = parseComponent(5);
460 const auto seconds = parseComponent(6);
461
462 unsigned fractions = 0;
463 if (matches[7].matched && matches.length(7) > 0)
464 {
465 const auto fractionComponent = makeComponentView(7);
466 const auto [ptr, ec] = std::from_chars(
467 fractionComponent.data(), fractionComponent.data() + fractionComponent.size(), fractions, 10);
468
469 if (ec != std::errc{} || ptr != fractionComponent.data() + fractionComponent.size())
470 throwConversionErrorFromString(std::string{value});
471
472 for (auto remaining = 4 - static_cast<int>(fractionComponent.size()); remaining > 0; --remaining)
473 fractions *= 10;
474 }
475
476 const Date date{std::chrono::year{year}, std::chrono::month{month}, std::chrono::day{day}};
477
478 if (!date.ok())
479 throwInvalidTimestampValue();
480
481 if (hours >= 24 || minutes >= 60 || seconds >= 60)
482 throwInvalidTimestampValue();
483
484 const auto timeOfDay = std::chrono::hours{hours} + std::chrono::minutes{minutes} +
485 std::chrono::seconds{seconds} + std::chrono::microseconds{static_cast<std::int64_t>(fractions) * 100};
486
487 if (timeOfDay >= std::chrono::hours{24})
488 throwInvalidTimestampValue();
489
490 return Timestamp{date, Time{timeOfDay}};
491 }
492
493 OpaqueTimestamp stringToOpaqueTimestamp(std::string_view value)
494 {
495 return timestampToOpaqueTimestamp(stringToTimestamp(value));
496 }
497
498 std::string opaqueTimestampToString(OpaqueTimestamp timestamp)
499 {
500 const auto converted = opaqueTimestampToTimestamp(timestamp);
501 const auto subseconds = static_cast<unsigned>(converted.time.subseconds().count() / 100);
502
503 const auto dateString = std::format("{:%Y-%m-%d}", std::chrono::local_days{converted.date});
504 const auto timeString =
505 std::format("{:02}:{:02}:{:02}.{:04}", static_cast<unsigned>(converted.time.hours().count()),
506 static_cast<unsigned>(converted.time.minutes().count()),
507 static_cast<unsigned>(converted.time.seconds().count()), subseconds);
508
509 return std::format("{} {}", dateString, timeString);
510 }
511
512 OpaqueTimestampTz timestampTzToOpaqueTimestampTz(const TimestampTz& timestampTz)
513 {
514 OpaqueTimestampTz opaque;
515
516 client.getUtil()->encodeTimeStampTz(
517 statusWrapper, &opaque.value, 1u, 1u, 1u, 0u, 0u, 0u, 0u, timestampTz.zone.c_str());
518
519 const auto utcOpaque = timestampToOpaqueTimestamp(timestampTz.utcTimestamp);
520 opaque.value.utc_timestamp = utcOpaque.value;
521
522 return opaque;
523 }
524
525 TimestampTz opaqueTimestampTzToTimestampTz(
526 const OpaqueTimestampTz& opaqueTimestamp, std::string* decodedTimeZoneName = nullptr)
527 {
528 const auto ticks =
529 (static_cast<std::int64_t>(opaqueTimestamp.value.utc_timestamp.timestamp_date) * TICKS_PER_DAY +
530 static_cast<std::int64_t>(opaqueTimestamp.value.utc_timestamp.timestamp_time)) *
531 100;
532
533 unsigned year;
534 unsigned month;
535 unsigned day;
536 unsigned hours;
537 unsigned minutes;
538 unsigned seconds;
539 unsigned subseconds;
540 std::array<char, 128> timeZoneBuffer;
541
542 client.getUtil()->decodeTimeStampTz(statusWrapper, &opaqueTimestamp.value, &year, &month, &day, &hours,
543 &minutes, &seconds, &subseconds, static_cast<unsigned>(timeZoneBuffer.size()), timeZoneBuffer.data());
544
545 TimestampTz timestampTz;
546 const auto utcLocalTime = BASE_EPOCH + std::chrono::microseconds{ticks};
547 timestampTz.utcTimestamp = Timestamp::fromLocalTime(utcLocalTime);
548 timestampTz.zone = timeZoneBuffer.data();
549
550 if (decodedTimeZoneName)
551 *decodedTimeZoneName = timestampTz.zone;
552
553 return timestampTz;
554 }
555
556 OpaqueTimestampTz stringToOpaqueTimestampTz(std::string_view value)
557 {
558 return timestampTzToOpaqueTimestampTz(stringToTimestampTz(value));
559 }
560
561 std::string opaqueTimestampTzToString(const OpaqueTimestampTz& timestamp)
562 {
563 unsigned year;
564 unsigned month;
565 unsigned day;
566 unsigned hours;
567 unsigned minutes;
568 unsigned seconds;
569 unsigned subseconds;
570 std::array<char, 128> timeZoneBuffer;
571
572 client.getUtil()->decodeTimeStampTz(statusWrapper, &timestamp.value, &year, &month, &day, &hours, &minutes,
573 &seconds, &subseconds, static_cast<unsigned>(timeZoneBuffer.size()), timeZoneBuffer.data());
574
575 return std::format("{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:04} {}", year, month, day, hours, minutes,
576 seconds, subseconds, timeZoneBuffer.data());
577 }
578
579 TimestampTz stringToTimestampTz(std::string_view value)
580 {
581 static const std::regex pattern(
582 R"(^\s*([0-9]{4})\s*-\s*([0-9]{2})\s*-\s*([0-9]{2})\s+([0-9]{2})\s*:\s*([0-9]{2})\s*:\s*([0-9]{2})(?:\s*\.\s*([0-9]{1,4}))?\s+([^\s]+)\s*$)");
583
584 const std::string stringValue{value};
585
586 std::smatch matches;
587 if (!std::regex_match(stringValue, matches, pattern))
588 throwConversionErrorFromString(std::string{value});
589
590 const auto makeComponentView = [&](const std::size_t index)
591 {
592 return std::string_view{stringValue.data() + static_cast<std::size_t>(matches.position(index)),
593 static_cast<std::size_t>(matches.length(index))};
594 };
595
596 const auto parseComponent = [&](const std::size_t index)
597 {
598 unsigned result;
599 const auto component = makeComponentView(index);
600 const auto [ptr, ec] =
601 std::from_chars(component.data(), component.data() + component.size(), result, 10);
602
603 if (ec != std::errc{} || ptr != component.data() + component.size())
604 throwConversionErrorFromString(std::string{value});
605
606 return result;
607 };
608
609 const int year = static_cast<int>(parseComponent(1));
610 const unsigned month = parseComponent(2);
611 const unsigned day = parseComponent(3);
612 const auto hours = parseComponent(4);
613 const auto minutes = parseComponent(5);
614 const auto seconds = parseComponent(6);
615
616 unsigned fractions = 0;
617 if (matches[7].matched && matches.length(7) > 0)
618 {
619 const auto fractionComponent = makeComponentView(7);
620 const auto [ptr, ec] = std::from_chars(
621 fractionComponent.data(), fractionComponent.data() + fractionComponent.size(), fractions, 10);
622
623 if (ec != std::errc{} || ptr != fractionComponent.data() + fractionComponent.size())
624 throwConversionErrorFromString(std::string{value});
625
626 for (auto remaining = 4 - static_cast<int>(fractionComponent.size()); remaining > 0; --remaining)
627 fractions *= 10;
628 }
629
630 const Date date{std::chrono::year{year}, std::chrono::month{month}, std::chrono::day{day}};
631
632 if (!date.ok())
633 throwInvalidTimestampValue();
634
635 if (hours >= 24 || minutes >= 60 || seconds >= 60)
636 throwInvalidTimestampValue();
637
638 const auto timeOfDay = std::chrono::hours{hours} + std::chrono::minutes{minutes} +
639 std::chrono::seconds{seconds} + std::chrono::microseconds{static_cast<std::int64_t>(fractions) * 100};
640
641 if (timeOfDay >= std::chrono::hours{24})
642 throwInvalidTimestampValue();
643
644 const Timestamp localTimestamp{date, Time{timeOfDay}};
645
646 const auto monthValue = static_cast<unsigned>(date.month());
647 const auto dayValue = static_cast<unsigned>(date.day());
648
649 OpaqueTimestampTz encoded;
650 const std::string timeZoneString{makeComponentView(8)};
651 client.getUtil()->encodeTimeStampTz(statusWrapper, &encoded.value,
652 static_cast<unsigned>(static_cast<int>(date.year())), monthValue, dayValue, hours, minutes, seconds,
653 fractions, timeZoneString.c_str());
654
655 const OpaqueTimestamp utcOpaque{encoded.value.utc_timestamp};
656 const auto utcTimestamp = opaqueTimestampToTimestamp(utcOpaque);
657
658 const auto offsetDuration = localTimestamp.toLocalTime() - utcTimestamp.toLocalTime();
659 if (offsetDuration % std::chrono::minutes{1} != std::chrono::microseconds::zero())
660 throwInvalidTimestampValue();
661
662 std::string resolvedTimeZoneName;
663 opaqueTimestampTzToTimestampTz(encoded, &resolvedTimeZoneName);
664
665 return TimestampTz{utcTimestamp, resolvedTimeZoneName};
666 }
667
668 private:
669 [[noreturn]] void throwConversionErrorFromString(const std::string& str)
670 {
671 const std::intptr_t STATUS_CONVERSION_ERROR_FROM_STRING[] = {
672 isc_convert_error,
673 reinterpret_cast<std::intptr_t>(str.c_str()),
674 isc_arg_end,
675 };
676
677 throw DatabaseException(client, STATUS_CONVERSION_ERROR_FROM_STRING);
678 }
679
680 [[noreturn]] void throwInvalidDateValue()
681 {
682 static constexpr std::intptr_t STATUS_INVALID_DATE_VALUE[] = {
683 isc_invalid_date_val,
684 isc_arg_end,
685 };
686
687 throw DatabaseException(client, STATUS_INVALID_DATE_VALUE);
688 }
689
690 [[noreturn]] void throwInvalidTimeValue()
691 {
692 static constexpr std::intptr_t STATUS_INVALID_TIME_VALUE[] = {
693 isc_invalid_time_val,
694 isc_arg_end,
695 };
696
697 throw DatabaseException(client, STATUS_INVALID_TIME_VALUE);
698 }
699
700 [[noreturn]] void throwInvalidTimestampValue()
701 {
702 static constexpr std::intptr_t STATUS_INVALID_TIMESTAMP_VALUE[] = {
703 isc_invalid_timestamp_val,
704 isc_arg_end,
705 };
706
707 throw DatabaseException(client, STATUS_INVALID_TIMESTAMP_VALUE);
708 }
709
710 private:
711 static constexpr auto TICKS_PER_DAY = std::int64_t{24} * 60 * 60 * 10000;
712 static constexpr auto BASE_EPOCH = std::chrono::local_days{
713 std::chrono::year{1858} / std::chrono::November / 17,
714 };
715 Client& client;
716 StatusWrapper* statusWrapper;
717 };
718} // namespace fbcpp::impl
719
720
721#endif // FBCPP_CALENDAR_CONVERTER_H
std::chrono::year_month_day Date
Firebird SQL calendar date.
Definition types.h:108
std::chrono::hh_mm_ss< std::chrono::microseconds > Time
Firebird SQL time-of-day with microsecond resolution.
Definition types.h:113
static Timestamp fromLocalTime(std::chrono::local_time< std::chrono::microseconds > value) noexcept
Builds a timestamp from a local-time value.
Definition types.h:138