fb-cpp 0.0.2
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)
52 : client{&client}
53 {
54 }
55
56 public:
57 OpaqueDate dateToOpaqueDate(const Date& date)
58 {
59 if (!date.ok())
60 throwInvalidDateValue();
61
62 const auto yearValue = static_cast<unsigned>(static_cast<int>(date.year()));
63 const auto monthValue = static_cast<unsigned>(date.month());
64 const auto dayValue = static_cast<unsigned>(date.day());
65
66 if (yearValue <= 0)
67 throwInvalidDateValue();
68
69 return OpaqueDate{client->getUtil()->encodeDate(static_cast<unsigned>(yearValue), monthValue, dayValue)};
70 }
71
72 Date opaqueDateToDate(OpaqueDate date)
73 {
74 unsigned year;
75 unsigned month;
76 unsigned day;
77
78 client->getUtil()->decodeDate(date.value, &year, &month, &day);
79
80 return Date{std::chrono::year{static_cast<int>(year)}, std::chrono::month{month}, std::chrono::day{day}};
81 }
82
83 Date stringToDate(std::string_view value)
84 {
85 static const std::regex pattern(R"(^\s*([0-9]{4})\s*-\s*([0-9]{2})\s*-\s*([0-9]{2})\s*$)");
86
87 const std::string stringValue{value};
88
89 std::smatch matches;
90 if (!std::regex_match(stringValue, matches, pattern))
91 throwConversionErrorFromString(std::string{value});
92
93 const auto makeComponentView = [&](const std::size_t index)
94 {
95 return std::string_view{stringValue.data() + static_cast<std::size_t>(matches.position(index)),
96 static_cast<std::size_t>(matches.length(index))};
97 };
98
99 const auto parseComponent = [&](const std::size_t index)
100 {
101 int result;
102 const auto component = makeComponentView(index);
103 const auto [ptr, ec] =
104 std::from_chars(component.data(), component.data() + component.size(), result, 10);
105
106 if (ec != std::errc{} || ptr != component.data() + component.size())
107 throwConversionErrorFromString(std::string{value});
108
109 return result;
110 };
111
112 const int year = parseComponent(1);
113 const unsigned month = static_cast<unsigned>(parseComponent(2));
114 const unsigned day = static_cast<unsigned>(parseComponent(3));
115
116 const Date date{std::chrono::year{year}, std::chrono::month{month}, std::chrono::day{day}};
117
118 if (!date.ok())
119 throwInvalidDateValue();
120
121 return date;
122 }
123
124 OpaqueDate stringToOpaqueDate(std::string_view value)
125 {
126 return dateToOpaqueDate(stringToDate(value));
127 }
128
129 std::string opaqueDateToString(OpaqueDate date)
130 {
131 return std::format("{:%Y-%m-%d}", std::chrono::local_days{opaqueDateToDate(date)});
132 }
133
134 OpaqueTime timeToOpaqueTime(const Time& time)
135 {
136 const auto hours = static_cast<unsigned>(time.hours().count());
137 const auto minutes = static_cast<unsigned>(time.minutes().count());
138 const auto seconds = static_cast<unsigned>(time.seconds().count());
139 const auto subseconds = static_cast<unsigned>(time.subseconds().count() / 100);
140
141 OpaqueTime opaqueTime;
142 opaqueTime.value = client->getUtil()->encodeTime(hours, minutes, seconds, subseconds);
143
144 return opaqueTime;
145 }
146
147 Time opaqueTimeToTime(OpaqueTime time)
148 {
149 unsigned hours;
150 unsigned minutes;
151 unsigned seconds;
152 unsigned subseconds;
153
154 const auto util = client->getUtil();
155 util->decodeTime(time.value, &hours, &minutes, &seconds, &subseconds);
156
157 const auto timeOfDay = std::chrono::hours{hours} + std::chrono::minutes{minutes} +
158 std::chrono::seconds{seconds} + std::chrono::microseconds{static_cast<std::int64_t>(subseconds) * 100};
159
160 return Time{timeOfDay};
161 }
162
163 Time stringToTime(std::string_view value)
164 {
165 static const std::regex pattern(
166 R"(^\s*([0-9]{2})\s*:\s*([0-9]{2})\s*:\s*([0-9]{2})(?:\s*\.\s*([0-9]{1,4}))?\s*$)");
167
168 const std::string stringValue{value};
169
170 std::smatch matches;
171 if (!std::regex_match(stringValue, matches, pattern))
172 throwConversionErrorFromString(std::string{value});
173
174 const auto makeComponentView = [&](const std::size_t index)
175 {
176 return std::string_view{stringValue.data() + static_cast<std::size_t>(matches.position(index)),
177 static_cast<std::size_t>(matches.length(index))};
178 };
179
180 const auto parseComponent = [&](const std::size_t index)
181 {
182 unsigned result;
183 const auto component = makeComponentView(index);
184 const auto [ptr, ec] =
185 std::from_chars(component.data(), component.data() + component.size(), result, 10);
186
187 if (ec != std::errc{} || ptr != component.data() + component.size())
188 throwConversionErrorFromString(std::string{value});
189
190 return result;
191 };
192
193 const auto hours = parseComponent(1);
194 const auto minutes = parseComponent(2);
195 const auto seconds = parseComponent(3);
196
197 unsigned fractions = 0;
198 if (matches[4].matched && matches.length(4) > 0)
199 {
200 const auto fractionComponent = makeComponentView(4);
201 const auto [ptr, ec] = std::from_chars(
202 fractionComponent.data(), fractionComponent.data() + fractionComponent.size(), fractions, 10);
203
204 if (ec != std::errc{} || ptr != fractionComponent.data() + fractionComponent.size())
205 throwConversionErrorFromString(std::string{value});
206
207 for (auto remaining = 4 - static_cast<int>(fractionComponent.size()); remaining > 0; --remaining)
208 fractions *= 10;
209 }
210
211 if (hours >= 24 || minutes >= 60 || seconds >= 60)
212 throwInvalidTimeValue();
213
214 const auto timeOfDay = std::chrono::hours{hours} + std::chrono::minutes{minutes} +
215 std::chrono::seconds{seconds} + std::chrono::microseconds{static_cast<std::int64_t>(fractions) * 100};
216
217 if (timeOfDay >= std::chrono::hours{24})
218 throwInvalidTimeValue();
219
220 return Time{std::chrono::duration_cast<std::chrono::microseconds>(timeOfDay)};
221 }
222
223 OpaqueTime stringToOpaqueTime(std::string_view value)
224 {
225 return timeToOpaqueTime(stringToTime(value));
226 }
227
228 std::string opaqueTimeToString(OpaqueTime time)
229 {
230 const auto converted = opaqueTimeToTime(time);
231 const auto subseconds = static_cast<unsigned>(converted.subseconds().count() / 100);
232
233 return std::format("{:02}:{:02}:{:02}.{:04}", static_cast<unsigned>(converted.hours().count()),
234 static_cast<unsigned>(converted.minutes().count()), static_cast<unsigned>(converted.seconds().count()),
235 subseconds);
236 }
237
238 OpaqueTimeTz timeTzToOpaqueTimeTz(StatusWrapper* statusWrapper, const TimeTz& timeTz)
239 {
240 const auto duration = timeTz.utcTime.to_duration();
241
242 if (duration.count() < 0)
243 throwInvalidTimeValue();
244
245 const auto dayDuration = std::chrono::hours{24};
246 if (duration >= dayDuration)
247 throwInvalidTimeValue();
248
249 if (duration.count() % 100 != 0)
250 throwInvalidTimeValue();
251
252 OpaqueTimeTz opaque{};
253
254 client->getUtil()->encodeTimeTz(statusWrapper, &opaque.value, 0u, 0u, 0u, 0u, timeTz.zone.c_str());
255
256 opaque.value.utc_time = static_cast<ISC_TIME>(duration.count() / 100);
257
258 return opaque;
259 }
260
261 TimeTz opaqueTimeTzToTimeTz(
262 StatusWrapper* statusWrapper, 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(StatusWrapper* statusWrapper, std::string_view value)
286 {
287 return timeTzToOpaqueTimeTz(statusWrapper, stringToTimeTz(statusWrapper, value));
288 }
289
290 std::string opaqueTimeTzToString(StatusWrapper* statusWrapper, 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(StatusWrapper* statusWrapper, 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(statusWrapper, 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(StatusWrapper* statusWrapper, 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(StatusWrapper* statusWrapper,
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(StatusWrapper* statusWrapper, std::string_view value)
557 {
558 return timestampTzToOpaqueTimestampTz(statusWrapper, stringToTimestampTz(statusWrapper, value));
559 }
560
561 std::string opaqueTimestampTzToString(StatusWrapper* statusWrapper, 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(StatusWrapper* statusWrapper, 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(statusWrapper, 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 };
717} // namespace fbcpp::impl
718
719
720#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