25#ifndef FBCPP_CALENDAR_CONVERTER_H
26#define FBCPP_CALENDAR_CONVERTER_H
48 class CalendarConverter final
51 explicit CalendarConverter(Client& client)
57 OpaqueDate dateToOpaqueDate(
const Date& date)
60 throwInvalidDateValue();
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());
67 throwInvalidDateValue();
69 return OpaqueDate{client->getUtil()->encodeDate(
static_cast<unsigned>(yearValue), monthValue, dayValue)};
72 Date opaqueDateToDate(OpaqueDate date)
78 client->getUtil()->decodeDate(date.value, &year, &month, &day);
80 return Date{std::chrono::year{
static_cast<int>(year)}, std::chrono::month{month}, std::chrono::day{day}};
83 Date stringToDate(std::string_view value)
85 static const std::regex pattern(R
"(^\s*([0-9]{4})\s*-\s*([0-9]{2})\s*-\s*([0-9]{2})\s*$)");
87 const std::string stringValue{value};
90 if (!std::regex_match(stringValue, matches, pattern))
91 throwConversionErrorFromString(std::string{value});
93 const auto makeComponentView = [&](
const std::size_t index)
95 return std::string_view{stringValue.data() +
static_cast<std::size_t
>(matches.position(index)),
96 static_cast<std::size_t
>(matches.length(index))};
99 const auto parseComponent = [&](
const std::size_t index)
102 const auto component = makeComponentView(index);
103 const auto [ptr, ec] =
104 std::from_chars(component.data(), component.data() + component.size(), result, 10);
106 if (ec != std::errc{} || ptr != component.data() + component.size())
107 throwConversionErrorFromString(std::string{value});
112 const int year = parseComponent(1);
113 const unsigned month =
static_cast<unsigned>(parseComponent(2));
114 const unsigned day =
static_cast<unsigned>(parseComponent(3));
116 const Date date{std::chrono::year{year}, std::chrono::month{month}, std::chrono::day{day}};
119 throwInvalidDateValue();
124 OpaqueDate stringToOpaqueDate(std::string_view value)
126 return dateToOpaqueDate(stringToDate(value));
129 std::string opaqueDateToString(OpaqueDate date)
131 return std::format(
"{:%Y-%m-%d}", std::chrono::local_days{opaqueDateToDate(date)});
134 OpaqueTime timeToOpaqueTime(
const Time& time)
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);
141 OpaqueTime opaqueTime;
142 opaqueTime.value = client->getUtil()->encodeTime(hours, minutes, seconds, subseconds);
147 Time opaqueTimeToTime(OpaqueTime time)
154 const auto util = client->getUtil();
155 util->decodeTime(time.value, &hours, &minutes, &seconds, &subseconds);
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};
160 return Time{timeOfDay};
163 Time stringToTime(std::string_view value)
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*$)");
168 const std::string stringValue{value};
171 if (!std::regex_match(stringValue, matches, pattern))
172 throwConversionErrorFromString(std::string{value});
174 const auto makeComponentView = [&](
const std::size_t index)
176 return std::string_view{stringValue.data() +
static_cast<std::size_t
>(matches.position(index)),
177 static_cast<std::size_t
>(matches.length(index))};
180 const auto parseComponent = [&](
const std::size_t index)
183 const auto component = makeComponentView(index);
184 const auto [ptr, ec] =
185 std::from_chars(component.data(), component.data() + component.size(), result, 10);
187 if (ec != std::errc{} || ptr != component.data() + component.size())
188 throwConversionErrorFromString(std::string{value});
193 const auto hours = parseComponent(1);
194 const auto minutes = parseComponent(2);
195 const auto seconds = parseComponent(3);
197 unsigned fractions = 0;
198 if (matches[4].matched && matches.length(4) > 0)
200 const auto fractionComponent = makeComponentView(4);
201 const auto [ptr, ec] = std::from_chars(
202 fractionComponent.data(), fractionComponent.data() + fractionComponent.size(), fractions, 10);
204 if (ec != std::errc{} || ptr != fractionComponent.data() + fractionComponent.size())
205 throwConversionErrorFromString(std::string{value});
207 for (
auto remaining = 4 -
static_cast<int>(fractionComponent.size()); remaining > 0; --remaining)
211 if (hours >= 24 || minutes >= 60 || seconds >= 60)
212 throwInvalidTimeValue();
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};
217 if (timeOfDay >= std::chrono::hours{24})
218 throwInvalidTimeValue();
220 return Time{std::chrono::duration_cast<std::chrono::microseconds>(timeOfDay)};
223 OpaqueTime stringToOpaqueTime(std::string_view value)
225 return timeToOpaqueTime(stringToTime(value));
228 std::string opaqueTimeToString(OpaqueTime time)
230 const auto converted = opaqueTimeToTime(time);
231 const auto subseconds =
static_cast<unsigned>(converted.subseconds().count() / 100);
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()),
238 OpaqueTimeTz timeTzToOpaqueTimeTz(StatusWrapper* statusWrapper,
const TimeTz& timeTz)
240 const auto duration = timeTz.utcTime.to_duration();
242 if (duration.count() < 0)
243 throwInvalidTimeValue();
245 const auto dayDuration = std::chrono::hours{24};
246 if (duration >= dayDuration)
247 throwInvalidTimeValue();
249 if (duration.count() % 100 != 0)
250 throwInvalidTimeValue();
252 OpaqueTimeTz opaque{};
254 client->getUtil()->encodeTimeTz(statusWrapper, &opaque.value, 0u, 0u, 0u, 0u, timeTz.zone.c_str());
256 opaque.value.utc_time =
static_cast<ISC_TIME
>(duration.count() / 100);
261 TimeTz opaqueTimeTzToTimeTz(
262 StatusWrapper* statusWrapper,
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(StatusWrapper* statusWrapper, std::string_view value)
287 return timeTzToOpaqueTimeTz(statusWrapper, stringToTimeTz(statusWrapper, value));
290 std::string opaqueTimeTzToString(StatusWrapper* statusWrapper,
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(StatusWrapper* statusWrapper, 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(statusWrapper, 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(StatusWrapper* statusWrapper,
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(StatusWrapper* statusWrapper,
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(StatusWrapper* statusWrapper, std::string_view value)
558 return timestampTzToOpaqueTimestampTz(statusWrapper, stringToTimestampTz(statusWrapper, value));
561 std::string opaqueTimestampTzToString(StatusWrapper* statusWrapper,
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(StatusWrapper* statusWrapper, 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(statusWrapper, 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,
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.