25#ifndef FBCPP_CALENDAR_CONVERTER_H
26#define FBCPP_CALENDAR_CONVERTER_H
48 class CalendarConverter final
51 explicit CalendarConverter(Client& client, StatusWrapper* statusWrapper)
53 statusWrapper{statusWrapper}
58 OpaqueDate dateToOpaqueDate(
const Date& date)
61 throwInvalidDateValue();
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());
68 throwInvalidDateValue();
70 return OpaqueDate{client.getUtil()->encodeDate(
static_cast<unsigned>(yearValue), monthValue, dayValue)};
73 Date opaqueDateToDate(OpaqueDate date)
79 client.getUtil()->decodeDate(date.value, &year, &month, &day);
81 return Date{std::chrono::year{
static_cast<int>(year)}, std::chrono::month{month}, std::chrono::day{day}};
84 Date stringToDate(std::string_view value)
86 static const std::regex pattern(R
"(^\s*([0-9]{4})\s*-\s*([0-9]{2})\s*-\s*([0-9]{2})\s*$)");
88 const std::string stringValue{value};
91 if (!std::regex_match(stringValue, matches, pattern))
92 throwConversionErrorFromString(std::string{value});
94 const auto makeComponentView = [&](
const std::size_t index)
96 return std::string_view{stringValue.data() +
static_cast<std::size_t
>(matches.position(index)),
97 static_cast<std::size_t
>(matches.length(index))};
100 const auto parseComponent = [&](
const std::size_t index)
103 const auto component = makeComponentView(index);
104 const auto [ptr, ec] =
105 std::from_chars(component.data(), component.data() + component.size(), result, 10);
107 if (ec != std::errc{} || ptr != component.data() + component.size())
108 throwConversionErrorFromString(std::string{value});
113 const int year = parseComponent(1);
114 const unsigned month =
static_cast<unsigned>(parseComponent(2));
115 const unsigned day =
static_cast<unsigned>(parseComponent(3));
117 const Date date{std::chrono::year{year}, std::chrono::month{month}, std::chrono::day{day}};
120 throwInvalidDateValue();
125 OpaqueDate stringToOpaqueDate(std::string_view value)
127 return dateToOpaqueDate(stringToDate(value));
130 std::string opaqueDateToString(OpaqueDate date)
132 return std::format(
"{:%Y-%m-%d}", std::chrono::local_days{opaqueDateToDate(date)});
135 OpaqueTime timeToOpaqueTime(
const Time& time)
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);
142 OpaqueTime opaqueTime;
143 opaqueTime.value = client.getUtil()->encodeTime(hours, minutes, seconds, subseconds);
148 Time opaqueTimeToTime(OpaqueTime time)
155 const auto util = client.getUtil();
156 util->decodeTime(time.value, &hours, &minutes, &seconds, &subseconds);
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};
161 return Time{timeOfDay};
164 Time stringToTime(std::string_view value)
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*$)");
169 const std::string stringValue{value};
172 if (!std::regex_match(stringValue, matches, pattern))
173 throwConversionErrorFromString(std::string{value});
175 const auto makeComponentView = [&](
const std::size_t index)
177 return std::string_view{stringValue.data() +
static_cast<std::size_t
>(matches.position(index)),
178 static_cast<std::size_t
>(matches.length(index))};
181 const auto parseComponent = [&](
const std::size_t index)
184 const auto component = makeComponentView(index);
185 const auto [ptr, ec] =
186 std::from_chars(component.data(), component.data() + component.size(), result, 10);
188 if (ec != std::errc{} || ptr != component.data() + component.size())
189 throwConversionErrorFromString(std::string{value});
194 const auto hours = parseComponent(1);
195 const auto minutes = parseComponent(2);
196 const auto seconds = parseComponent(3);
198 unsigned fractions = 0;
199 if (matches[4].matched && matches.length(4) > 0)
201 const auto fractionComponent = makeComponentView(4);
202 const auto [ptr, ec] = std::from_chars(
203 fractionComponent.data(), fractionComponent.data() + fractionComponent.size(), fractions, 10);
205 if (ec != std::errc{} || ptr != fractionComponent.data() + fractionComponent.size())
206 throwConversionErrorFromString(std::string{value});
208 for (
auto remaining = 4 -
static_cast<int>(fractionComponent.size()); remaining > 0; --remaining)
212 if (hours >= 24 || minutes >= 60 || seconds >= 60)
213 throwInvalidTimeValue();
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};
218 if (timeOfDay >= std::chrono::hours{24})
219 throwInvalidTimeValue();
221 return Time{std::chrono::duration_cast<std::chrono::microseconds>(timeOfDay)};
224 OpaqueTime stringToOpaqueTime(std::string_view value)
226 return timeToOpaqueTime(stringToTime(value));
229 std::string opaqueTimeToString(OpaqueTime time)
231 const auto converted = opaqueTimeToTime(time);
232 const auto subseconds =
static_cast<unsigned>(converted.subseconds().count() / 100);
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()),
239 OpaqueTimeTz timeTzToOpaqueTimeTz(
const TimeTz& timeTz)
241 const auto duration = timeTz.utcTime.to_duration();
243 if (duration.count() < 0)
244 throwInvalidTimeValue();
246 const auto dayDuration = std::chrono::hours{24};
247 if (duration >= dayDuration)
248 throwInvalidTimeValue();
250 if (duration.count() % 100 != 0)
251 throwInvalidTimeValue();
253 OpaqueTimeTz opaque{};
255 client.getUtil()->encodeTimeTz(statusWrapper, &opaque.value, 0u, 0u, 0u, 0u, timeTz.zone.c_str());
257 opaque.value.utc_time =
static_cast<ISC_TIME
>(duration.count() / 100);
262 TimeTz opaqueTimeTzToTimeTz(
const OpaqueTimeTz& opaqueTime, std::string* decodedTimeZoneName =
nullptr)
264 const auto ticks =
static_cast<std::int64_t
>(opaqueTime.value.utc_time) * 100;
270 std::array<char, 128> timeZoneBuffer;
272 client.getUtil()->decodeTimeTz(statusWrapper, &opaqueTime.value, &hours, &minutes, &seconds, &fractions,
273 static_cast<unsigned>(timeZoneBuffer.size()), timeZoneBuffer.data());
276 timeTz.utcTime =
Time{std::chrono::microseconds{ticks}};
277 timeTz.zone = timeZoneBuffer.data();
279 if (decodedTimeZoneName)
280 *decodedTimeZoneName = timeTz.zone;
285 OpaqueTimeTz stringToOpaqueTimeTz(std::string_view value)
287 return timeTzToOpaqueTimeTz(stringToTimeTz(value));
290 std::string opaqueTimeTzToString(
const OpaqueTimeTz& time)
296 std::array<char, 128> timeZoneBuffer;
298 client.getUtil()->decodeTimeTz(statusWrapper, &time.value, &hours, &minutes, &seconds, &fractions,
299 static_cast<unsigned>(timeZoneBuffer.size()), timeZoneBuffer.data());
301 return std::format(
"{:02}:{:02}:{:02}.{:04} {}", hours, minutes, seconds, fractions, timeZoneBuffer.data());
304 TimeTz stringToTimeTz(std::string_view value)
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*$)");
309 const std::string stringValue{value};
312 if (!std::regex_match(stringValue, matches, pattern))
313 throwConversionErrorFromString(std::string{value});
315 const auto makeComponentView = [&](
const std::size_t index)
317 return std::string_view{stringValue.data() +
static_cast<std::size_t
>(matches.position(index)),
318 static_cast<std::size_t
>(matches.length(index))};
321 const auto parseComponent = [&](
const std::size_t index)
324 const auto component = makeComponentView(index);
325 const auto [ptr, ec] =
326 std::from_chars(component.data(), component.data() + component.size(), result, 10);
328 if (ec != std::errc{} || ptr != component.data() + component.size())
329 throwConversionErrorFromString(std::string{value});
334 const auto hours = parseComponent(1);
335 const auto minutes = parseComponent(2);
336 const auto seconds = parseComponent(3);
338 unsigned fractions = 0;
339 if (matches[4].matched && matches.length(4) > 0)
341 const auto fractionComponent = makeComponentView(4);
342 const auto [ptr, ec] = std::from_chars(
343 fractionComponent.data(), fractionComponent.data() + fractionComponent.size(), fractions, 10);
345 if (ec != std::errc{} || ptr != fractionComponent.data() + fractionComponent.size())
346 throwConversionErrorFromString(std::string{value});
348 for (
auto remaining = 4 -
static_cast<int>(fractionComponent.size()); remaining > 0; --remaining)
352 if (hours >= 24 || minutes >= 60 || seconds >= 60)
353 throwInvalidTimeValue();
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};
358 if (timeOfDay >= std::chrono::hours{24})
359 throwInvalidTimeValue();
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());
366 return opaqueTimeTzToTimeTz(encoded);
370 OpaqueTimestamp timestampToOpaqueTimestamp(
const Timestamp& timestamp)
372 const auto& date = timestamp.date;
374 throwInvalidTimestampValue();
376 const auto opaqueDate = dateToOpaqueDate(date);
378 const auto timeOfDay = timestamp.time.to_duration();
379 if (timeOfDay.count() < 0 || timeOfDay >= std::chrono::hours{24})
380 throwInvalidTimestampValue();
382 if (timestamp.time.is_negative())
383 throwInvalidTimestampValue();
385 const auto subseconds = timestamp.time.subseconds().count();
386 if (subseconds % 100 != 0)
387 throwInvalidTimestampValue();
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));
396 return opaqueTimestamp;
399 Timestamp opaqueTimestampToTimestamp(OpaqueTimestamp timestamp)
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);
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};
417 std::chrono::year{
static_cast<int>(year)}, std::chrono::month{month}, std::chrono::day{day}};
420 throwInvalidTimestampValue();
422 return Timestamp{date,
Time{timeOfDay}};
425 Timestamp stringToTimestamp(std::string_view value)
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*$)");
430 const std::string stringValue{value};
433 if (!std::regex_match(stringValue, matches, pattern))
434 throwConversionErrorFromString(std::string{value});
436 const auto makeComponentView = [&](
const std::size_t index)
438 return std::string_view{stringValue.data() +
static_cast<std::size_t
>(matches.position(index)),
439 static_cast<std::size_t
>(matches.length(index))};
442 const auto parseComponent = [&](
const std::size_t index)
445 const auto component = makeComponentView(index);
446 const auto [ptr, ec] =
447 std::from_chars(component.data(), component.data() + component.size(), result, 10);
449 if (ec != std::errc{} || ptr != component.data() + component.size())
450 throwConversionErrorFromString(std::string{value});
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);
462 unsigned fractions = 0;
463 if (matches[7].matched && matches.length(7) > 0)
465 const auto fractionComponent = makeComponentView(7);
466 const auto [ptr, ec] = std::from_chars(
467 fractionComponent.data(), fractionComponent.data() + fractionComponent.size(), fractions, 10);
469 if (ec != std::errc{} || ptr != fractionComponent.data() + fractionComponent.size())
470 throwConversionErrorFromString(std::string{value});
472 for (
auto remaining = 4 -
static_cast<int>(fractionComponent.size()); remaining > 0; --remaining)
476 const Date date{std::chrono::year{year}, std::chrono::month{month}, std::chrono::day{day}};
479 throwInvalidTimestampValue();
481 if (hours >= 24 || minutes >= 60 || seconds >= 60)
482 throwInvalidTimestampValue();
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};
487 if (timeOfDay >= std::chrono::hours{24})
488 throwInvalidTimestampValue();
490 return Timestamp{date,
Time{timeOfDay}};
493 OpaqueTimestamp stringToOpaqueTimestamp(std::string_view value)
495 return timestampToOpaqueTimestamp(stringToTimestamp(value));
498 std::string opaqueTimestampToString(OpaqueTimestamp timestamp)
500 const auto converted = opaqueTimestampToTimestamp(timestamp);
501 const auto subseconds =
static_cast<unsigned>(converted.time.subseconds().count() / 100);
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);
509 return std::format(
"{} {}", dateString, timeString);
512 OpaqueTimestampTz timestampTzToOpaqueTimestampTz(
const TimestampTz& timestampTz)
514 OpaqueTimestampTz opaque;
516 client.getUtil()->encodeTimeStampTz(
517 statusWrapper, &opaque.value, 1u, 1u, 1u, 0u, 0u, 0u, 0u, timestampTz.zone.c_str());
519 const auto utcOpaque = timestampToOpaqueTimestamp(timestampTz.utcTimestamp);
520 opaque.value.utc_timestamp = utcOpaque.value;
525 TimestampTz opaqueTimestampTzToTimestampTz(
526 const OpaqueTimestampTz& opaqueTimestamp, std::string* decodedTimeZoneName =
nullptr)
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)) *
540 std::array<char, 128> timeZoneBuffer;
542 client.getUtil()->decodeTimeStampTz(statusWrapper, &opaqueTimestamp.value, &year, &month, &day, &hours,
543 &minutes, &seconds, &subseconds,
static_cast<unsigned>(timeZoneBuffer.size()), timeZoneBuffer.data());
545 TimestampTz timestampTz;
546 const auto utcLocalTime = BASE_EPOCH + std::chrono::microseconds{ticks};
548 timestampTz.zone = timeZoneBuffer.data();
550 if (decodedTimeZoneName)
551 *decodedTimeZoneName = timestampTz.zone;
556 OpaqueTimestampTz stringToOpaqueTimestampTz(std::string_view value)
558 return timestampTzToOpaqueTimestampTz(stringToTimestampTz(value));
561 std::string opaqueTimestampTzToString(
const OpaqueTimestampTz& timestamp)
570 std::array<char, 128> timeZoneBuffer;
572 client.getUtil()->decodeTimeStampTz(statusWrapper, ×tamp.value, &year, &month, &day, &hours, &minutes,
573 &seconds, &subseconds,
static_cast<unsigned>(timeZoneBuffer.size()), timeZoneBuffer.data());
575 return std::format(
"{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:04} {}", year, month, day, hours, minutes,
576 seconds, subseconds, timeZoneBuffer.data());
579 TimestampTz stringToTimestampTz(std::string_view value)
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*$)");
584 const std::string stringValue{value};
587 if (!std::regex_match(stringValue, matches, pattern))
588 throwConversionErrorFromString(std::string{value});
590 const auto makeComponentView = [&](
const std::size_t index)
592 return std::string_view{stringValue.data() +
static_cast<std::size_t
>(matches.position(index)),
593 static_cast<std::size_t
>(matches.length(index))};
596 const auto parseComponent = [&](
const std::size_t index)
599 const auto component = makeComponentView(index);
600 const auto [ptr, ec] =
601 std::from_chars(component.data(), component.data() + component.size(), result, 10);
603 if (ec != std::errc{} || ptr != component.data() + component.size())
604 throwConversionErrorFromString(std::string{value});
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);
616 unsigned fractions = 0;
617 if (matches[7].matched && matches.length(7) > 0)
619 const auto fractionComponent = makeComponentView(7);
620 const auto [ptr, ec] = std::from_chars(
621 fractionComponent.data(), fractionComponent.data() + fractionComponent.size(), fractions, 10);
623 if (ec != std::errc{} || ptr != fractionComponent.data() + fractionComponent.size())
624 throwConversionErrorFromString(std::string{value});
626 for (
auto remaining = 4 -
static_cast<int>(fractionComponent.size()); remaining > 0; --remaining)
630 const Date date{std::chrono::year{year}, std::chrono::month{month}, std::chrono::day{day}};
633 throwInvalidTimestampValue();
635 if (hours >= 24 || minutes >= 60 || seconds >= 60)
636 throwInvalidTimestampValue();
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};
641 if (timeOfDay >= std::chrono::hours{24})
642 throwInvalidTimestampValue();
644 const Timestamp localTimestamp{date,
Time{timeOfDay}};
646 const auto monthValue =
static_cast<unsigned>(date.month());
647 const auto dayValue =
static_cast<unsigned>(date.day());
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());
655 const OpaqueTimestamp utcOpaque{encoded.value.utc_timestamp};
656 const auto utcTimestamp = opaqueTimestampToTimestamp(utcOpaque);
658 const auto offsetDuration = localTimestamp.toLocalTime() - utcTimestamp.toLocalTime();
659 if (offsetDuration % std::chrono::minutes{1} != std::chrono::microseconds::zero())
660 throwInvalidTimestampValue();
662 std::string resolvedTimeZoneName;
663 opaqueTimestampTzToTimestampTz(encoded, &resolvedTimeZoneName);
665 return TimestampTz{utcTimestamp, resolvedTimeZoneName};
669 [[noreturn]]
void throwConversionErrorFromString(
const std::string& str)
671 const std::intptr_t STATUS_CONVERSION_ERROR_FROM_STRING[] = {
673 reinterpret_cast<std::intptr_t
>(str.c_str()),
677 throw DatabaseException(client, STATUS_CONVERSION_ERROR_FROM_STRING);
680 [[noreturn]]
void throwInvalidDateValue()
682 static constexpr std::intptr_t STATUS_INVALID_DATE_VALUE[] = {
683 isc_invalid_date_val,
687 throw DatabaseException(client, STATUS_INVALID_DATE_VALUE);
690 [[noreturn]]
void throwInvalidTimeValue()
692 static constexpr std::intptr_t STATUS_INVALID_TIME_VALUE[] = {
693 isc_invalid_time_val,
697 throw DatabaseException(client, STATUS_INVALID_TIME_VALUE);
700 [[noreturn]]
void throwInvalidTimestampValue()
702 static constexpr std::intptr_t STATUS_INVALID_TIMESTAMP_VALUE[] = {
703 isc_invalid_timestamp_val,
707 throw DatabaseException(client, STATUS_INVALID_TIMESTAMP_VALUE);
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,
716 StatusWrapper* statusWrapper;
std::chrono::year_month_day Date
Firebird SQL calendar date.
std::chrono::hh_mm_ss< std::chrono::microseconds > Time
Firebird SQL time-of-day with microsecond resolution.
static Timestamp fromLocalTime(std::chrono::local_time< std::chrono::microseconds > value) noexcept
Builds a timestamp from a local-time value.