diff --git a/doc/README.cast.format.md b/doc/README.cast.format.md index b000699e285..d4cf38ce480 100644 --- a/doc/README.cast.format.md +++ b/doc/README.cast.format.md @@ -42,6 +42,14 @@ The dividers are: | 'space' | | - | +Patterns can be used without any dividers: +``` +SELECT CAST(CURRENT_TIMESTAMP AS VARCHAR(50) FORMAT 'YEARMMDD HH24MISS') FROM RDB$DATABASE; +========================= +20230719 161757 +``` +However, be careful with patterns like `DDDDD`, it will be interpreted as `DDD` + `DD`. + It is possible to insert raw text into a format string with `""`: `... FORMAT '"Today is" DAY'` - Today is MONDAY. To add `"` in output raw string use `\"` (to print `\` use `\\`). Also the format is case-insensitive, so `YYYY-MM` == `yyyy-mm`. Example: @@ -76,7 +84,8 @@ The following flags are currently implemented for string to datetime conversion: | TZH | Time zone in Hours (-14 - 14) | | TZM | Time zone in Minutes (0 - 59) | -The dividers are the same as for datetime to string conversion. +Dividers are the same as for datetime to string conversion and can also be omitted. + 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/cvt.cpp b/src/common/cvt.cpp index 944cc1b56ee..28cfa9cf6cf 100644 --- a/src/common/cvt.cpp +++ b/src/common/cvt.cpp @@ -153,6 +153,17 @@ enum class ExpectedDateType 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" +}; + //#ifndef WORDS_BIGENDIAN //static const SQUAD quad_min_int = { 0, SLONG_MIN }; @@ -1489,66 +1500,74 @@ string CVT_datetime_to_format_string(const dsc* desc, const string& format, Call } } - int offset = 0; - char separator = '\0'; + string result; + int formatOffset = 0; + std::string_view pattern; std::string_view previousPattern; - string result = ""; for (int i = 0; i < formatUpper.length(); i++) { const char symbol = formatUpper[i]; - if (separator == '\"') + if (is_separator(symbol)) { - int rawStringLength = formatUpper.length() - i; - string rawString(rawStringLength, '\0'); - for (int j = 0; j < rawStringLength; j++, i++) + if (formatOffset != i) { - if (formatUpper[i] == '\"') - break; - else if (formatUpper[i] == '\\') - rawString[j] = formatUpper[++i]; - else - rawString[j] = formatUpper[i]; + result += datetime_to_format_string_pattern_matcher(desc, pattern, previousPattern, times, + fractions, cb, invalidPatternException); + previousPattern = pattern; + } + if (symbol == '\"') + { + i++; + int rawStringLength = formatUpper.length() - i; + string rawString(rawStringLength, '\0'); + for (int j = 0; j < rawStringLength; j++, i++) + { + if (formatUpper[i] == '\"') + break; + else if (formatUpper[i] == '\\') + rawString[j] = formatUpper[++i]; + else + rawString[j] = formatUpper[i]; + } + rawString.recalculate_length(); + result += rawString; } - rawString.recalculate_length(); - result += rawString; + else + result += symbol; - offset = i + 1; - separator = '\0'; + formatOffset = i + 1; continue; } - if (is_separator(symbol)) - separator = symbol; - else + 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++) { - if (i != formatUpper.length() - 1) - continue; - ++i; + if (!strncmp(TO_DATETIME_PATTERNS[j], pattern.data(), pattern.length())) + { + isFound = true; + if (i == formatUpper.length() - 1) + { + result += datetime_to_format_string_pattern_matcher(desc, pattern, previousPattern, times, + fractions, cb, invalidPatternException); + } + break; + } } - - int patternLength = i - offset; - if (patternLength == 0) - { - if (separator == '\"') - continue; - - result += separator; - ++offset; + if (isFound) continue; - } - std::string_view pattern(formatUpper.c_str() + offset, patternLength); - result += datetime_to_format_string_pattern_matcher(desc, pattern, - previousPattern, times, fractions, cb, invalidPatternException); + if (pattern.length() <= 1) + invalidPatternException(pattern, cb); + pattern = pattern.substr(0, pattern.length() - 1); + result += datetime_to_format_string_pattern_matcher(desc, pattern, previousPattern, times, + fractions, cb, invalidPatternException); previousPattern = pattern; - - if (i < formatUpper.length() && separator != '\"') - result += separator; - - offset = i + 1; + formatOffset = i; + i--; } return result; @@ -1806,18 +1825,12 @@ static void string_to_format_datetime_pattern_matcher(std::string_view pattern, std::string_view period = parse_string_to_get_first_word(str, strLength, strOffset, 2); if (period == "AM") { - if (hours == 12) - outTimes.tm_hour = 0; - else - outTimes.tm_hour = hours; + outTimes.tm_hour = hours == 12 ? 0 : hours; return; } else if (period == "PM") { - if (hours == 12) - outTimes.tm_hour = hours; - else - outTimes.tm_hour = 12 + hours; + outTimes.tm_hour = hours == 12 ? hours : 12 + hours; return; } @@ -1955,45 +1968,59 @@ ISC_TIMESTAMP_TZ CVT_string_to_format_datetime(const dsc* desc, const Firebird:: int formatOffset = 0; int stringOffset = 0; + std::string_view pattern; std::string_view previousPattern; - for (int i = 0; i < format.length(); i++) + for (int i = 0; i < formatUpper.length(); i++) { - const char symbol = format[i]; + const char symbol = formatUpper[i]; - if (!is_separator(symbol)) + if (is_separator(symbol)) { - if (i + 1 != format.length()) - continue; - i++; - } - - if (stringOffset >= stringLength) - cb->err(Arg::Gds(isc_data_for_format_is_exhausted) << string(formatUpper.c_str() + formatOffset)); + if (formatOffset != i) + { + string_to_format_datetime_pattern_matcher(pattern, previousPattern, stringUpper.c_str(), + stringLength, stringOffset, times, fractions, timezoneOffsetInMinutes, cb, invalidPatternException); + previousPattern = pattern; + } - const int patternLength = i - formatOffset; - if (patternLength <= 0) - { formatOffset = i + 1; continue; } - std::string_view pattern(formatUpper.c_str() + formatOffset, patternLength); - - string patternResult; - for (; stringOffset < stringLength; stringOffset++) { if (!is_separator(stringUpper[stringOffset])) break; } - string_to_format_datetime_pattern_matcher(pattern, previousPattern, - stringUpper.c_str(), stringLength, stringOffset, times, fractions, timezoneOffsetInMinutes, - cb, invalidPatternException); + 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++) + { + if (!strncmp(TO_STRING_PATTERNS[j], pattern.data(), pattern.length())) + { + isFound = true; + if (i == formatUpper.length() - 1) + { + string_to_format_datetime_pattern_matcher(pattern, previousPattern, stringUpper.c_str(), + stringLength, stringOffset, times, fractions, timezoneOffsetInMinutes, cb, invalidPatternException); + } + break; + } + } + if (isFound) + continue; + + if (pattern.length() <= 1) + invalidPatternException(pattern, cb); + pattern = pattern.substr(0, pattern.length() - 1); + string_to_format_datetime_pattern_matcher(pattern, previousPattern, stringUpper.c_str(), + stringLength, stringOffset, times, fractions, timezoneOffsetInMinutes, cb, invalidPatternException); previousPattern = pattern; - formatOffset = i + 1; + formatOffset = i; + i--; } for (; stringOffset < stringLength; stringOffset++) diff --git a/src/common/tests/CvtTest.cpp b/src/common/tests/CvtTest.cpp index 697525f0a36..c9a6b44e457 100644 --- a/src/common/tests/CvtTest.cpp +++ b/src/common/tests/CvtTest.cpp @@ -119,6 +119,16 @@ BOOST_AUTO_TEST_CASE(CVTDatetimeToFormatStringTest_TIMESTAMP_TZ) testCVTDatetimeToFormatString(createTimeStampTZ(1982, 4, 21, 0, 0, 0, -160), "TZH MI TZM", "-02 20 -40", cb); } +BOOST_AUTO_TEST_CASE(CVTDatetimeToFormatStringTest_SOLID_PATTERNS) +{ + ISC_TIMESTAMP_TZ timestampTZ = createTimeStampTZ(1982, 4, 21, 1, 34, 15, 0, 500); + + 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); +} + BOOST_AUTO_TEST_CASE(CVTDatetimeToFormatStringTest_RAW_TEXT) { testCVTDatetimeToFormatString(createDate(1981, 7, 12), "YYYY-\"RaW TeXt\"-MON", "1981-RaW TeXt-Jul", cb); @@ -292,6 +302,12 @@ BOOST_AUTO_TEST_CASE(CVTStringToFormatDateTime_TZ) testCVTStringToFormatDateTime("12:00 +0:00", "HH24:MI TZH:TZM", createTimeStampTZ(1, 1, 1, 12, 0, 0, 0, 0), cb); } +BOOST_AUTO_TEST_CASE(CVTStringToFormatDateTime_SOLID_PATTERNS) +{ + testCVTStringToFormatDateTime("1 PM - 25 - 45 - 200", "HHMISSFF4", createTimeStampTZ(1, 1, 1, 13, 25, 45, 0, 200), cb); + testCVTStringToFormatDateTime("1981-8/13", "YEARMMDD", createTimeStampTZ(1981, 8, 13, 0, 0, 0, 0), cb); +} + BOOST_AUTO_TEST_SUITE_END() // FunctionalTest BOOST_AUTO_TEST_SUITE_END() // CVTStringToFormatDateTime