From a429459b76d1543c7604139fa553e6c754e10781 Mon Sep 17 00:00:00 2001 From: TreeHunter <60896014+TreeHunter9@users.noreply.github.com> Date: Mon, 4 Mar 2024 14:43:14 +0300 Subject: [PATCH] Change some aspects of the string-to-date conversion with format to make it more similar to the SQL standard #2388 (#7881) * Use current TimeStamp for data in stringToDate conversion if it's not specify Also fix RM pattern and change (A/P)M to (A/P).M. * Add more tests * Add TimeStamp validation Also move duplicated code to functions. * Add more unit tests for "YY" and "YYY" patterns * Use Callback for getting current date It's better because we can mock Callback for unit tests. * Fix exception and README description * Add ability to print blr_cast_format * Put a comment about new BLR in the right place * Add information about behavior of string to datetime conversion * Rework old patterns and add new ones Add A.M, P.M., RR and RRRR patterns. Rework YY, YYY, HH and HH12 patterns due to new patterns. Add restriction from SQL standard to format. Fix incorrect error message for mismatched pattern. Fix bug with 0 hours in HH12. * Add more unit tests * Update doc for cast format * Allow specification of log_level for BOOST_TESTS in make * Change enum class to enum in namespace * Switch from plain enum to constexpr values --------- Co-authored-by: Artyom Ivanov --- builds/posix/Makefile.in | 10 +- doc/README.cast.format.md | 24 +- src/common/common.h | 1 + src/common/cvt.cpp | 665 +++++++++++++++++++--------- src/common/cvt.h | 4 +- src/common/tests/CvtTest.cpp | 521 +++++++++++++++++----- src/common/tests/CvtTestUtils.h | 41 +- src/dsql/ExprNodes.cpp | 20 +- src/include/firebird/impl/msg/jrd.h | 5 +- src/include/gen/Firebird.pas | 3 + src/jrd/blp.h | 4 +- src/yvalve/gds.cpp | 3 +- 12 files changed, 946 insertions(+), 355 deletions(-) diff --git a/builds/posix/Makefile.in b/builds/posix/Makefile.in index 61a677c24f..6dee68ce86 100644 --- a/builds/posix/Makefile.in +++ b/builds/posix/Makefile.in @@ -790,18 +790,20 @@ install install-embedded silent_install package packages dist: .PHONY: tests tests_process run_tests run_tests_process +log_level ?= all + tests: $(MAKE) TARGET?=$(DefaultTarget) tests_process tests_process: $(COMMON_TEST) $(ENGINE_TEST) $(ISQL_TEST) run_tests: - $(MAKE) TARGET?=$(DefaultTarget) run_tests_process + $(MAKE) TARGET?=$(DefaultTarget) LOG_LEVEL?=$(log_level) run_tests_process run_tests_process: tests_process - $(COMMON_TEST) --log_level=all - $(ENGINE_TEST) --log_level=all - $(ISQL_TEST) --log_level=all + $(COMMON_TEST) --log_level=$(LOG_LEVEL) + $(ENGINE_TEST) --log_level=$(LOG_LEVEL) + $(ISQL_TEST) --log_level=$(LOG_LEVEL) #___________________________________________________________________________ diff --git a/doc/README.cast.format.md b/doc/README.cast.format.md index 9efcfee4d3..7305d9da31 100644 --- a/doc/README.cast.format.md +++ b/doc/README.cast.format.md @@ -1,6 +1,6 @@ ## 1. DATETIME TO STRING -The following flags are currently implemented for datetime to string conversion: +The following patterns are currently implemented for datetime to string conversion: | Format Pattern | Description | | -------------- | ----------- | | YEAR | Year (1 - 9999) | @@ -21,12 +21,13 @@ The following flags are currently implemented for datetime to string conversion: | DDD | Day of the Year (001 - 366) | | DY | Short name of the Day (Mon) | | J | Julian Day (number of days since January 1, 4712 BC) | -| HH / HH12 | Hour of the Day (01 - 12) with period (AM, PM) | +| HH / HH12 | Hour of the Day (01 - 12) without Period (for Period use A.M or P.M.) | | HH24 | Hour of the Day (00 - 23) | | MI | Minutes (00 - 59) | | SS | Seconds (00 - 59) | | SSSSS | Seconds after midnight (0 - 86399) | | FF1 - FF9 | Fractional seconds with the specified accuracy | +| A.M. / P.M. | Period for 12 hours time (it doesn't matter which one is used, period will be inserted based on time) | | TZH | Time zone in Hours (-14 - 14) | | TZM | Time zone in Minutes (00 - 59) | | TZR | Time zone Name | @@ -61,7 +62,7 @@ SELECT CAST(CURRENT_TIMESTAMP AS VARCHAR(45) FORMAT 'DD.MM.YEAR HH24:MI:SS "is" ## 2. STRING TO DATETIME -The following flags are currently implemented for string to datetime conversion: +The following patterns are currently implemented for string to datetime conversion: | Format Pattern | Description | | ------------- | ------------- | | YEAR | Year | @@ -69,24 +70,39 @@ The following flags are currently implemented for string to datetime conversion: | YYY | Last 3 digits of Year | | YY | Last 2 digits of Year | | Y | Last 1 digits of Year | +| RR / RRRR | Round Year (further information below) | | MM | Month (1 - 12) | | MON | Short Month name (Apr) | | MONTH | Full Month name (APRIL) | | RM | Roman representation of the Month (I - XII) | | DD | Day of the Month (1 - 31) | | J | Julian Day (number of days since January 1, 4712 BC) | -| HH / HH12 | Hour of the Day (1 - 12) with period (AM, PM) | +| HH / HH12 | Hour of the Day (1 - 12) without Period (to specify Period use A.M or P.M.) | | HH24 | Hour of the Day (0 - 23) | | MI | Minutes (0 - 59) | | SS | Seconds (0 - 59) | | SSSSS | Seconds after midnight (0 - 86399) | | FF1 - FF4 | Fractional seconds with the specified accuracy | +| A.M. / P.M. | Period for 12 hours time (it doesn't matter which one is used, period will be taken from input string) | | TZH | Time zone in Hours (-14 - 14) | | TZM | Time zone in Minutes (0 - 59) | | TZR | Time zone Name | Dividers are the same as for datetime to string conversion and can also be omitted. +Year, month and day will be taken from current date if these components are not used in pattern (this applies only to data types that contain a date component). + +Behavior of `RR`: +- If the specified two-digit year is 00 to 49, then + - If the last two digits of the current year are 00 to 49, then the returned year has the same first two digits as the current year. + - If the last two digits of the current year are 50 to 99, then the first 2 digits of the returned year are 1 greater than the first 2 digits of the current year. +- If the specified two-digit year is 50 to 99, then + - If the last two digits of the current year are 00 to 49, then the first 2 digits of the returned year are 1 less than the first 2 digits of the current year. + - If the last two digits of the current year are 50 to 99, then the returned year has the same first two digits as the current year. + +Behavior of `RRRR`: Accepts either 4-digit or 2-digit input. If 2-digit, provides the same return as `RR`. If you do not want this functionality, then enter the 4-digit year. + + Example: ``` SELECT CAST('2000.12.08 12:35:30.5000' AS TIMESTAMP FORMAT 'YEAR.MM.DD HH24:MI:SS.FF4') FROM RDB$DATABASE; diff --git a/src/common/common.h b/src/common/common.h index 2162205a3b..bddee3fe5d 100644 --- a/src/common/common.h +++ b/src/common/common.h @@ -971,6 +971,7 @@ const int HIGH_WORD = 0; #endif #endif + inline const TEXT FB_SHORT_MONTHS[][4] = { "Jan", "Feb", "Mar", diff --git a/src/common/cvt.cpp b/src/common/cvt.cpp index 2afa5cc1e7..8be4b19b68 100644 --- a/src/common/cvt.cpp +++ b/src/common/cvt.cpp @@ -250,28 +250,49 @@ namespace AutoPtr m_root; MemoryPool& m_pool; }; + + namespace Format + { + typedef unsigned Patterns; + + constexpr Patterns NONE = 0; + constexpr Patterns HH24 = 1 << 0; + constexpr Patterns HH12 = 1 << 1; + constexpr Patterns AM = 1 << 2; + constexpr Patterns PM = 1 << 3; + constexpr Patterns AM_OR_PM_FIRST = 1 << 4; + constexpr Patterns SSSSS = 1 << 5; + constexpr Patterns MI = 1 << 6; + constexpr Patterns SS = 1 << 7; + constexpr Patterns TZH = 1 << 8; + constexpr Patterns TZM = 1 << 9; + } + + enum class ExpectedDateType + { + TIME, + DATE, + TIMEZONE + }; + + constexpr char AM_PERIOD[] = "A.M."; + constexpr char PM_PERIOD[] = "P.M."; + + const char* const TO_DATETIME_PATTERNS[] = { + "YEAR", "YYYY", "YYY", "YY", "Y", "Q", "MM", "MON", "MONTH", "RM", "WW", "W", + "D", "DAY", "DD", "DDD", "DY", "J", "HH", "HH12", "HH24", "MI", "SS", "SSSSS", + "FF1", "FF2", "FF3", "FF4", "FF5", "FF6", "FF7", "FF8", "FF9", "TZH", "TZM", "TZR", + AM_PERIOD, PM_PERIOD + }; + + const char* const TO_STRING_PATTERNS[] = { + "YEAR", "YYYY", "YYY", "YY", "Y", "RRRR", "RR", "MM", "MON", "MONTH", "RM", "DD", "J", "HH", "HH12", + "HH24", "MI", "SS", "SSSSS", "FF1", "FF2", "FF3", "FF4", "TZH", "TZM", "TZR", AM_PERIOD, PM_PERIOD + }; + + InitInstance timeZoneTrie; } -enum class ExpectedDateType -{ - TIME, - DATE, - TIMEZONE -}; - -static const char* const TO_DATETIME_PATTERNS[] = { - "YEAR", "YYYY", "YYY", "YY", "Y", "Q", "MM", "MON", "MONTH", "RM", "WW", "W", - "D", "DAY", "DD", "DDD", "DY", "J", "HH", "HH12", "HH24", "MI", "SS", "SSSSS", - "FF1", "FF2", "FF3", "FF4", "FF5", "FF6", "FF7", "FF8", "FF9", "TZH", "TZM", "TZR" -}; - -static const char* const TO_STRING_PATTERNS[] = { - "YEAR", "YYYY", "YYY", "YY", "Y", "MM", "MON", "MONTH", "RM", "DD", "J", "HH", "HH12", - "HH24", "MI", "SS", "SSSSS", "FF1", "FF2", "FF3", "FF4", "TZH", "TZM", "TZR" -}; - -static InitInstance timeZoneTrie; - //#ifndef WORDS_BIGENDIAN //static const SQUAD quad_min_int = { 0, SLONG_MIN }; @@ -373,6 +394,51 @@ static constexpr int sign(T value) return (value >= T(0)) ? 1 : -1; } +static void validateTimeStamp(const ISC_TIMESTAMP timestamp, const EXPECT_DATETIME expectedType, const dsc* desc, + Callbacks* cb) +{ + if (!NoThrowTimeStamp::isValidTimeStamp(timestamp)) + { + switch (expectedType) + { + case expect_sql_date: + cb->err(Arg::Gds(isc_date_range_exceeded)); + break; + case expect_sql_time: + case expect_sql_time_tz: + cb->err(Arg::Gds(isc_time_range_exceeded)); + break; + case expect_timestamp: + case expect_timestamp_tz: + cb->err(Arg::Gds(isc_datetime_range_exceeded)); + break; + default: // this should never happen! + CVT_conversion_error(desc, cb->err); + break; + } + } +} + +static void timeStampToUtc(ISC_TIMESTAMP_TZ& timestampTZ, USHORT sessionTimeZone, const EXPECT_DATETIME expectedType, + Callbacks* cb) +{ + if (expectedType == expect_sql_time_tz || expectedType == expect_timestamp_tz || timestampTZ.time_zone != sessionTimeZone) + TimeZoneUtil::localTimeStampToUtc(timestampTZ); + + if (timestampTZ.time_zone != sessionTimeZone) + { + if (expectedType == expect_sql_time) + { + ISC_TIME_TZ timeTz; + timeTz.utc_time = timestampTZ.utc_timestamp.timestamp_time; + timeTz.time_zone = timestampTZ.time_zone; + timestampTZ.utc_timestamp.timestamp_time = TimeZoneUtil::timeTzToTime(timeTz, cb); + } + else if (expectedType == expect_timestamp) + *(ISC_TIMESTAMP*) ×tampTZ = TimeZoneUtil::timeStampTzToTimeStamp(timestampTZ, sessionTimeZone); + } +} + static void float_to_text(const dsc* from, dsc* to, Callbacks* cb) { @@ -1183,27 +1249,7 @@ void CVT_string_to_datetime(const dsc* desc, // This catches things like 29-Feb-1995 (not a leap year) Firebird::TimeStamp ts(times); - - if (!ts.isValid()) - { - switch (expect_type) - { - case expect_sql_date: - cb->err(Arg::Gds(isc_date_range_exceeded)); - break; - case expect_sql_time: - case expect_sql_time_tz: - cb->err(Arg::Gds(isc_time_range_exceeded)); - break; - case expect_timestamp: - case expect_timestamp_tz: - cb->err(Arg::Gds(isc_datetime_range_exceeded)); - break; - default: // this should never happen! - CVT_conversion_error(desc, cb->err); - break; - } - } + validateTimeStamp(ts.value(), expect_type, desc, cb); if (expect_type != expect_sql_time && expect_type != expect_sql_time_tz) { @@ -1230,21 +1276,7 @@ void CVT_string_to_datetime(const dsc* desc, date->utc_timestamp.timestamp_time += components[6]; date->time_zone = zone; - if (expect_type == expect_sql_time_tz || expect_type == expect_timestamp_tz || zone != sessionTimeZone) - TimeZoneUtil::localTimeStampToUtc(*date); - - if (zone != sessionTimeZone) - { - if (expect_type == expect_sql_time) - { - ISC_TIME_TZ timeTz; - timeTz.utc_time = date->utc_timestamp.timestamp_time; - timeTz.time_zone = zone; - date->utc_timestamp.timestamp_time = TimeZoneUtil::timeTzToTime(timeTz, cb); - } - else if (expect_type == expect_timestamp) - *(ISC_TIMESTAMP*) date = TimeZoneUtil::timeStampTzToTimeStamp(*date, sessionTimeZone); - } + timeStampToUtc(*date, sessionTimeZone, expect_type, cb); } @@ -1293,6 +1325,26 @@ static string int_to_roman(int num) } +static std::pair calculate_12hours_from_24hours(int hours) +{ + const char* period = nullptr; + if (hours >= 12) + { + period = PM_PERIOD; + if (hours > 12) + hours -= 12; + } + else + { + period = AM_PERIOD; + if (hours == 0) + hours = 12; + } + + return { hours, period }; +} + + static SSHORT extract_timezone_offset(const dsc* desc) { SSHORT timezoneOffset = 0; @@ -1374,27 +1426,45 @@ static string datetime_to_format_string_pattern_matcher(const dsc* desc, std::st { case 'Y': { - date_type_check(ExpectedDateType::DATE, desc, pattern, cb); - int year = times.tm_year + 1900; if (pattern == "Y") + { + date_type_check(ExpectedDateType::DATE, desc, pattern, cb); + patternResult.printf("%d", year % 10); + } else if (pattern == "YY") + { + date_type_check(ExpectedDateType::DATE, desc, pattern, cb); + patternResult.printf("%02d", year % 100); + } else if (pattern == "YYY") + { + date_type_check(ExpectedDateType::DATE, desc, pattern, cb); + patternResult.printf("%03d", year % 1000); + } else if (pattern == "YYYY") + { + date_type_check(ExpectedDateType::DATE, desc, pattern, cb); + patternResult.printf("%04d", year % 10000); + } else if (pattern == "YEAR") + { + date_type_check(ExpectedDateType::DATE, desc, pattern, cb); + patternResult.printf("%d", year); + } break; } case 'Q': - date_type_check(ExpectedDateType::DATE, desc, pattern, cb); - if (pattern == "Q") { + date_type_check(ExpectedDateType::DATE, desc, pattern, cb); + int quarter = times.tm_mon / 3 + 1; patternResult.printf("%d", quarter); @@ -1410,64 +1480,92 @@ static string datetime_to_format_string_pattern_matcher(const dsc* desc, std::st break; } - date_type_check(ExpectedDateType::DATE, desc, pattern, cb); - if (pattern == "MM") + { + date_type_check(ExpectedDateType::DATE, desc, pattern, cb); + patternResult.printf("%02d", (times.tm_mon + 1)); + } else if (pattern == "MON") + { + date_type_check(ExpectedDateType::DATE, desc, pattern, cb); + patternResult.printf("%s", FB_SHORT_MONTHS[times.tm_mon]); + } else if (pattern == "MONTH") + { + date_type_check(ExpectedDateType::DATE, desc, pattern, cb); + patternResult.printf("%s", FB_LONG_MONTHS_UPPER[times.tm_mon]); + } break; case 'R': - date_type_check(ExpectedDateType::DATE, desc, pattern, cb); - if (pattern == "RM") { + date_type_check(ExpectedDateType::DATE, desc, pattern, cb); + string roman = int_to_roman(times.tm_mon + 1); patternResult.printf("%s", roman.c_str()); } break; case 'W': - date_type_check(ExpectedDateType::DATE, desc, pattern, cb); - if (pattern == "W") { + date_type_check(ExpectedDateType::DATE, desc, pattern, cb); + int week = (times.tm_mday - 1) / 7; patternResult.printf("%d", week + 1); } else if (pattern == "WW") { + date_type_check(ExpectedDateType::DATE, desc, pattern, cb); + int week = NoThrowTimeStamp::convertGregorianDateToWeekDate(times); patternResult.printf("%02d", week); } break; case 'D': - date_type_check(ExpectedDateType::DATE, desc, pattern, cb); - if (pattern == "D") + { + date_type_check(ExpectedDateType::DATE, desc, pattern, cb); + patternResult.printf("%d", times.tm_wday + 1); + } else if (pattern == "DAY") + { + date_type_check(ExpectedDateType::DATE, desc, pattern, cb); + patternResult.printf("%s", FB_LONG_DAYS_UPPER[times.tm_wday]); + } else if (pattern == "DD") + { + date_type_check(ExpectedDateType::DATE, desc, pattern, cb); + patternResult.printf("%02d", times.tm_mday); + } else if (pattern == "DDD") { + date_type_check(ExpectedDateType::DATE, desc, pattern, cb); + int daysInYear = times.tm_yday + 1; patternResult.printf("%03d", daysInYear); } else if (pattern == "DY") + { + date_type_check(ExpectedDateType::DATE, desc, pattern, cb); + patternResult.printf("%s", FB_SHORT_DAYS[times.tm_wday]); + } break; case 'J': - date_type_check(ExpectedDateType::DATE, desc, pattern, cb); - if (pattern == "J") { + date_type_check(ExpectedDateType::DATE, desc, pattern, cb); + int JulianDay = NoThrowTimeStamp::convertGregorianDateToJulianDate(times.tm_year + 1900, times.tm_mon + 1, times.tm_mday); @@ -1476,50 +1574,44 @@ static string datetime_to_format_string_pattern_matcher(const dsc* desc, std::st break; case 'H': - date_type_check(ExpectedDateType::TIME, desc, pattern, cb); - if (pattern == "HH"|| pattern == "HH12") { - const char* period; - int hours = times.tm_hour; + date_type_check(ExpectedDateType::TIME, desc, pattern, cb); - if (hours >= 12) - { - period = "PM"; - if (hours > 12) - hours -= 12; - } - else - { - period = "AM"; - if (hours == 0) - hours = 12; - } + const auto [hours, period] = calculate_12hours_from_24hours(times.tm_hour); - patternResult.printf("%02d %s", hours, period); + patternResult.printf("%02d", hours); } else if (pattern == "HH24") + { + date_type_check(ExpectedDateType::TIME, desc, pattern, cb); + patternResult.printf("%02d", times.tm_hour); + } break; case 'S': - date_type_check(ExpectedDateType::TIME, desc, pattern, cb); - if (pattern == "SS") + { + date_type_check(ExpectedDateType::TIME, desc, pattern, cb); + patternResult.printf("%02d", times.tm_sec); + } else if (pattern == "SSSSS") { + date_type_check(ExpectedDateType::TIME, desc, pattern, cb); + int secondsInDay = times.tm_hour * 60 * 60 + times.tm_min * 60 + times.tm_sec; patternResult.printf("%d", secondsInDay); } break; case 'F': - date_type_check(ExpectedDateType::TIME, desc, pattern, cb); - if (!strncmp(pattern.data(), "FF", pattern.length() - 1)) { + date_type_check(ExpectedDateType::TIME, desc, pattern, cb); + int number = pattern.back() - '0'; if (number < 1 || number > 9) { @@ -1539,11 +1631,33 @@ static string datetime_to_format_string_pattern_matcher(const dsc* desc, std::st } break; - case 'T': - date_type_check(ExpectedDateType::TIMEZONE, desc, pattern, cb); + case 'A': + if (pattern == AM_PERIOD) + { + date_type_check(ExpectedDateType::TIME, desc, pattern, cb); + const auto [hours, period] = calculate_12hours_from_24hours(times.tm_hour); + + patternResult.printf("%s", period); + } + break; + + case 'P': + if (pattern == PM_PERIOD) + { + date_type_check(ExpectedDateType::TIME, desc, pattern, cb); + + const auto [hours, period] = calculate_12hours_from_24hours(times.tm_hour); + + patternResult.printf("%s", period); + } + break; + + case 'T': if (pattern == "TZH") { + date_type_check(ExpectedDateType::TIMEZONE, desc, pattern, cb); + SSHORT timezoneOffset = extract_timezone_offset(desc); int timezoneSign = sign(timezoneOffset); SSHORT offsetInHours = abs(timezoneOffset / 60); @@ -1562,6 +1676,8 @@ static string datetime_to_format_string_pattern_matcher(const dsc* desc, std::st } else if (pattern == "TZM") { + date_type_check(ExpectedDateType::TIMEZONE, desc, pattern, cb); + SSHORT timezoneOffset = extract_timezone_offset(desc); int timezoneSign = sign(timezoneOffset); SSHORT offsetInMinutes = abs(timezoneOffset % 60); @@ -1579,7 +1695,11 @@ static string datetime_to_format_string_pattern_matcher(const dsc* desc, std::st patternResult.printf(printfFormat.c_str(), offsetInMinutes); } else if (pattern == "TZR") + { + date_type_check(ExpectedDateType::TIMEZONE, desc, pattern, cb); + patternResult = extract_timezone_name(desc); + } break; default: @@ -1635,6 +1755,7 @@ string CVT_datetime_to_format_string(const dsc* desc, const string& format, Call } string formatUpper(format); + // Convert format to upper case except for text in double quotes for (int i = 0; i < formatUpper.length(); i++) { const char symbol = formatUpper[i]; @@ -1665,28 +1786,24 @@ string CVT_datetime_to_format_string(const dsc* desc, const string& format, Call break; } } + int formatLength = formatUpper.length(); string result; int formatOffset = 0; std::string_view pattern; std::string_view previousPattern; - for (int i = 0; i < formatUpper.length(); i++) + for (int i = 0; i < formatLength; i++) { const char symbol = formatUpper[i]; + // We meet separator, if it's double quotes - start copying this text into result string, if not, just insert separator if (is_separator(symbol)) { - if (formatOffset != i) - { - result += datetime_to_format_string_pattern_matcher(desc, pattern, previousPattern, times, - fractions, cb); - previousPattern = pattern; - } if (symbol == '\"') { i++; - int rawStringLength = formatUpper.length() - i; + int rawStringLength = formatLength - i; string rawString(rawStringLength, '\0'); for (int j = 0; j < rawStringLength; j++, i++) { @@ -1707,31 +1824,40 @@ string CVT_datetime_to_format_string(const dsc* desc, const string& format, Call continue; } - pattern = std::string_view(formatUpper.c_str() + formatOffset, i - formatOffset + 1); - bool isFound = false; - for (int j = 0; j < FB_NELEM(TO_DATETIME_PATTERNS); j++) + // Start reading format and comparing it to patterns, until we stop finding similarities + for (; i < formatLength; i++) { - if (!strncmp(TO_DATETIME_PATTERNS[j], pattern.data(), pattern.length())) + pattern = std::string_view(formatUpper.c_str() + formatOffset, i - formatOffset + 1); + bool isFound = false; + for (int j = 0; j < FB_NELEM(TO_DATETIME_PATTERNS); j++) { - isFound = true; - if (i == formatUpper.length() - 1) + if (!strncmp(TO_DATETIME_PATTERNS[j], pattern.data(), pattern.length())) { - result += datetime_to_format_string_pattern_matcher(desc, pattern, previousPattern, times, - fractions, cb); + isFound = true; + if (i == formatLength - 1) + { + result += datetime_to_format_string_pattern_matcher(desc, pattern, previousPattern, times, + fractions, cb); + } + break; } - break; } + if (!isFound) + break; } - if (isFound) - continue; + + if (i == formatLength) + break; if (pattern.length() <= 1) invalid_pattern_exception(pattern, cb); + // Our current pattern contains real pattern + one extra symbol, so we need to drop it pattern = pattern.substr(0, pattern.length() - 1); result += datetime_to_format_string_pattern_matcher(desc, pattern, previousPattern, times, fractions, cb); previousPattern = pattern; + formatOffset = i; i--; } @@ -1758,7 +1884,7 @@ static int roman_to_int(const char* str, int length, int& offset) case 'C': value = 100; break; case 'D': value = 500; break; case 'M': value = 1000; break; - default: return 0; + default: return result; } result += value; @@ -1799,17 +1925,17 @@ static int parse_string_to_get_int(const char* str, int length, int& offset, int } -static std::string_view parse_string_to_get_first_word(const char* str, int length, int& offset, int parseLength) +static std::string_view parse_string_to_get_substring(const char* str, int length, int& offset, int parseLength = 0, + bool onlyCharacters = true) { int wordLen = 0; int startPoint = offset; - const int parseLengthWithOffset = offset + parseLength; + const int parseLengthWithOffset = parseLength > 0 ? offset + parseLength : std::numeric_limits::max(); for (; offset < parseLengthWithOffset && offset < length; offset++) { - if (!LETTER(str[offset])) + if (onlyCharacters && !LETTER(str[offset])) break; - ++wordLen; } @@ -1817,7 +1943,52 @@ static std::string_view parse_string_to_get_first_word(const char* str, int leng } -static void string_to_format_datetime_pattern_matcher(std::string_view pattern, std::string_view previousPattern, +static Format::Patterns apply_period(std::string_view period, struct tm& outTimes, Firebird::Callbacks* cb) +{ + if (period == AM_PERIOD) + { + if (outTimes.tm_hour == 12) + outTimes.tm_hour = 0; + + return Format::AM; + } + else if (period == PM_PERIOD) + { + int hours = outTimes.tm_hour; + outTimes.tm_hour = hours == 12 ? hours : 12 + hours; + + return Format::PM; + } + + cb->err(Arg::Gds(isc_incorrect_hours_period) << string(period.data(), period.length())); +} + + +static int round_year_pattern_implementation(int parsedRRValue, int currentYear) +{ + int firstTwoDigits = currentYear / 100; + int lastTwoDigits = currentYear % 100; + + int result = 0; + + if (parsedRRValue < 50) + { + result = lastTwoDigits < 50 + ? firstTwoDigits * 100 + parsedRRValue + : (firstTwoDigits + 1) * 100 + parsedRRValue; + } + else + { + result = lastTwoDigits < 50 + ? (firstTwoDigits - 1) * 100 + parsedRRValue + : firstTwoDigits * 100 + parsedRRValue; + } + + return result; +} + + +static void string_to_format_datetime_pattern_matcher(std::string_view pattern, Format::Patterns& formatFlags, std::string_view previousPattern, const char* str, int strLength, int& strOffset, struct tm& outTimes, int& outFractions, SSHORT& outTimezoneInMinutes, USHORT& outTimezoneId, Firebird::Callbacks* cb) { @@ -1826,43 +1997,35 @@ static void string_to_format_datetime_pattern_matcher(std::string_view pattern, case 'Y': if (pattern == "Y") { + // Set last digit to zero + int currentYear = (outTimes.tm_year + 1900) / 10 * 10; int year = parse_string_to_get_int(str, strLength, strOffset, 1); - outTimes.tm_year = 2000 + year - 1900; + + outTimes.tm_year = currentYear + year - 1900; return; } else if (pattern == "YY") { - tm currentTm; - TimeStamp::getCurrentTimeStamp().decode(¤tTm); // Set 2 last digits to zero - int currentAge = (currentTm.tm_year + 1900) / 100 * 100; + int currentAge = (outTimes.tm_year + 1900) / 100 * 100; + int parsedYear = parse_string_to_get_int(str, strLength, strOffset, 2); - outTimes.tm_year = parse_string_to_get_int(str, strLength, strOffset, 2); - outTimes.tm_year += outTimes.tm_year < (currentTm.tm_year + 1900 - 50) % 100 - ? currentAge - : currentAge - 100; - - outTimes.tm_year -= 1900; + outTimes.tm_year = currentAge + parsedYear - 1900; return; } else if (pattern == "YYY") { - tm currentTm; - TimeStamp::getCurrentTimeStamp().decode(¤tTm); // Set 3 last digits to zero - int currentThousand = (currentTm.tm_year + 1900) / 1000 * 1000; + int currentThousand = (outTimes.tm_year + 1900) / 1000 * 1000; + int parsedYear = parse_string_to_get_int(str, strLength, strOffset, 3); - outTimes.tm_year = parse_string_to_get_int(str, strLength, strOffset, 3); - outTimes.tm_year += outTimes.tm_year < (currentTm.tm_year + 1900 - 500) % 1000 - ? currentThousand - : currentThousand - 1000; - - outTimes.tm_year -= 1900; + outTimes.tm_year = currentThousand + parsedYear - 1900; return; } else if (pattern == "YYYY") { int year = parse_string_to_get_int(str, strLength, strOffset, 4); + outTimes.tm_year = year - 1900; return; } @@ -1882,6 +2045,8 @@ static void string_to_format_datetime_pattern_matcher(std::string_view pattern, case 'M': if (pattern == "MI") { + formatFlags |= Format::MI; + int minutes = parse_string_to_get_int(str, strLength, strOffset, 2); if (minutes > 59) { @@ -1906,8 +2071,7 @@ static void string_to_format_datetime_pattern_matcher(std::string_view pattern, } else if (pattern == "MON") { - std::string_view monthShortName = parse_string_to_get_first_word(str, strLength, strOffset, 3); - int month = -1; + std::string_view monthShortName = parse_string_to_get_substring(str, strLength, strOffset, 3); for (int i = 0; i < FB_NELEM(FB_SHORT_MONTHS) - 1; i++) { if (std::equal(monthShortName.begin(), monthShortName.end(), @@ -1923,7 +2087,7 @@ static void string_to_format_datetime_pattern_matcher(std::string_view pattern, } else if (pattern == "MONTH") { - std::string_view monthFullName = parse_string_to_get_first_word(str, strLength, strOffset, strLength - strOffset); + std::string_view monthFullName = parse_string_to_get_substring(str, strLength, strOffset); for (int i = 0; i < FB_NELEM(FB_LONG_MONTHS_UPPER) - 1; i++) { if (std::equal(monthFullName.begin(), monthFullName.end(), @@ -1940,7 +2104,28 @@ static void string_to_format_datetime_pattern_matcher(std::string_view pattern, break; case 'R': - if (pattern == "RM") + if (pattern == "RR") + { + // tm_year already contains current date + int parsedYear = parse_string_to_get_int(str, strLength, strOffset, 2); + outTimes.tm_year = round_year_pattern_implementation(parsedYear, outTimes.tm_year + 1900) - 1900; + + return; + } + else if (pattern == "RRRR") + { + int startOffset = strOffset; + int parsedYear = parse_string_to_get_int(str, strLength, strOffset, 4); + int numberOfSymbols = strOffset - startOffset; + + outTimes.tm_year = numberOfSymbols <= 2 + ? round_year_pattern_implementation(parsedYear, outTimes.tm_year + 1900) + : parsedYear; + outTimes.tm_year -= 1900; + + return; + } + else if (pattern == "RM") { int month = roman_to_int(str, strLength, strOffset); if (month == 0 || month > 12) @@ -1995,32 +2180,22 @@ static void string_to_format_datetime_pattern_matcher(std::string_view pattern, if (pattern == "HH"|| pattern == "HH12") { + formatFlags |= Format::HH12; + int hours = parse_string_to_get_int(str, strLength, strOffset, 2); - if (hours > 12) + if (hours < 1 || hours > 12) { cb->err(Arg::Gds(isc_value_for_pattern_is_out_of_range) << - string(pattern.data(), pattern.length()) << Arg::Num(0) << Arg::Num(12)); + string(pattern.data(), pattern.length()) << Arg::Num(1) << Arg::Num(12)); } - if (str[strOffset] == ' ') - strOffset++; - - std::string_view period = parse_string_to_get_first_word(str, strLength, strOffset, 2); - if (period == "AM") - { - outTimes.tm_hour = hours == 12 ? 0 : hours; - return; - } - else if (period == "PM") - { - outTimes.tm_hour = hours == 12 ? hours : 12 + hours; - return; - } - - cb->err(Arg::Gds(isc_incorrect_hours_period) << string(period.data(), period.length())); + outTimes.tm_hour = hours; + return; } else if (pattern == "HH24") { + formatFlags |= Format::HH24; + int hours = parse_string_to_get_int(str, strLength, strOffset, 2); if (hours > 23) { @@ -2036,6 +2211,8 @@ static void string_to_format_datetime_pattern_matcher(std::string_view pattern, case 'S': if (pattern == "SS") { + formatFlags |= Format::SS; + int seconds = parse_string_to_get_int(str, strLength, strOffset, 2); if (seconds > 59) { @@ -2048,7 +2225,9 @@ static void string_to_format_datetime_pattern_matcher(std::string_view pattern, } else if (pattern == "SSSSS") { - constexpr int maximumSecondsInDay = 24 * 60 * 60 - 1; + formatFlags |= Format::SSSSS; + + constexpr int maximumSecondsInDay = NoThrowTimeStamp::SECONDS_PER_DAY - 1; int secondsInDay = parse_string_to_get_int(str, strLength, strOffset, 5); if (secondsInDay > maximumSecondsInDay) @@ -2083,9 +2262,37 @@ static void string_to_format_datetime_pattern_matcher(std::string_view pattern, } break; + case 'A': + if (pattern == AM_PERIOD) + { + // If we get A.M or P.M first, set flag, so we can calculate hours later + if (outTimes.tm_hour == 0) + formatFlags |= Format::AM_OR_PM_FIRST; + + std::string_view period = parse_string_to_get_substring(str, strLength, strOffset, sizeof(AM_PERIOD) - 1, false); + formatFlags |= apply_period(period, outTimes, cb); + return; + } + break; + + case 'P': + if (pattern == PM_PERIOD) + { + // If we get A.M or P.M first, set flag, so we can calculate hours later + if (outTimes.tm_hour == 0) + formatFlags |= Format::AM_OR_PM_FIRST; + + std::string_view period = parse_string_to_get_substring(str, strLength, strOffset, sizeof(PM_PERIOD) - 1, false); + formatFlags |= apply_period(period, outTimes, cb); + return; + } + break; + case 'T': if (pattern == "TZH") { + formatFlags |= Format::TZH; + if (previousPattern == "TZM") { outTimezoneInMinutes += sign(outTimezoneInMinutes) * @@ -2097,6 +2304,8 @@ static void string_to_format_datetime_pattern_matcher(std::string_view pattern, } else if (pattern == "TZM") { + formatFlags |= Format::TZM; + if (previousPattern == "TZH") { outTimezoneInMinutes += sign(outTimezoneInMinutes) * @@ -2108,8 +2317,6 @@ static void string_to_format_datetime_pattern_matcher(std::string_view pattern, } else if (pattern == "TZR") { - USHORT timezoneId; - int parsedTimezoneNameLength; bool timezoneNameIsCorrect = timeZoneTrie().contains(str + strOffset, outTimezoneId, parsedTimezoneNameLength); if (!timezoneNameIsCorrect) @@ -2124,9 +2331,49 @@ static void string_to_format_datetime_pattern_matcher(std::string_view pattern, invalid_pattern_exception(pattern, cb); } +// These rules are taken from ISO/IEC 9075-2:2023(E) 9.52 Datetime templates +static void validate_format_flags(Format::Patterns formatFlags, Callbacks* cb) +{ + if (formatFlags & (Format::HH12 | Format::AM | Format::PM)) + { + // If CT contains , then CT shall not contain or . + if (formatFlags & Format::HH24) + cb->err(Arg::Gds(isc_incompatible_format_patterns) << Arg::Str("HH24") << Arg::Str("HH/HH12/A.M./P.M.")); -ISC_TIMESTAMP_TZ CVT_string_to_format_datetime(const dsc* desc, const Firebird::string& format, Firebird::Callbacks* cb, - const EXPECT_DATETIME expectedType) + // If CT contains , then CT shall contain and shall not contain . + // If CT contains , then CT shall contain and shall not contain . + if (!(formatFlags & Format::HH12) == (formatFlags & Format::AM || formatFlags & Format::PM)) + { + cb->err(Arg::Gds(isc_pattern_cant_be_used_without_other_pattern_and_vice_versa) + << Arg::Str("HH/HH12") << Arg::Str("A.M./P.M.")); + } + } + + // If CT contains , then CT shall not contain any of the following: + // , , , , or . + if (formatFlags & Format::SSSSS) + { + if (formatFlags & Format::HH12) + cb->err(Arg::Gds(isc_incompatible_format_patterns) << Arg::Str("SSSSS") << Arg::Str("HH/HH12")); + if (formatFlags & Format::HH24) + cb->err(Arg::Gds(isc_incompatible_format_patterns) << Arg::Str("SSSSS") << Arg::Str("HH24")); + if (formatFlags & Format::MI) + cb->err(Arg::Gds(isc_incompatible_format_patterns) << Arg::Str("SSSSS") << Arg::Str("MI")); + if (formatFlags & Format::SS) + cb->err(Arg::Gds(isc_incompatible_format_patterns) << Arg::Str("SSSSS") << Arg::Str("HH/HH12")); + if (formatFlags & (Format::AM | Format::PM)) + cb->err(Arg::Gds(isc_incompatible_format_patterns) << Arg::Str("SSSSS") << Arg::Str("A.M./P.M.")); + } + + // If CT contains , then CT shall contain . + if (formatFlags & Format::TZM && !(formatFlags & Format::TZH)) + cb->err(Arg::Gds(isc_pattern_cant_be_used_without_other_pattern) << Arg::Str("TZM") << Arg::Str("TZH")); +} + + +ISC_TIMESTAMP_TZ CVT_string_to_format_datetime(const dsc* desc, const Firebird::string& format, + const EXPECT_DATETIME expectedType, Firebird::Callbacks* cb) { if (!DTYPE_IS_TEXT(desc->dsc_dtype)) cb->err(Arg::Gds(isc_invalid_data_type_for_date_format)); @@ -2145,11 +2392,12 @@ ISC_TIMESTAMP_TZ CVT_string_to_format_datetime(const dsc* desc, const Firebird:: string formatUpper(format.length(), '\0'); for (int i = 0; i < format.length(); i++) formatUpper[i] = toupper(format[i]); + int formatLength = formatUpper.length(); + struct tm times; - memset(×, 0, sizeof(struct tm)); - times.tm_year = 1 - 1900; - times.tm_mday = 1; + memset(×, 0, sizeof(times)); + NoThrowTimeStamp::decode_date(cb->getLocalDate(), ×); int fractions = 0; @@ -2163,22 +2411,19 @@ ISC_TIMESTAMP_TZ CVT_string_to_format_datetime(const dsc* desc, const Firebird:: std::string_view pattern; std::string_view previousPattern; - for (int i = 0; i < formatUpper.length(); i++) + Format::Patterns formatFlags = Format::NONE; + + for (int i = 0; i < formatLength; i++) { - const char symbol = formatUpper[i]; - - if (is_separator(symbol)) + // Iterate through format and string until we meet any non separator symbol + for (; i < formatLength; i++, formatOffset++) { - if (formatOffset != i) - { - string_to_format_datetime_pattern_matcher(pattern, previousPattern, stringUpper.c_str(), - stringLength, stringOffset, times, fractions, timezoneOffsetInMinutes, timezoneId, cb); - previousPattern = pattern; - } - - formatOffset = i + 1; - continue; + if (!is_separator(formatUpper[i])) + break; } + // All remaining characters were separators, so we have fully read the format, get out + if (i == formatLength) + break; for (; stringOffset < stringLength; stringOffset++) { @@ -2189,31 +2434,41 @@ ISC_TIMESTAMP_TZ CVT_string_to_format_datetime(const dsc* desc, const Firebird:: if (stringOffset >= stringLength) cb->err(Arg::Gds(isc_data_for_format_is_exhausted) << string(formatUpper.c_str() + formatOffset)); - pattern = std::string_view(formatUpper.c_str() + formatOffset, i - formatOffset + 1); - bool isFound = false; - for (int j = 0; j < FB_NELEM(TO_STRING_PATTERNS); j++) + // Start reading format and comparing it to patterns, until we stop finding similarities + for (; i < formatLength; i++) { - if (!strncmp(TO_STRING_PATTERNS[j], pattern.data(), pattern.length())) + pattern = std::string_view(formatUpper.c_str() + formatOffset, i - formatOffset + 1); + bool isFound = false; + + for (int j = 0; j < FB_NELEM(TO_STRING_PATTERNS); j++) { - isFound = true; - if (i == formatUpper.length() - 1) + if (!strncmp(TO_STRING_PATTERNS[j], pattern.data(), pattern.length())) { - string_to_format_datetime_pattern_matcher(pattern, previousPattern, stringUpper.c_str(), - stringLength, stringOffset, times, fractions, timezoneOffsetInMinutes, timezoneId, cb); + isFound = true; + if (i == formatLength - 1) + { + string_to_format_datetime_pattern_matcher(pattern, formatFlags, previousPattern, stringUpper.c_str(), + stringLength, stringOffset, times, fractions, timezoneOffsetInMinutes, timezoneId, cb); + } + break; } - break; } + if (!isFound) + break; } - if (isFound) - continue; + + if (i == formatLength) + break; if (pattern.length() <= 1) invalid_pattern_exception(pattern, cb); + // Our current pattern contains real pattern + one extra symbol, so we need to drop it pattern = pattern.substr(0, pattern.length() - 1); - string_to_format_datetime_pattern_matcher(pattern, previousPattern, stringUpper.c_str(), + string_to_format_datetime_pattern_matcher(pattern, formatFlags, previousPattern, stringUpper.c_str(), stringLength, stringOffset, times, fractions, timezoneOffsetInMinutes, timezoneId, cb); previousPattern = pattern; + formatOffset = i; i--; } @@ -2227,6 +2482,16 @@ ISC_TIMESTAMP_TZ CVT_string_to_format_datetime(const dsc* desc, const Firebird:: if (stringOffset < stringLength) cb->err(Arg::Gds(isc_trailing_part_of_string) << string(stringUpper.c_str() + stringOffset)); + + validate_format_flags(formatFlags, cb); + + // Deferred application of 12h period, if period was encountered first + if (formatFlags & Format::AM_OR_PM_FIRST) + { + bool periodIsAm = static_cast(formatFlags & Format::AM); + apply_period(periodIsAm ? AM_PERIOD : PM_PERIOD, times, cb); + } + ISC_TIMESTAMP_TZ timestampTZ; timestampTZ.utc_timestamp = NoThrowTimeStamp::encode_timestamp(×, fractions); @@ -2242,21 +2507,9 @@ ISC_TIMESTAMP_TZ CVT_string_to_format_datetime(const dsc* desc, const Firebird:: abs(timezoneOffsetInMinutes) / 60, abs(timezoneOffsetInMinutes) % 60); } - if (expectedType == expect_sql_time_tz || expectedType == expect_timestamp_tz || timestampTZ.time_zone != sessionTimeZone) - TimeZoneUtil::localTimeStampToUtc(timestampTZ); + timeStampToUtc(timestampTZ, sessionTimeZone, expectedType, cb); - if (timestampTZ.time_zone != sessionTimeZone) - { - if (expectedType == expect_sql_time) - { - ISC_TIME_TZ timeTz; - timeTz.utc_time = timestampTZ.utc_timestamp.timestamp_time; - timeTz.time_zone = timestampTZ.time_zone; - timestampTZ.utc_timestamp.timestamp_time = TimeZoneUtil::timeTzToTime(timeTz, cb); - } - else if (expectedType == expect_timestamp) - *(ISC_TIMESTAMP*) ×tampTZ = TimeZoneUtil::timeStampTzToTimeStamp(timestampTZ, sessionTimeZone); - } + validateTimeStamp(timestampTZ.utc_timestamp, expectedType, desc, cb); return timestampTZ; } diff --git a/src/common/cvt.h b/src/common/cvt.h index 2fd0ecedfa..aaf6eed9f3 100644 --- a/src/common/cvt.h +++ b/src/common/cvt.h @@ -106,7 +106,7 @@ void CVT_string_to_datetime(const dsc*, ISC_TIMESTAMP_TZ*, bool*, const Firebird bool, Firebird::Callbacks*); const UCHAR* CVT_get_bytes(const dsc*, unsigned&); Firebird::string CVT_datetime_to_format_string(const dsc* desc, const Firebird::string& format, Firebird::Callbacks* cb); -ISC_TIMESTAMP_TZ CVT_string_to_format_datetime(const dsc* desc, const Firebird::string& format, Firebird::Callbacks* cb, - const Firebird::EXPECT_DATETIME expectedType); +ISC_TIMESTAMP_TZ CVT_string_to_format_datetime(const dsc* desc, const Firebird::string& format, + const Firebird::EXPECT_DATETIME expectedType, Firebird::Callbacks* cb); #endif //COMMON_CVT_H diff --git a/src/common/tests/CvtTest.cpp b/src/common/tests/CvtTest.cpp index b28e14e1d5..c2893266b6 100644 --- a/src/common/tests/CvtTest.cpp +++ b/src/common/tests/CvtTest.cpp @@ -6,29 +6,42 @@ using namespace Firebird; using namespace Jrd; using namespace CvtTestUtils; + BOOST_AUTO_TEST_SUITE(CVTSuite) BOOST_AUTO_TEST_SUITE(CVTDatetimeFormat) +// Currently we cannot print our error messages because we need master interface for that static void errFunc(const Firebird::Arg::StatusVector& v) { v.raise(); } -CVTCallback cb(errFunc); +MockCallback cb(errFunc, std::bind(mockGetLocalDate, 2023)); BOOST_AUTO_TEST_SUITE(CVTDatetimeToFormatString) template static void testCVTDatetimeToFormatString(T date, const string& format, const string& expected, Callbacks& cb) { - dsc desc; - desc.dsc_dtype = getDSCTypeFromDateType(); - desc.dsc_length = sizeof(T); - desc.dsc_address = (UCHAR*) &date; - desc.dsc_scale = 0; + try + { + dsc desc; + desc.dsc_dtype = getDSCTypeFromDateType(); + desc.dsc_length = sizeof(T); + desc.dsc_address = (UCHAR*) &date; + desc.dsc_scale = 0; - string result = CVT_datetime_to_format_string(&desc, format, &cb); - BOOST_TEST(result == expected, "\nRESULT: " << result.c_str() << "\nEXPECTED: " << expected.c_str()); + BOOST_TEST_INFO("FORMAT: " << "\"" << format.c_str() << "\""); + + string result = CVT_datetime_to_format_string(&desc, format, &cb); + + BOOST_TEST(result == expected, "\nRESULT: " << result.c_str() << "\nEXPECTED: " << expected.c_str()); + } + catch(const Exception& ex) + { + BOOST_TEST_INFO("Exception was caught!"); + BOOST_TEST(false); + } } BOOST_AUTO_TEST_SUITE(FunctionalTest) @@ -44,9 +57,56 @@ BOOST_AUTO_TEST_CASE(CVTDatetimeToFormatStringTest_DATE) testCVTDatetimeToFormatString(createDate(9999, 1, 1), "YEAR.YYYY.YYY.YY.Y", "9999.9999.999.99.9", cb); testCVTDatetimeToFormatString(createDate(1, 1, 1), "Q", "1", cb); + testCVTDatetimeToFormatString(createDate(1, 2, 1), "Q", "1", cb); + testCVTDatetimeToFormatString(createDate(1, 3, 1), "Q", "1", cb); testCVTDatetimeToFormatString(createDate(1, 4, 1), "Q", "2", cb); + testCVTDatetimeToFormatString(createDate(1, 5, 1), "Q", "2", cb); + testCVTDatetimeToFormatString(createDate(1, 6, 1), "Q", "2", cb); testCVTDatetimeToFormatString(createDate(1, 7, 1), "Q", "3", cb); + testCVTDatetimeToFormatString(createDate(1, 8, 1), "Q", "3", cb); + testCVTDatetimeToFormatString(createDate(1, 9, 1), "Q", "3", cb); testCVTDatetimeToFormatString(createDate(1, 10, 1), "Q", "4", cb); + testCVTDatetimeToFormatString(createDate(1, 11, 1), "Q", "4", cb); + testCVTDatetimeToFormatString(createDate(1, 12, 1), "Q", "4", cb); + + testCVTDatetimeToFormatString(createDate(1, 1, 1), "MON", "Jan", cb); + testCVTDatetimeToFormatString(createDate(1, 2, 1), "MON", "Feb", cb); + testCVTDatetimeToFormatString(createDate(1, 3, 1), "MON", "Mar", cb); + testCVTDatetimeToFormatString(createDate(1, 4, 1), "MON", "Apr", cb); + testCVTDatetimeToFormatString(createDate(1, 5, 1), "MON", "May", cb); + testCVTDatetimeToFormatString(createDate(1, 6, 1), "MON", "Jun", cb); + testCVTDatetimeToFormatString(createDate(1, 7, 1), "MON", "Jul", cb); + testCVTDatetimeToFormatString(createDate(1, 8, 1), "MON", "Aug", cb); + testCVTDatetimeToFormatString(createDate(1, 9, 1), "MON", "Sep", cb); + testCVTDatetimeToFormatString(createDate(1, 10, 1), "MON", "Oct", cb); + testCVTDatetimeToFormatString(createDate(1, 11, 1), "MON", "Nov", cb); + testCVTDatetimeToFormatString(createDate(1, 12, 1), "MON", "Dec", cb); + + testCVTDatetimeToFormatString(createDate(1, 1, 1), "MONTH", "JANUARY", cb); + testCVTDatetimeToFormatString(createDate(1, 2, 1), "MONTH", "FEBRUARY", cb); + testCVTDatetimeToFormatString(createDate(1, 3, 1), "MONTH", "MARCH", cb); + testCVTDatetimeToFormatString(createDate(1, 4, 1), "MONTH", "APRIL", cb); + testCVTDatetimeToFormatString(createDate(1, 5, 1), "MONTH", "MAY", cb); + testCVTDatetimeToFormatString(createDate(1, 6, 1), "MONTH", "JUNE", cb); + testCVTDatetimeToFormatString(createDate(1, 7, 1), "MONTH", "JULY", cb); + testCVTDatetimeToFormatString(createDate(1, 8, 1), "MONTH", "AUGUST", cb); + testCVTDatetimeToFormatString(createDate(1, 9, 1), "MONTH", "SEPTEMBER", cb); + testCVTDatetimeToFormatString(createDate(1, 10, 1), "MONTH", "OCTOBER", cb); + testCVTDatetimeToFormatString(createDate(1, 11, 1), "MONTH", "NOVEMBER", cb); + testCVTDatetimeToFormatString(createDate(1, 12, 1), "MONTH", "DECEMBER", cb); + + testCVTDatetimeToFormatString(createDate(1, 1, 1), "RM", "I", cb); + testCVTDatetimeToFormatString(createDate(1, 2, 1), "RM", "II", cb); + testCVTDatetimeToFormatString(createDate(1, 3, 1), "RM", "III", cb); + testCVTDatetimeToFormatString(createDate(1, 4, 1), "RM", "IV", cb); + testCVTDatetimeToFormatString(createDate(1, 5, 1), "RM", "V", cb); + testCVTDatetimeToFormatString(createDate(1, 6, 1), "RM", "VI", cb); + testCVTDatetimeToFormatString(createDate(1, 7, 1), "RM", "VII", cb); + testCVTDatetimeToFormatString(createDate(1, 8, 1), "RM", "VIII", cb); + testCVTDatetimeToFormatString(createDate(1, 9, 1), "RM", "IX", cb); + testCVTDatetimeToFormatString(createDate(1, 10, 1), "RM", "X", cb); + testCVTDatetimeToFormatString(createDate(1, 11, 1), "RM", "XI", cb); + testCVTDatetimeToFormatString(createDate(1, 12, 1), "RM", "XII", cb); testCVTDatetimeToFormatString(createDate(1, 1, 1), "MM:RM-MON/MONTH", "01:I-Jan/JANUARY", cb); testCVTDatetimeToFormatString(createDate(1, 6, 1), "MM-RM.MON;MONTH", "06-VI.Jun;JUNE", cb); @@ -56,6 +116,22 @@ BOOST_AUTO_TEST_CASE(CVTDatetimeToFormatStringTest_DATE) testCVTDatetimeToFormatString(createDate(1, 6, 15), "WW-W", "24-3", cb); testCVTDatetimeToFormatString(createDate(1, 12, 30), "WW.W", "52.5", cb); + testCVTDatetimeToFormatString(createDate(1, 1, 1), "DAY", "MONDAY", cb); + testCVTDatetimeToFormatString(createDate(1, 1, 2), "DAY", "TUESDAY", cb); + testCVTDatetimeToFormatString(createDate(1, 1, 3), "DAY", "WEDNESDAY", cb); + testCVTDatetimeToFormatString(createDate(1, 1, 4), "DAY", "THURSDAY", cb); + testCVTDatetimeToFormatString(createDate(1, 1, 5), "DAY", "FRIDAY", cb); + testCVTDatetimeToFormatString(createDate(1, 1, 6), "DAY", "SATURDAY", cb); + testCVTDatetimeToFormatString(createDate(1, 1, 7), "DAY", "SUNDAY", cb); + + testCVTDatetimeToFormatString(createDate(1, 1, 1), "DY", "Mon", cb); + testCVTDatetimeToFormatString(createDate(1, 1, 2), "DY", "Tue", cb); + testCVTDatetimeToFormatString(createDate(1, 1, 3), "DY", "Wed", cb); + testCVTDatetimeToFormatString(createDate(1, 1, 4), "DY", "Thu", cb); + testCVTDatetimeToFormatString(createDate(1, 1, 5), "DY", "Fri", cb); + testCVTDatetimeToFormatString(createDate(1, 1, 6), "DY", "Sat", cb); + testCVTDatetimeToFormatString(createDate(1, 1, 7), "DY", "Sun", cb); + testCVTDatetimeToFormatString(createDate(2023, 6, 4), "D;DAY-DY", "1;SUNDAY-Sun", cb); testCVTDatetimeToFormatString(createDate(2023, 6, 7), "D.DAY,DY", "4.WEDNESDAY,Wed", cb); testCVTDatetimeToFormatString(createDate(2023, 6, 10), "D DAY DY", "7 SATURDAY Sat", cb); @@ -72,9 +148,47 @@ BOOST_AUTO_TEST_CASE(CVTDatetimeToFormatStringTest_DATE) BOOST_AUTO_TEST_CASE(CVTDatetimeToFormatStringTest_TIME) { - testCVTDatetimeToFormatString(createTime(0, 0, 0), "HH-HH12.HH24,MI/SS SSSSS", "12 AM-12 AM.00,00/00 0", cb); - testCVTDatetimeToFormatString(createTime(12, 35, 15), "HH.HH12:HH24;MI-SS/SSSSS", "12 PM.12 PM:12;35-15/45315", cb); - testCVTDatetimeToFormatString(createTime(23, 59, 59), " HH - HH12 . HH24 , MI / SS SSSSS ", " 11 PM - 11 PM . 23 , 59 / 59 86399 ", cb); + testCVTDatetimeToFormatString(createTime(0, 0, 0), "HH24", "00", cb); + testCVTDatetimeToFormatString(createTime(12, 0, 0), "HH24", "12", cb); + testCVTDatetimeToFormatString(createTime(23, 0, 0), "HH24", "23", cb); + + testCVTDatetimeToFormatString(createTime(0, 0, 0), "HH A.M.", "12 A.M.", cb); + testCVTDatetimeToFormatString(createTime(0, 0, 0), "HH P.M.", "12 A.M.", cb); + testCVTDatetimeToFormatString(createTime(11, 0, 0), "HH A.M.", "11 A.M.", cb); + testCVTDatetimeToFormatString(createTime(11, 0, 0), "HH P.M.", "11 A.M.", cb); + testCVTDatetimeToFormatString(createTime(12, 0, 0), "HH A.M.", "12 P.M.", cb); + testCVTDatetimeToFormatString(createTime(12, 0, 0), "HH P.M.", "12 P.M.", cb); + testCVTDatetimeToFormatString(createTime(13, 0, 0), "HH A.M.", "01 P.M.", cb); + testCVTDatetimeToFormatString(createTime(13, 0, 0), "HH P.M.", "01 P.M.", cb); + testCVTDatetimeToFormatString(createTime(23, 0, 0), "HH A.M.", "11 P.M.", cb); + testCVTDatetimeToFormatString(createTime(23, 0, 0), "HH P.M.", "11 P.M.", cb); + + testCVTDatetimeToFormatString(createTime(0, 0, 0), "HH12 A.M.", "12 A.M.", cb); + testCVTDatetimeToFormatString(createTime(0, 0, 0), "HH12 P.M.", "12 A.M.", cb); + testCVTDatetimeToFormatString(createTime(11, 0, 0), "HH12 A.M.", "11 A.M.", cb); + testCVTDatetimeToFormatString(createTime(11, 0, 0), "HH12 P.M.", "11 A.M.", cb); + testCVTDatetimeToFormatString(createTime(12, 0, 0), "HH12 A.M.", "12 P.M.", cb); + testCVTDatetimeToFormatString(createTime(12, 0, 0), "HH12 P.M.", "12 P.M.", cb); + testCVTDatetimeToFormatString(createTime(13, 0, 0), "HH12 A.M.", "01 P.M.", cb); + testCVTDatetimeToFormatString(createTime(13, 0, 0), "HH12 P.M.", "01 P.M.", cb); + testCVTDatetimeToFormatString(createTime(23, 0, 0), "HH12 A.M.", "11 P.M.", cb); + testCVTDatetimeToFormatString(createTime(23, 0, 0), "HH12 P.M.", "11 P.M.", cb); + + testCVTDatetimeToFormatString(createTime(0, 0, 0), "MI", "00", cb); + testCVTDatetimeToFormatString(createTime(0, 30, 0), "MI", "30", cb); + testCVTDatetimeToFormatString(createTime(0, 59, 0), "MI", "59", cb); + + testCVTDatetimeToFormatString(createTime(0, 0, 0), "SS", "00", cb); + testCVTDatetimeToFormatString(createTime(0, 0, 30), "SS", "30", cb); + testCVTDatetimeToFormatString(createTime(0, 0, 59), "SS", "59", cb); + + testCVTDatetimeToFormatString(createTime(0, 0, 0), "SSSSS", "0", cb); + testCVTDatetimeToFormatString(createTime(12, 30, 15), "SSSSS", "45015", cb); + testCVTDatetimeToFormatString(createTime(23, 59, 59), "SSSSS", "86399", cb); + + testCVTDatetimeToFormatString(createTime(0, 0, 0), "HH-HH12 A.M..HH24,MI/SS SSSSS", "12-12 A.M..00,00/00 0", cb); + testCVTDatetimeToFormatString(createTime(12, 35, 15), "HH.HH12 P.M.:HH24;MI-SS/SSSSS", "12.12 P.M.:12;35-15/45315", cb); + testCVTDatetimeToFormatString(createTime(23, 59, 59), " HH P.M. - HH12 . HH24 , MI / SS SSSSS ", " 11 P.M. - 11 . 23 , 59 / 59 86399 ", cb); testCVTDatetimeToFormatString(createTime(0, 0, 0, 1), "FF1.FF2/FF3;FF4:FF5-FF6,FF7-FF8 FF9", "1.10/100;1000:10000-100000,1000000-10000000 100000000", cb); testCVTDatetimeToFormatString(createTime(0, 0, 0, 1000), "FF1.FF2/FF3;FF4:FF5-FF6,FF7-FF8 FF9", "1.10/100;1000:10000-100000,1000000-10000000 100000000", cb); @@ -88,12 +202,12 @@ BOOST_AUTO_TEST_CASE(CVTDatetimeToFormatStringTest_TIMESTAMP) testCVTDatetimeToFormatString(timestamp, "YEAR.YYYY.YYY.YY.Y/J", "1982.1982.982.82.2/2445081", cb); testCVTDatetimeToFormatString(timestamp, "Q-MM-RM-MON-MONTH", "2-04-IV-Apr-APRIL", cb); testCVTDatetimeToFormatString(timestamp, "WW,W-D;DAY:DD DDD.DY", "16,3-4;WEDNESDAY:21 111.Wed", cb); - testCVTDatetimeToFormatString(timestamp, "HH-HH12-HH24-MI-SS-SSSSS.FF2", "01 AM-01 AM-01-34-15-5655.25", cb); + testCVTDatetimeToFormatString(timestamp, "HH-HH12 P.M.-HH24-MI-SS-SSSSS.FF2", "01-01 A.M.-01-34-15-5655.25", cb); } BOOST_AUTO_TEST_CASE(CVTDatetimeToFormatStringTest_TIME_TZ) { - testCVTDatetimeToFormatString(createTimeTZ(15, 35, 59, 0, 900), "HH-HH12-HH24-MI-SS-SSSSS.FF1/TZH/TZM", "03 PM-03 PM-15-35-59-56159.9/+00/00", cb); + testCVTDatetimeToFormatString(createTimeTZ(15, 35, 59, 0, 900), "HH A.M.-HH12-HH24-MI-SS-SSSSS.FF1/TZH/TZM", "03 P.M.-03-15-35-59-56159.9/+00/00", cb); testCVTDatetimeToFormatString(createTimeTZ(15, 35, 59, 160), "HH24:MI-TZH:TZM", "15:35-+02:40", cb); testCVTDatetimeToFormatString(createTimeTZ(15, 35, 59, -160), "HH24:MI TZH:TZM", "15:35 -02:40", cb); @@ -109,7 +223,7 @@ BOOST_AUTO_TEST_CASE(CVTDatetimeToFormatStringTest_TIMESTAMP_TZ) testCVTDatetimeToFormatString(timestampTZ, "YEAR.YYYY.YYY.YY.Y/J", "1982.1982.982.82.2/2445081", cb); testCVTDatetimeToFormatString(timestampTZ, "Q-MM-RM-MON-MONTH", "2-04-IV-Apr-APRIL", cb); testCVTDatetimeToFormatString(timestampTZ, "WW,W-D;DAY:DD DDD.DY", "16,3-4;WEDNESDAY:21 111.Wed", cb); - testCVTDatetimeToFormatString(timestampTZ, "HH-HH12-HH24-MI-SS-SSSSS.FF2/TZH/TZM", "01 AM-01 AM-01-34-15-5655.50/+00/00", cb); + testCVTDatetimeToFormatString(timestampTZ, "HH A.M.-HH12-HH24-MI-SS-SSSSS.FF2/TZH/TZM", "01 A.M.-01-01-34-15-5655.50/+00/00", cb); testCVTDatetimeToFormatString(createTimeStampTZ(1982, 4, 21, 1, 34, 15, 70), "HH24:MI-TZH:TZM", "01:34-+01:10", cb); testCVTDatetimeToFormatString(createTimeStampTZ(1982, 4, 21, 1, 34, 15, -70), "HH24:MI TZH:TZM", "01:34 -01:10", cb); @@ -126,7 +240,7 @@ BOOST_AUTO_TEST_CASE(CVTDatetimeToFormatStringTest_SOLID_PATTERNS) testCVTDatetimeToFormatString(timestampTZ, "YEARYYYYYYYYYYJ", "198219821982822445081", cb); testCVTDatetimeToFormatString(timestampTZ, "QMMRMMONMONTH", "204IVAprAPRIL", cb); testCVTDatetimeToFormatString(timestampTZ, "WWWD/DAYDDDDDDY", "1634/WEDNESDAY1111112", cb); - testCVTDatetimeToFormatString(timestampTZ, "HHHH12HH24MISSSSSSSFF2TZHTZM", "01 AM01 AM013456551550+0000", cb); + testCVTDatetimeToFormatString(timestampTZ, "HHHH12A.M.HH24MISSSSSSSFF2TZHTZM", "0101A.M.013456551550+0000", cb); } BOOST_AUTO_TEST_CASE(CVTDatetimeToFormatStringTest_RAW_TEXT) @@ -149,37 +263,48 @@ BOOST_AUTO_TEST_SUITE(CVTStringToFormatDateTime) static void testCVTStringToFormatDateTime(const string& date, const string& format, const ISC_TIMESTAMP_TZ& expected, Firebird::EXPECT_DATETIME expectedType, Callbacks& cb) { - string varyingString = "xx"; - varyingString += date; - *(USHORT*) varyingString.data() = varyingString.size() - sizeof(USHORT); + try + { + string varyingString = "xx"; + varyingString += date; + *(USHORT*) varyingString.data() = varyingString.size() - sizeof(USHORT); - dsc desc; - desc.dsc_dtype = dtype_varying; - desc.dsc_length = varyingString.size() + sizeof(USHORT); - desc.dsc_address = (UCHAR*) varyingString.data(); - desc.dsc_scale = 0; + dsc desc; + desc.dsc_dtype = dtype_varying; + desc.dsc_length = varyingString.size() + sizeof(USHORT); + desc.dsc_address = (UCHAR*) varyingString.data(); + desc.dsc_scale = 0; - const ISC_TIMESTAMP_TZ result = CVT_string_to_format_datetime(&desc, format, &cb, expectedType); + BOOST_TEST_INFO("INPUT: " << "\"" << date.c_str() << "\""); + BOOST_TEST_INFO("FORMAT: " << "\"" << format.c_str() << "\""); - struct tm resultTimes; - memset(&resultTimes, 0, sizeof(resultTimes)); - int resultFractions; - NoThrowTimeStamp::decode_timestamp(result.utc_timestamp, &resultTimes, &resultFractions); - SSHORT resultOffset; - TimeZoneUtil::extractOffset(result, &resultOffset); + const ISC_TIMESTAMP_TZ result = CVT_string_to_format_datetime(&desc, format, expectedType, &cb); - struct tm expectedTimes; - memset(&expectedTimes, 0, sizeof(expectedTimes)); - int expectedFractions; - NoThrowTimeStamp::decode_timestamp(expected.utc_timestamp, &expectedTimes, &expectedFractions); - SSHORT expectedOffset; - TimeZoneUtil::extractOffset(expected, &expectedOffset); + struct tm resultTimes; + memset(&resultTimes, 0, sizeof(resultTimes)); + int resultFractions; + NoThrowTimeStamp::decode_timestamp(result.utc_timestamp, &resultTimes, &resultFractions); + SSHORT resultOffset; + TimeZoneUtil::extractOffset(result, &resultOffset); - bool isEqual = !((bool) memcmp(&resultTimes, &expectedTimes, sizeof(struct tm))) - && resultFractions == expectedFractions && resultOffset == expectedOffset; + struct tm expectedTimes; + memset(&expectedTimes, 0, sizeof(expectedTimes)); + int expectedFractions; + NoThrowTimeStamp::decode_timestamp(expected.utc_timestamp, &expectedTimes, &expectedFractions); + SSHORT expectedOffset; + TimeZoneUtil::extractOffset(expected, &expectedOffset); - BOOST_TEST(isEqual, "\nRESULT: " << DECOMPOSE_TM_STRUCT(resultTimes, resultFractions, resultOffset) - << "\nEXPECTED: " << DECOMPOSE_TM_STRUCT(expectedTimes, expectedFractions, expectedOffset)); + bool isEqual = !((bool) memcmp(&resultTimes, &expectedTimes, sizeof(struct tm))) + && resultFractions == expectedFractions && resultOffset == expectedOffset; + + BOOST_TEST(isEqual, "\nRESULT: " << DECOMPOSE_TM_STRUCT(resultTimes, resultFractions, resultOffset) + << "\nEXPECTED: " << DECOMPOSE_TM_STRUCT(expectedTimes, expectedFractions, expectedOffset)); + } + catch(const Exception& ex) + { + BOOST_TEST_INFO("Exception was caught!"); + BOOST_TEST(false); + } } static void testCVTStringToFormatDateTimeExpectDate(const string& date, const string& format, @@ -200,48 +325,141 @@ static void testCVTStringToFormatDateTimeExpectTimeTZ(const string& date, const testCVTStringToFormatDateTime(date, format, expected, expect_sql_time_tz, cb); }; +static void testExceptionCvtStringToFormatDateTime(const string& date, const string& format, Callbacks& cb) +{ + string varyingString = "xx"; + varyingString += date; + *(USHORT*) varyingString.data() = varyingString.size() - sizeof(USHORT); + + dsc desc; + desc.dsc_dtype = dtype_varying; + desc.dsc_length = varyingString.size() + sizeof(USHORT); + desc.dsc_address = (UCHAR*) varyingString.data(); + desc.dsc_scale = 0; + + BOOST_TEST_INFO("INPUT: " << "\"" << date.c_str() << "\""); + BOOST_TEST_INFO("FORMAT: " << "\"" << format.c_str() << "\""); + + BOOST_CHECK_THROW(CVT_string_to_format_datetime(&desc, format, expect_timestamp_tz, &cb), status_exception); +} + BOOST_AUTO_TEST_SUITE(FunctionalTest) BOOST_AUTO_TEST_CASE(CVTStringToFormatDateTime_DATE) { - testCVTStringToFormatDateTimeExpectDate("1", "YEAR", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectDate("1234", "YEAR", createTimeStampTZ(1234, 1, 1, 0, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectDate("9999", "YEAR", createTimeStampTZ(9999, 1, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("1", "YEAR", createTimeStampTZ(1, 0, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("0001", "YEAR", createTimeStampTZ(1, 0, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("1234", "YEAR", createTimeStampTZ(1234, 0, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("9999", "YEAR", createTimeStampTZ(9999, 0, 0, 0, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectDate("1", "YYYY", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectDate("1234", "YYYY", createTimeStampTZ(1234, 1, 1, 0, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectDate("9999", "YYYY", createTimeStampTZ(9999, 1, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("1", "YYYY", createTimeStampTZ(1, 0, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("0001", "YYYY", createTimeStampTZ(1, 0, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("1234", "YYYY", createTimeStampTZ(1234, 0, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("9999", "YYYY", createTimeStampTZ(9999, 0, 0, 0, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectDate("1", "YYY", createTimeStampTZ(2001, 1, 1, 0, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectDate("522", "YYY", createTimeStampTZ(2522, 1, 1, 0, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectDate("999", "YYY", createTimeStampTZ(1999, 1, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("1", "YYY", createTimeStampTZ(2001, 0, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("001", "YYY", createTimeStampTZ(2001, 0, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("522", "YYY", createTimeStampTZ(2522, 0, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("523", "YYY", createTimeStampTZ(2523, 0, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("999", "YYY", createTimeStampTZ(2999, 0, 0, 0, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectDate("1", "YY", createTimeStampTZ(2001, 1, 1, 0, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectDate("72", "YY", createTimeStampTZ(2072, 1, 1, 0, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectDate("99", "YY", createTimeStampTZ(1999, 1, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("1", "YY", createTimeStampTZ(2001, 0, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("01", "YY", createTimeStampTZ(2001, 0, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("50", "YY", createTimeStampTZ(2050, 0, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("99", "YY", createTimeStampTZ(2099, 0, 0, 0, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectDate("1", "Y", createTimeStampTZ(2001, 1, 1, 0, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectDate("9", "Y", createTimeStampTZ(2009, 1, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("1", "Y", createTimeStampTZ(2021, 0, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("9", "Y", createTimeStampTZ(2029, 0, 0, 0, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectDate("1", "MM", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectDate("6", "MM", createTimeStampTZ(1, 6, 1, 0, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectDate("12", "MM", createTimeStampTZ(1, 12, 1, 0, 0, 0, 0), cb); + { + // RR pattern depends on last 2 digits of current year + MockCallback cb_2000(errFunc, std::bind(mockGetLocalDate, 2000)); + MockCallback cb_2049(errFunc, std::bind(mockGetLocalDate, 2049)); + MockCallback cb_2050(errFunc, std::bind(mockGetLocalDate, 2050)); + MockCallback cb_2099(errFunc, std::bind(mockGetLocalDate, 2099)); - testCVTStringToFormatDateTimeExpectDate("Jan", "MON", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectDate("Jun", "MON", createTimeStampTZ(1, 6, 1, 0, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectDate("Dec", "MON", createTimeStampTZ(1, 12, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("1", "RR", createTimeStampTZ(2001, 0, 0, 0, 0, 0, 0), cb_2000); + testCVTStringToFormatDateTimeExpectDate("1", "RR", createTimeStampTZ(2001, 0, 0, 0, 0, 0, 0), cb_2049); + testCVTStringToFormatDateTimeExpectDate("1", "RR", createTimeStampTZ(2101, 0, 0, 0, 0, 0, 0), cb_2050); + testCVTStringToFormatDateTimeExpectDate("1", "RR", createTimeStampTZ(2101, 0, 0, 0, 0, 0, 0), cb_2099); - testCVTStringToFormatDateTimeExpectDate("January", "MONTH", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectDate("June", "MONTH", createTimeStampTZ(1, 6, 1, 0, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectDate("December", "MONTH", createTimeStampTZ(1, 12, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("01", "RR", createTimeStampTZ(2001, 0, 0, 0, 0, 0, 0), cb_2000); + testCVTStringToFormatDateTimeExpectDate("01", "RR", createTimeStampTZ(2001, 0, 0, 0, 0, 0, 0), cb_2049); + testCVTStringToFormatDateTimeExpectDate("01", "RR", createTimeStampTZ(2101, 0, 0, 0, 0, 0, 0), cb_2050); + testCVTStringToFormatDateTimeExpectDate("01", "RR", createTimeStampTZ(2101, 0, 0, 0, 0, 0, 0), cb_2099); - testCVTStringToFormatDateTimeExpectDate("I", "RM", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectDate("IV", "RM", createTimeStampTZ(1, 4, 1, 0, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectDate("XII", "RM", createTimeStampTZ(1, 12, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("49", "RR", createTimeStampTZ(2049, 0, 0, 0, 0, 0, 0), cb_2000); + testCVTStringToFormatDateTimeExpectDate("49", "RR", createTimeStampTZ(2049, 0, 0, 0, 0, 0, 0), cb_2049); + testCVTStringToFormatDateTimeExpectDate("49", "RR", createTimeStampTZ(2149, 0, 0, 0, 0, 0, 0), cb_2050); + testCVTStringToFormatDateTimeExpectDate("49", "RR", createTimeStampTZ(2149, 0, 0, 0, 0, 0, 0), cb_2099); - testCVTStringToFormatDateTimeExpectDate("1", "DD", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectDate("15", "DD", createTimeStampTZ(1, 1, 15, 0, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectDate("31", "DD", createTimeStampTZ(1, 1, 31, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("50", "RR", createTimeStampTZ(1950, 0, 0, 0, 0, 0, 0), cb_2000); + testCVTStringToFormatDateTimeExpectDate("50", "RR", createTimeStampTZ(1950, 0, 0, 0, 0, 0, 0), cb_2049); + testCVTStringToFormatDateTimeExpectDate("50", "RR", createTimeStampTZ(2050, 0, 0, 0, 0, 0, 0), cb_2050); + testCVTStringToFormatDateTimeExpectDate("50", "RR", createTimeStampTZ(2050, 0, 0, 0, 0, 0, 0), cb_2099); + + testCVTStringToFormatDateTimeExpectDate("99", "RR", createTimeStampTZ(1999, 0, 0, 0, 0, 0, 0), cb_2000); + testCVTStringToFormatDateTimeExpectDate("99", "RR", createTimeStampTZ(1999, 0, 0, 0, 0, 0, 0), cb_2049); + testCVTStringToFormatDateTimeExpectDate("99", "RR", createTimeStampTZ(2099, 0, 0, 0, 0, 0, 0), cb_2050); + testCVTStringToFormatDateTimeExpectDate("99", "RR", createTimeStampTZ(2099, 0, 0, 0, 0, 0, 0), cb_2099); + + testCVTStringToFormatDateTimeExpectDate("1", "RRRR", createTimeStampTZ(2001, 0, 0, 0, 0, 0, 0), cb_2000); + testCVTStringToFormatDateTimeExpectDate("99", "RRRR", createTimeStampTZ(1999, 0, 0, 0, 0, 0, 0), cb_2000); + testCVTStringToFormatDateTimeExpectDate("0001", "RRRR", createTimeStampTZ(1, 0, 0, 0, 0, 0, 0), cb_2000); + testCVTStringToFormatDateTimeExpectDate("0099", "RRRR", createTimeStampTZ(99, 0, 0, 0, 0, 0, 0), cb_2000); + testCVTStringToFormatDateTimeExpectDate("1000", "RRRR", createTimeStampTZ(1000, 0, 0, 0, 0, 0, 0), cb_2000); + testCVTStringToFormatDateTimeExpectDate("9999", "RRRR", createTimeStampTZ(9999, 0, 0, 0, 0, 0, 0), cb_2000); + } + + testCVTStringToFormatDateTimeExpectDate("1", "MM", createTimeStampTZ(0, 1, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("01", "MM", createTimeStampTZ(0, 1, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("6", "MM", createTimeStampTZ(0, 6, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("06", "MM", createTimeStampTZ(0, 6, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("12", "MM", createTimeStampTZ(0, 12, 0, 0, 0, 0, 0), cb); + + testCVTStringToFormatDateTimeExpectDate("Jan", "MON", createTimeStampTZ(0, 1, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("Feb", "MON", createTimeStampTZ(0, 2, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("Mar", "MON", createTimeStampTZ(0, 3, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("Apr", "MON", createTimeStampTZ(0, 4, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("May", "MON", createTimeStampTZ(0, 5, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("Jun", "MON", createTimeStampTZ(0, 6, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("Jul", "MON", createTimeStampTZ(0, 7, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("Aug", "MON", createTimeStampTZ(0, 8, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("Sep", "MON", createTimeStampTZ(0, 9, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("Oct", "MON", createTimeStampTZ(0, 10, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("Nov", "MON", createTimeStampTZ(0, 11, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("Dec", "MON", createTimeStampTZ(0, 12, 0, 0, 0, 0, 0), cb); + + testCVTStringToFormatDateTimeExpectDate("January", "MONTH", createTimeStampTZ(0, 1, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("February", "MONTH", createTimeStampTZ(0, 2, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("March", "MONTH", createTimeStampTZ(0, 3, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("April", "MONTH", createTimeStampTZ(0, 4, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("May", "MONTH", createTimeStampTZ(0, 5, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("June", "MONTH", createTimeStampTZ(0, 6, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("July", "MONTH", createTimeStampTZ(0, 7, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("August", "MONTH", createTimeStampTZ(0, 8, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("September", "MONTH", createTimeStampTZ(0, 9, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("October", "MONTH", createTimeStampTZ(0, 10, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("November", "MONTH", createTimeStampTZ(0, 11, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("December", "MONTH", createTimeStampTZ(0, 12, 0, 0, 0, 0, 0), cb); + + testCVTStringToFormatDateTimeExpectDate("I", "RM", createTimeStampTZ(0, 1, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("II", "RM", createTimeStampTZ(0, 2, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("III", "RM", createTimeStampTZ(0, 3, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("IV", "RM", createTimeStampTZ(0, 4, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("V", "RM", createTimeStampTZ(0, 5, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("VI", "RM", createTimeStampTZ(0, 6, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("VII", "RM", createTimeStampTZ(0, 7, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("VIII", "RM", createTimeStampTZ(0, 8, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("IX", "RM", createTimeStampTZ(0, 9, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("X", "RM", createTimeStampTZ(0, 10, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("XI", "RM", createTimeStampTZ(0, 11, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("XII", "RM", createTimeStampTZ(0, 12, 0, 0, 0, 0, 0), cb); + + testCVTStringToFormatDateTimeExpectDate("1", "DD", createTimeStampTZ(0, 0, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("01", "DD", createTimeStampTZ(0, 0, 1, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("15", "DD", createTimeStampTZ(0, 0, 15, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("31", "DD", createTimeStampTZ(0, 0, 31, 0, 0, 0, 0), cb); testCVTStringToFormatDateTimeExpectDate("2451887", "J", createTimeStampTZ(2000, 12, 8, 0, 0, 0, 0), cb); testCVTStringToFormatDateTimeExpectDate("1721426", "J", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb); @@ -251,85 +469,150 @@ BOOST_AUTO_TEST_CASE(CVTStringToFormatDateTime_DATE) testCVTStringToFormatDateTimeExpectDate("1981-8/13", "YEAR.MM.DD", createTimeStampTZ(1981, 8, 13, 0, 0, 0, 0), cb); testCVTStringToFormatDateTimeExpectDate("9999 12;31", "YEAR.MM.DD", createTimeStampTZ(9999, 12, 31, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("1981-Aug/13", "YEAR.MON.DD", createTimeStampTZ(1981, 8, 13, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("1981-August/13", "YEAR.MONTH.DD", createTimeStampTZ(1981, 8, 13, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("1981-VIII/13", "YEAR.RM.DD", createTimeStampTZ(1981, 8, 13, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectDate("25.Jan.25", "YY;MON;DD", createTimeStampTZ(2025, 1, 25, 0, 0, 0, 0), cb); testCVTStringToFormatDateTimeExpectDate("./.1981./-8--/13--", " YEAR. -.MM.,,-.DD//", createTimeStampTZ(1981, 8, 13, 0, 0, 0, 0), cb); } BOOST_AUTO_TEST_CASE(CVTStringToFormatDateTime_TIME) { - testCVTStringToFormatDateTimeExpectTime("12 AM", "HH", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectTime("1 AM", "HH", createTimeStampTZ(1, 1, 1, 1, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectTime("11 AM", "HH", createTimeStampTZ(1, 1, 1, 11, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("12 A.M.", "HH A.M.", createTimeStampTZ(0, 0, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("12 A.M.", "HH P.M.", createTimeStampTZ(0, 0, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("1 A.M.", "HH A.M.", createTimeStampTZ(0, 0, 0, 1, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("01 A.M.", "HH A.M.", createTimeStampTZ(0, 0, 0, 1, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("1 A.M.", "HH P.M.", createTimeStampTZ(0, 0, 0, 1, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("01 A.M.", "HH P.M.", createTimeStampTZ(0, 0, 0, 1, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("11 A.M.", "HH A.M.", createTimeStampTZ(0, 0, 0, 11, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("11 A.M.", "HH P.M.", createTimeStampTZ(0, 0, 0, 11, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectTime("12 PM", "HH", createTimeStampTZ(1, 1, 1, 12, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectTime("1 PM", "HH", createTimeStampTZ(1, 1, 1, 13, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectTime("11 PM", "HH", createTimeStampTZ(1, 1, 1, 23, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("12 P.M.", "HH A.M.", createTimeStampTZ(0, 0, 0, 12, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("12 P.M.", "HH P.M.", createTimeStampTZ(0, 0, 0, 12, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("1 P.M.", "HH A.M.", createTimeStampTZ(0, 0, 0, 13, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("01 P.M.", "HH A.M.", createTimeStampTZ(0, 0, 0, 13, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("1 P.M.", "HH P.M.", createTimeStampTZ(0, 0, 0, 13, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("01 P.M.", "HH P.M.", createTimeStampTZ(0, 0, 0, 13, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("11 P.M.", "HH A.M.", createTimeStampTZ(0, 0, 0, 23, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("11 P.M.", "HH P.M.", createTimeStampTZ(0, 0, 0, 23, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectTime("12 AM", "HH12", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectTime("1 AM", "HH12", createTimeStampTZ(1, 1, 1, 1, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectTime("11 AM", "HH12", createTimeStampTZ(1, 1, 1, 11, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("12 A.M.", "HH12 A.M.", createTimeStampTZ(0, 0, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("12 A.M.", "HH12 P.M.", createTimeStampTZ(0, 0, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("1 A.M.", "HH12 A.M.", createTimeStampTZ(0, 0, 0, 1, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("01 A.M.", "HH12 A.M.", createTimeStampTZ(0, 0, 0, 1, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("1 A.M.", "HH12 P.M.", createTimeStampTZ(0, 0, 0, 1, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("01 A.M.", "HH12 P.M.", createTimeStampTZ(0, 0, 0, 1, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("11 A.M.", "HH12 A.M.", createTimeStampTZ(0, 0, 0, 11, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("11 A.M.", "HH12 P.M.", createTimeStampTZ(0, 0, 0, 11, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectTime("12 PM", "HH12", createTimeStampTZ(1, 1, 1, 12, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectTime("1 PM", "HH12", createTimeStampTZ(1, 1, 1, 13, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectTime("11 PM", "HH12", createTimeStampTZ(1, 1, 1, 23, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("12 P.M.", "HH12 A.M.", createTimeStampTZ(0, 0, 0, 12, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("12 P.M.", "HH12 P.M.", createTimeStampTZ(0, 0, 0, 12, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("1 P.M.", "HH12 A.M.", createTimeStampTZ(0, 0, 0, 13, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("01 P.M.", "HH12 A.M.", createTimeStampTZ(0, 0, 0, 13, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("1 P.M.", "HH12 P.M.", createTimeStampTZ(0, 0, 0, 13, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("01 P.M.", "HH12 P.M.", createTimeStampTZ(0, 0, 0, 13, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("11 P.M.", "HH12 A.M.", createTimeStampTZ(0, 0, 0, 23, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("11 P.M.", "HH12 P.M.", createTimeStampTZ(0, 0, 0, 23, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectTime("0", "HH24", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectTime("12", "HH24", createTimeStampTZ(1, 1, 1, 12, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectTime("23", "HH24", createTimeStampTZ(1, 1, 1, 23, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("A.M. 12", "A.M. HH", createTimeStampTZ(0, 0, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("A.M. 1", "P.M. HH", createTimeStampTZ(0, 0, 0, 1, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("A.M. 01", "P.M. HH", createTimeStampTZ(0, 0, 0, 1, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("A.M. 11", "A.M. HH", createTimeStampTZ(0, 0, 0, 11, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("P.M. 12", "A.M. HH", createTimeStampTZ(0, 0, 0, 12, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("P.M. 1", "P.M. HH", createTimeStampTZ(0, 0, 0, 13, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("P.M. 01", "P.M. HH", createTimeStampTZ(0, 0, 0, 13, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("P.M. 11", "A.M. HH", createTimeStampTZ(0, 0, 0, 23, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectTime("0", "MI", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectTime("30", "MI", createTimeStampTZ(1, 1, 1, 0, 30, 0, 0), cb); - testCVTStringToFormatDateTimeExpectTime("59", "MI", createTimeStampTZ(1, 1, 1, 0, 59, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("0", "HH24", createTimeStampTZ(0, 0, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("00", "HH24", createTimeStampTZ(0, 0, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("12", "HH24", createTimeStampTZ(0, 0, 0, 12, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("23", "HH24", createTimeStampTZ(0, 0, 0, 23, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectTime("0", "SS", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectTime("30", "SS", createTimeStampTZ(1, 1, 1, 0, 0, 30, 0), cb); - testCVTStringToFormatDateTimeExpectTime("59", "SS", createTimeStampTZ(1, 1, 1, 0, 0, 59, 0), cb); + testCVTStringToFormatDateTimeExpectTime("0", "MI", createTimeStampTZ(0, 0, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("00", "MI", createTimeStampTZ(0, 0, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("30", "MI", createTimeStampTZ(0, 0, 0, 0, 30, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("59", "MI", createTimeStampTZ(0, 0, 0, 0, 59, 0, 0), cb); - testCVTStringToFormatDateTimeExpectTime("0", "SSSSS", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb); - testCVTStringToFormatDateTimeExpectTime("45315", "SSSSS", createTimeStampTZ(1, 1, 1, 12, 35, 15, 0), cb); - testCVTStringToFormatDateTimeExpectTime("86399", "SSSSS", createTimeStampTZ(1, 1, 1, 23, 59, 59, 0), cb); + testCVTStringToFormatDateTimeExpectTime("0", "SS", createTimeStampTZ(0, 0, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("00", "SS", createTimeStampTZ(0, 0, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("30", "SS", createTimeStampTZ(0, 0, 0, 0, 0, 30, 0), cb); + testCVTStringToFormatDateTimeExpectTime("59", "SS", createTimeStampTZ(0, 0, 0, 0, 0, 59, 0), cb); - testCVTStringToFormatDateTimeExpectTime("1", "FF1", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 1000), cb); - testCVTStringToFormatDateTimeExpectTime("5", "FF1", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 5000), cb); - testCVTStringToFormatDateTimeExpectTime("9", "FF1", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 9000), cb); + testCVTStringToFormatDateTimeExpectTime("0", "SSSSS", createTimeStampTZ(0, 0, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("00000", "SSSSS", createTimeStampTZ(0, 0, 0, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTime("45315", "SSSSS", createTimeStampTZ(0, 0, 0, 12, 35, 15, 0), cb); + testCVTStringToFormatDateTimeExpectTime("86399", "SSSSS", createTimeStampTZ(0, 0, 0, 23, 59, 59, 0), cb); - testCVTStringToFormatDateTimeExpectTime("1", "FF2", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 100), cb); - testCVTStringToFormatDateTimeExpectTime("10", "FF2", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 1000), cb); - testCVTStringToFormatDateTimeExpectTime("50", "FF2", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 5000), cb); - testCVTStringToFormatDateTimeExpectTime("99", "FF2", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 9900), cb); + testCVTStringToFormatDateTimeExpectTime("1", "FF1", createTimeStampTZ(0, 0, 0, 0, 0, 0, 0, 1000), cb); + testCVTStringToFormatDateTimeExpectTime("5", "FF1", createTimeStampTZ(0, 0, 0, 0, 0, 0, 0, 5000), cb); + testCVTStringToFormatDateTimeExpectTime("9", "FF1", createTimeStampTZ(0, 0, 0, 0, 0, 0, 0, 9000), cb); - testCVTStringToFormatDateTimeExpectTime("1", "FF3", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 10), cb); - testCVTStringToFormatDateTimeExpectTime("10", "FF3", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 100), cb); - testCVTStringToFormatDateTimeExpectTime("100", "FF3", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 1000), cb); - testCVTStringToFormatDateTimeExpectTime("500", "FF3", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 5000), cb); - testCVTStringToFormatDateTimeExpectTime("999", "FF3", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 9990), cb); + testCVTStringToFormatDateTimeExpectTime("1", "FF2", createTimeStampTZ(0, 0, 0, 0, 0, 0, 0, 100), cb); + testCVTStringToFormatDateTimeExpectTime("10", "FF2", createTimeStampTZ(0, 0, 0, 0, 0, 0, 0, 1000), cb); + testCVTStringToFormatDateTimeExpectTime("50", "FF2", createTimeStampTZ(0, 0, 0, 0, 0, 0, 0, 5000), cb); + testCVTStringToFormatDateTimeExpectTime("99", "FF2", createTimeStampTZ(0, 0, 0, 0, 0, 0, 0, 9900), cb); - testCVTStringToFormatDateTimeExpectTime("1", "FF4", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 1), cb); - testCVTStringToFormatDateTimeExpectTime("10", "FF4", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 10), cb); - testCVTStringToFormatDateTimeExpectTime("100", "FF4", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 100), cb); - testCVTStringToFormatDateTimeExpectTime("1000", "FF4", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 1000), cb); - testCVTStringToFormatDateTimeExpectTime("5000", "FF4", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 5000), cb); - testCVTStringToFormatDateTimeExpectTime("9999", "FF4", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 9999), cb); + testCVTStringToFormatDateTimeExpectTime("1", "FF3", createTimeStampTZ(0, 0, 0, 0, 0, 0, 0, 10), cb); + testCVTStringToFormatDateTimeExpectTime("10", "FF3", createTimeStampTZ(0, 0, 0, 0, 0, 0, 0, 100), cb); + testCVTStringToFormatDateTimeExpectTime("100", "FF3", createTimeStampTZ(0, 0, 0, 0, 0, 0, 0, 1000), cb); + testCVTStringToFormatDateTimeExpectTime("500", "FF3", createTimeStampTZ(0, 0, 0, 0, 0, 0, 0, 5000), cb); + testCVTStringToFormatDateTimeExpectTime("999", "FF3", createTimeStampTZ(0, 0, 0, 0, 0, 0, 0, 9990), cb); - testCVTStringToFormatDateTimeExpectTime("1 PM - 25 - 45 - 200", "HH.MI.SS.FF4", createTimeStampTZ(1, 1, 1, 13, 25, 45, 0, 200), cb); - testCVTStringToFormatDateTimeExpectTime("15:0:15:2", "HH24.MI.SS.FF1", createTimeStampTZ(1, 1, 1, 15, 0, 15, 0, 2000), cb); + testCVTStringToFormatDateTimeExpectTime("1", "FF4", createTimeStampTZ(0, 0, 0, 0, 0, 0, 0, 1), cb); + testCVTStringToFormatDateTimeExpectTime("10", "FF4", createTimeStampTZ(0, 0, 0, 0, 0, 0, 0, 10), cb); + testCVTStringToFormatDateTimeExpectTime("100", "FF4", createTimeStampTZ(0, 0, 0, 0, 0, 0, 0, 100), cb); + testCVTStringToFormatDateTimeExpectTime("1000", "FF4", createTimeStampTZ(0, 0, 0, 0, 0, 0, 0, 1000), cb); + testCVTStringToFormatDateTimeExpectTime("5000", "FF4", createTimeStampTZ(0, 0, 0, 0, 0, 0, 0, 5000), cb); + testCVTStringToFormatDateTimeExpectTime("9999", "FF4", createTimeStampTZ(0, 0, 0, 0, 0, 0, 0, 9999), cb); + + testCVTStringToFormatDateTimeExpectTime("1 P.M. - 25 - 45 - 200", "HH P.M. MI.SS.FF4", createTimeStampTZ(0, 0, 0, 13, 25, 45, 0, 200), cb); + testCVTStringToFormatDateTimeExpectTime("15:0:15:2", "HH24.MI.SS.FF1", createTimeStampTZ(0, 0, 0, 15, 0, 15, 0, 2000), cb); } BOOST_AUTO_TEST_CASE(CVTStringToFormatDateTime_TZ) { - testCVTStringToFormatDateTimeExpectTimeTZ("12:00 2:30", "HH24:MI TZH:TZM", createTimeStampTZ(1, 1, 1, 12, 0, 0, 150, 0), cb); - testCVTStringToFormatDateTimeExpectTimeTZ("12:00 +2:30", "HH24:MI TZH:TZM", createTimeStampTZ(1, 1, 1, 12, 0, 0, 150, 0), cb); - testCVTStringToFormatDateTimeExpectTimeTZ("12:00 -2:30", "HH24:MI TZH:TZM", createTimeStampTZ(1, 1, 1, 12, 0, 0, -150, 0), cb); - testCVTStringToFormatDateTimeExpectTimeTZ("12:00 +0:30", "HH24:MI TZH:TZM", createTimeStampTZ(1, 1, 1, 12, 0, 0, 30, 0), cb); - testCVTStringToFormatDateTimeExpectTimeTZ("12:00 +0:00", "HH24:MI TZH:TZM", createTimeStampTZ(1, 1, 1, 12, 0, 0, 0, 0), cb); + testCVTStringToFormatDateTimeExpectTimeTZ("12:00 2:30", "HH24:MI TZH:TZM", createTimeStampTZ(0, 0, 0, 12, 0, 0, 150, 0), cb); + testCVTStringToFormatDateTimeExpectTimeTZ("12:00 +2:30", "HH24:MI TZH:TZM", createTimeStampTZ(0, 0, 0, 12, 0, 0, 150, 0), cb); + testCVTStringToFormatDateTimeExpectTimeTZ("12:00 -2:30", "HH24:MI TZH:TZM", createTimeStampTZ(0, 0, 0, 12, 0, 0, -150, 0), cb); + testCVTStringToFormatDateTimeExpectTimeTZ("12:00 +0:30", "HH24:MI TZH:TZM", createTimeStampTZ(0, 0, 0, 12, 0, 0, 30, 0), cb); + testCVTStringToFormatDateTimeExpectTimeTZ("12:00 +0:00", "HH24:MI TZH:TZM", createTimeStampTZ(0, 0, 0, 12, 0, 0, 0, 0), cb); } BOOST_AUTO_TEST_CASE(CVTStringToFormatDateTime_SOLID_PATTERNS) { - testCVTStringToFormatDateTimeExpectTime("1 PM - 25 - 45 - 200", "HHMISSFF4", createTimeStampTZ(1, 1, 1, 13, 25, 45, 0, 200), cb); + testCVTStringToFormatDateTimeExpectTime("1 P.M. - 25 - 45 - 200", "HHA.M.MISSFF4", createTimeStampTZ(0, 0, 0, 13, 25, 45, 0, 200), cb); testCVTStringToFormatDateTimeExpectDate("1981-8/13", "YEARMMDD", createTimeStampTZ(1981, 8, 13, 0, 0, 0, 0), cb); } +BOOST_AUTO_TEST_CASE(CVTStringToFormatDateTime_EXCEPTION_CHECK) +{ + testExceptionCvtStringToFormatDateTime("2000.12.11", "WRONG FORMAT", cb); + testExceptionCvtStringToFormatDateTime("2000.12.11", "YYYY.MM.DD SS", cb); + testExceptionCvtStringToFormatDateTime("2000.12", "YYYY.MM.DD", cb); + + testExceptionCvtStringToFormatDateTime("1 A.G.", "HH12 A.M.", cb); + testExceptionCvtStringToFormatDateTime("1 A.G.", "HH12 A.P.", cb); + + testExceptionCvtStringToFormatDateTime("24 12 A.M.", "HH24 HH12 P.M.", cb); + testExceptionCvtStringToFormatDateTime("24 12", "HH24 HH12", cb); + testExceptionCvtStringToFormatDateTime("24 A.M.", "HH24 P.M.", cb); + testExceptionCvtStringToFormatDateTime("12", "HH12", cb); + testExceptionCvtStringToFormatDateTime("A.M.", "P.M.", cb); + testExceptionCvtStringToFormatDateTime("P.M.", "A.M.", cb); + + testExceptionCvtStringToFormatDateTime("1 1 A.M.", "SSSSS HH12 A.M.", cb); + testExceptionCvtStringToFormatDateTime("1 1 A.M.", "SSSSS HH12 P.M.", cb); + testExceptionCvtStringToFormatDateTime("1 1 A.M.", "SSSSS HH A.M.", cb); + testExceptionCvtStringToFormatDateTime("1 1 A.M.", "SSSSS HH P.M.", cb); + testExceptionCvtStringToFormatDateTime("1 1", "SSSSS HH24", cb); + testExceptionCvtStringToFormatDateTime("1 1", "SSSSS MI", cb); + testExceptionCvtStringToFormatDateTime("1 1", "SSSSS SS", cb); + + testExceptionCvtStringToFormatDateTime("30 1", "TZM SS", cb); + testExceptionCvtStringToFormatDateTime("30", "TZM", cb); +} + BOOST_AUTO_TEST_SUITE_END() // FunctionalTest BOOST_AUTO_TEST_SUITE_END() // CVTStringToFormatDateTime diff --git a/src/common/tests/CvtTestUtils.h b/src/common/tests/CvtTestUtils.h index beafaf7055..fc0ea1eb90 100644 --- a/src/common/tests/CvtTestUtils.h +++ b/src/common/tests/CvtTestUtils.h @@ -26,22 +26,38 @@ namespace CvtTestUtils { template static constexpr int sign(T value) { - return (T(0) < value) - (value < T(0)); + return (value >= T(0)) ? 1 : -1; } +static ISC_DATE mockGetLocalDate(int year = 2023) +{ + struct tm time; + memset(&time, 0, sizeof(time)); + time.tm_year = year - 1900; + time.tm_mon = 0; + time.tm_mday = 1; + return NoThrowTimeStamp::encode_date(&time); +} + + +// Pass 0 to year, month and day to use CurrentTimeStamp for them static struct tm initTMStruct(int year, int month, int day) { + struct tm currentTime; + NoThrowTimeStamp::decode_date(mockGetLocalDate(), ¤tTime); + struct tm times; memset(×, 0, sizeof(struct tm)); - times.tm_year = year - 1900; - times.tm_mon = month - 1; - times.tm_mday = day; + times.tm_year = year > 0 ? year - 1900 : currentTime.tm_year; + times.tm_mon = month > 0 ? month - 1 : currentTime.tm_mon; + times.tm_mday = day > 0 ? day : currentTime.tm_mday; mktime(×); return times; } +// Pass 0 to year, month and day to use CurrentTimeStamp for them static ISC_DATE createDate(int year, int month, int day) { struct tm times = initTMStruct(year, month, day); @@ -53,6 +69,7 @@ static ISC_TIME createTime(int hours, int minutes, int seconds, int fractions = return NoThrowTimeStamp::encode_time(hours, minutes, seconds, fractions); } +// Pass 0 to year, month and day to use CurrentTimeStamp for them static ISC_TIMESTAMP createTimeStamp(int year, int month, int day, int hours, int minutes, int seconds, int fractions = 0) { struct tm times = initTMStruct(year, month, day); @@ -63,6 +80,7 @@ static ISC_TIMESTAMP createTimeStamp(int year, int month, int day, int hours, in return NoThrowTimeStamp::encode_timestamp(×, fractions); } +// Pass 0 to year, month and day to use CurrentTimeStamp for them static ISC_TIMESTAMP_TZ createTimeStampTZ(int year, int month, int day, int hours, int minutes, int seconds, int offsetInMinutes, int fractions = 0) { @@ -84,10 +102,11 @@ static ISC_TIME_TZ createTimeTZ(int hours, int minutes, int seconds, int offsetI } -class CVTCallback : public Firebird::Callbacks +class MockCallback : public Firebird::Callbacks { public: - explicit CVTCallback(ErrorFunction aErr) : Callbacks(aErr) + explicit MockCallback(ErrorFunction aErr, std::function mockGetLocalDateFunc) + : Callbacks(aErr), m_mockGetLocalDateFunc(mockGetLocalDateFunc) {} public: @@ -97,10 +116,18 @@ public: void validateData(Firebird::CharSet* toCharset, SLONG length, const UCHAR* q) override { } ULONG validateLength(Firebird::CharSet* charSet, CHARSET_ID charSetId, ULONG length, const UCHAR* start, const USHORT size) override { return 0; } - SLONG getLocalDate() override { return 0; } + + SLONG getLocalDate() override + { + return m_mockGetLocalDateFunc(); + } + ISC_TIMESTAMP getCurrentGmtTimeStamp() override { ISC_TIMESTAMP ts; return ts; } USHORT getSessionTimeZone() override { return 1439; } // 1439 is ONE_DAY, so we have no offset void isVersion4(bool& v4) override { } + +private: + std::function m_mockGetLocalDateFunc; }; diff --git a/src/dsql/ExprNodes.cpp b/src/dsql/ExprNodes.cpp index 2933e87eb0..67a200fd27 100644 --- a/src/dsql/ExprNodes.cpp +++ b/src/dsql/ExprNodes.cpp @@ -3815,38 +3815,38 @@ dsc* CastNode::perform(thread_db* tdbb, impure_value* impure, dsc* value, { case dtype_sql_time: { - ISC_TIMESTAMP_TZ timestampTZ = CVT_string_to_format_datetime(value, format, &EngineCallbacks::instance, - expect_sql_time); + ISC_TIMESTAMP_TZ timestampTZ = CVT_string_to_format_datetime(value, format, expect_sql_time, + &EngineCallbacks::instance); *(ISC_TIME*) impure->vlu_desc.dsc_address = timestampTZ.utc_timestamp.timestamp_time; break; } case dtype_sql_date: { - ISC_TIMESTAMP_TZ timestampTZ = CVT_string_to_format_datetime(value, format, &EngineCallbacks::instance, - expect_sql_date); + ISC_TIMESTAMP_TZ timestampTZ = CVT_string_to_format_datetime(value, format, expect_sql_date, + &EngineCallbacks::instance); *(ISC_DATE*) impure->vlu_desc.dsc_address = timestampTZ.utc_timestamp.timestamp_date; break; } case dtype_timestamp: { - ISC_TIMESTAMP_TZ timestampTZ = CVT_string_to_format_datetime(value, format, &EngineCallbacks::instance, - expect_timestamp); + ISC_TIMESTAMP_TZ timestampTZ = CVT_string_to_format_datetime(value, format, expect_timestamp, + &EngineCallbacks::instance); *(ISC_TIMESTAMP*) impure->vlu_desc.dsc_address = timestampTZ.utc_timestamp; break; } case dtype_sql_time_tz: case dtype_ex_time_tz: { - ISC_TIMESTAMP_TZ timestampTZ = CVT_string_to_format_datetime(value, format, &EngineCallbacks::instance, - expect_sql_time_tz); + ISC_TIMESTAMP_TZ timestampTZ = CVT_string_to_format_datetime(value, format, expect_sql_time_tz, + &EngineCallbacks::instance); *(ISC_TIME_TZ*) impure->vlu_desc.dsc_address = TimeZoneUtil::timeStampTzToTimeTz(timestampTZ); break; } case dtype_timestamp_tz: case dtype_ex_timestamp_tz: { - ISC_TIMESTAMP_TZ timestampTZ = CVT_string_to_format_datetime(value, format, &EngineCallbacks::instance, - expect_timestamp_tz); + ISC_TIMESTAMP_TZ timestampTZ = CVT_string_to_format_datetime(value, format, expect_timestamp_tz, + &EngineCallbacks::instance); *(ISC_TIMESTAMP_TZ*) impure->vlu_desc.dsc_address = timestampTZ; break; } diff --git a/src/include/firebird/impl/msg/jrd.h b/src/include/firebird/impl/msg/jrd.h index 0c12476bf8..346adcfb64 100644 --- a/src/include/firebird/impl/msg/jrd.h +++ b/src/include/firebird/impl/msg/jrd.h @@ -978,6 +978,9 @@ FB_IMPL_MSG(JRD, 975, invalid_data_type_for_date_format, -901, "HY", "000", "It FB_IMPL_MSG(JRD, 976, incompatible_date_format_with_current_date_type, -901, "HY", "000", "Cannot use \"@1\" format with current date type") FB_IMPL_MSG(JRD, 977, value_for_pattern_is_out_of_range, -901, "HY", "000", "Value for @1 pattern is out of range [@2, @3]") FB_IMPL_MSG(JRD, 978, month_name_mismatch, -901, "HY", "000", "@1 is not MONTH") -FB_IMPL_MSG(JRD, 979, incorrect_hours_period, -901, "HY", "000", "@1 is incorrect period for 12H, it should be AM or PM") +FB_IMPL_MSG(JRD, 979, incorrect_hours_period, -901, "HY", "000", "@1 is incorrect period for 12H, it should be A.M. or P.M.") FB_IMPL_MSG(JRD, 980, data_for_format_is_exhausted, -901, "HY", "000", "All data has been read, but format pattern wants more. Unfilled patterns: \"@1\"") FB_IMPL_MSG(JRD, 981, trailing_part_of_string, -901, "HY", "000", "There is a trailing part of input string that does not fit into FORMAT: \"@1\"") +FB_IMPL_MSG(JRD, 982, pattern_cant_be_used_without_other_pattern, -901, "HY", "000", "@1 can't be used without @2") +FB_IMPL_MSG(JRD, 983, pattern_cant_be_used_without_other_pattern_and_vice_versa, -901, "HY", "000", "@1 can't be used without @2 and vice versa") +FB_IMPL_MSG(JRD, 984, incompatible_format_patterns, -901, "HY", "000", "@1 incompatible with @2") diff --git a/src/include/gen/Firebird.pas b/src/include/gen/Firebird.pas index bfde9bc087..2644e03fbb 100644 --- a/src/include/gen/Firebird.pas +++ b/src/include/gen/Firebird.pas @@ -5724,6 +5724,9 @@ const isc_incorrect_hours_period = 335545299; isc_data_for_format_is_exhausted = 335545300; isc_trailing_part_of_string = 335545301; + isc_pattern_cant_be_used_without_other_pattern = 335545302; + isc_pattern_cant_be_used_without_other_pattern_and_vice_versa = 335545303; + isc_incompatible_format_patterns = 335545304; isc_gfix_db_name = 335740929; isc_gfix_invalid_sw = 335740930; isc_gfix_incmp_sw = 335740932; diff --git a/src/jrd/blp.h b/src/jrd/blp.h index 23f2a31d76..085cb30e78 100644 --- a/src/jrd/blp.h +++ b/src/jrd/blp.h @@ -251,13 +251,15 @@ static const struct // New BLR in FB5.0 {"dcl_local_table", dcl_local_table}, {"local_table_truncate", one_word}, - {"local_table_id", local_table}, + {"local_table_id", local_table}, // 220 {"outer_map", outer_map}, {NULL, NULL}, // blr_json_function {"skip_locked", zero}, + // New BLR in FB6.0 {"invoke_function", invoke_function}, {"invoke_procedure", invsel_procedure}, {"select_procedure", invsel_procedure}, {"blr_default_arg", zero}, + {"cast_format", cast_format}, {0, 0} }; diff --git a/src/yvalve/gds.cpp b/src/yvalve/gds.cpp index 57f2b0f726..c19ee28f53 100644 --- a/src/yvalve/gds.cpp +++ b/src/yvalve/gds.cpp @@ -419,7 +419,8 @@ static const UCHAR outer_map[] = { op_outer_map, 0 }, in_list[] = { op_line, op_verb, op_indent, op_word, op_line, op_args, 0}, invoke_function[] = { op_invoke_function, 0 }, - invsel_procedure[] = { op_invsel_procedure, 0 }; + invsel_procedure[] = { op_invsel_procedure, 0 }, + cast_format[] = { op_byte, op_literal, op_dtype, op_line, op_verb, 0 }; #include "../jrd/blp.h"