Skip to content

Commit

Permalink
Add ability to write patterns without separators
Browse files Browse the repository at this point in the history
  • Loading branch information
TreeHunter9 committed Jul 19, 2023
1 parent d942ed0 commit 7be7e1c
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 71 deletions.
11 changes: 10 additions & 1 deletion doc/README.cast.format.md
Expand Up @@ -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:
Expand Down Expand Up @@ -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;
Expand Down
167 changes: 97 additions & 70 deletions src/common/cvt.cpp
Expand Up @@ -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 };
Expand Down Expand Up @@ -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<decltype(invalidPatternException)>(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;
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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<decltype(invalidPatternException)>(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++)
Expand Down
16 changes: 16 additions & 0 deletions src/common/tests/CvtTest.cpp
Expand Up @@ -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);
Expand Down Expand Up @@ -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

Expand Down

0 comments on commit 7be7e1c

Please sign in to comment.