8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-22 14:43:03 +01:00

Add FORMAT clause to convert datetime types to string and vice versa #2388 (#7629)

* Add FORMAT clause to convert datetime types to string and vice versa

* Add tests for FORMAT clause

* Fixes after review

* Change TZD to TZR

* Change inline variables back to static

* Add README documentation

* Add ability to use " in raw string and ...

Use session timezone if timezone is not specified.
Add ability to use + sign in timezone offset.
Add truncating string exception.

* Move util methods from BOOST_AUTO_TEST_SUITE

* Switch back to inline variables

* Consider charset in the format string

* Add ability to write patterns without separators

* Use printf to add extra zeros

Also add extra zeros to the year patterns.

* Replace template exception with a plain function

* Clean code after review

* Fix bug with TZH:TZM when TZH is 0

* Add TZR to STRING to DATE

---------

Co-authored-by: Artyom Ivanov <artyom.ivanov@red-soft.ru>
This commit is contained in:
TreeHunter 2023-10-24 13:01:58 +03:00 committed by GitHub
parent 1fe1abd366
commit 897ac0c650
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1947 additions and 133 deletions

95
doc/README.cast.format.md Normal file
View File

@ -0,0 +1,95 @@
## 1. DATETIME TO STRING
The following flags are currently implemented for datetime to string conversion:
| Format Pattern | Description |
| -------------- | ----------- |
| YEAR | Year (1 - 9999) |
| YYYY | Last 4 digits of Year (0001 - 9999) |
| YYY | Last 3 digits of Year (000 - 999) |
| YY | Last 2 digits of Year (00 - 99) |
| Y | Last 1 digits of Year (0 - 9) |
| Q | Quarter of the Year (1 - 4) |
| MM | Month (01 - 12) |
| MON | Short Month name (Apr) |
| MONTH | Full Month name (APRIL) |
| RM | Roman representation of the Month (I - XII) |
| WW | Week of the Year (01 - 53) |
| W | Week of the Month (1 - 5) |
| D | Day of the Week (1 - 7) |
| DAY | Full name of the Day (MONDAY) |
| DD | Day of the Month (01 - 31) |
| 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) |
| 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 |
| TZH | Time zone in Hours (-14 - 14) |
| TZM | Time zone in Minutes (00 - 59) |
| TZR | Time zone Name |
The dividers are:
| Dividers |
| ------------- |
| . |
| / |
| , |
| ; |
| : |
| '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:
```
SELECT CAST(CURRENT_TIMESTAMP AS VARCHAR(45) FORMAT 'DD.MM.YEAR HH24:MI:SS "is" J "Julian day"') FROM RDB$DATABASE;
=========================
14.6.2023 15:41:29 is 2460110 Julian day
```
## 2. STRING TO DATETIME
The following flags are currently implemented for string to datetime conversion:
| Format Pattern | Description |
| ------------- | ------------- |
| YEAR | Year |
| YYYY | Last 4 digits of Year |
| YYY | Last 3 digits of Year |
| YY | Last 2 digits of Year |
| Y | Last 1 digits of Year |
| 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) |
| 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 |
| 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.
Example:
```
SELECT CAST('2000.12.08 12:35:30.5000' AS TIMESTAMP FORMAT 'YEAR.MM.DD HH24:MI:SS.FF4') FROM RDB$DATABASE;
=====================
2000-12-08 12:35:30.5000
```

View File

@ -222,6 +222,7 @@ PARSER_TOKEN(TOK_FLOOR, "FLOOR", true)
PARSER_TOKEN(TOK_FOLLOWING, "FOLLOWING", true)
PARSER_TOKEN(TOK_FOR, "FOR", false)
PARSER_TOKEN(TOK_FOREIGN, "FOREIGN", false)
PARSER_TOKEN(TOK_FORMAT, "FORMAT", true)
PARSER_TOKEN(TOK_FREE_IT, "FREE_IT", true)
PARSER_TOKEN(TOK_FROM, "FROM", false)
PARSER_TOKEN(TOK_FULL, "FULL", false)

View File

@ -80,7 +80,6 @@ namespace
static const TimeZoneDesc* getDesc(USHORT timeZone);
static inline bool isOffset(USHORT timeZone);
static USHORT makeFromOffset(int sign, unsigned tzh, unsigned tzm);
static inline SSHORT offsetZoneToDisplacement(USHORT timeZone);
static inline USHORT displacementToOffsetZone(SSHORT displacement);
static int parseNumber(const char*& p, const char* end);
@ -634,6 +633,20 @@ void TimeZoneUtil::extractOffset(const ISC_TIME_TZ& timeTz, SSHORT* offset)
extractOffset(tsTz, offset);
}
// Makes a time zone id from offsets.
USHORT TimeZoneUtil::makeFromOffset(int sign, unsigned tzh, unsigned tzm)
{
if (!TimeZoneUtil::isValidOffset(sign, tzh, tzm))
{
string str;
str.printf("%s%02u:%02u", (sign == -1 ? "-" : "+"), tzh, tzm);
status_exception::raise(Arg::Gds(isc_invalid_timezone_offset) << str);
}
return (USHORT)displacementToOffsetZone((tzh * 60 + tzm) * sign);
}
// Converts a time from local to UTC.
void TimeZoneUtil::localTimeToUtc(ISC_TIME& time, ISC_USHORT timeZone)
{
@ -1156,19 +1169,6 @@ static inline bool isOffset(USHORT timeZone)
return timeZone <= ONE_DAY * 2;
}
// Makes a time zone id from offsets.
static USHORT makeFromOffset(int sign, unsigned tzh, unsigned tzm)
{
if (!TimeZoneUtil::isValidOffset(sign, tzh, tzm))
{
string str;
str.printf("%s%02u:%02u", (sign == -1 ? "-" : "+"), tzh, tzm);
status_exception::raise(Arg::Gds(isc_invalid_timezone_offset) << str);
}
return (USHORT)displacementToOffsetZone((tzh * 60 + tzm) * sign);
}
// Gets the displacement from a offset-based time zone id.
static inline SSHORT offsetZoneToDisplacement(USHORT timeZone)
{

View File

@ -97,6 +97,8 @@ public:
static void extractOffset(const ISC_TIMESTAMP_TZ& timeStampTz, SSHORT* offset);
static void extractOffset(const ISC_TIME_TZ& timeTz, SSHORT* offset);
static USHORT makeFromOffset(int sign, unsigned tzh, unsigned tzm);
static void localTimeToUtc(ISC_TIME& time, ISC_USHORT timeZone);
static void localTimeToUtc(ISC_TIME_TZ& timeTz);

View File

@ -337,6 +337,80 @@ void NoThrowTimeStamp::round_time(ISC_TIME &ntime, const int precision)
ntime -= (ntime % period);
}
int NoThrowTimeStamp::convertGregorianDateToWeekDate(const struct tm& times)
{
// Algorithm for Converting Gregorian Dates to ISO 8601 Week Date by Rick McCarty, 1999
// http://personal.ecu.edu/mccartyr/ISOwdALG.txt
const int y = times.tm_year + 1900;
const int dayOfYearNumber = times.tm_yday + 1;
// Find the jan1Weekday for y (Monday=1, Sunday=7)
const int yy = (y - 1) % 100;
const int c = (y - 1) - yy;
const int g = yy + yy / 4;
const int jan1Weekday = 1 + (((((c / 100) % 4) * 5) + g) % 7);
// Find the weekday for y m d
const int h = dayOfYearNumber + (jan1Weekday - 1);
const int weekday = 1 + ((h - 1) % 7);
// Find if y m d falls in yearNumber y-1, weekNumber 52 or 53
int yearNumber, weekNumber;
if ((dayOfYearNumber <= (8 - jan1Weekday)) && (jan1Weekday > 4))
{
yearNumber = y - 1;
weekNumber = ((jan1Weekday == 5) || ((jan1Weekday == 6) &&
isLeapYear(yearNumber))) ? 53 : 52;
}
else
{
yearNumber = y;
// Find if y m d falls in yearNumber y+1, weekNumber 1
int i = isLeapYear(y) ? 366 : 365;
if ((i - dayOfYearNumber) < (4 - weekday))
{
yearNumber = y + 1;
weekNumber = 1;
}
}
// Find if y m d falls in yearNumber y, weekNumber 1 through 53
if (yearNumber == y)
{
int j = dayOfYearNumber + (7 - weekday) + (jan1Weekday - 1);
weekNumber = j / 7;
if (jan1Weekday > 4)
weekNumber--;
}
return weekNumber;
}
int NoThrowTimeStamp::convertGregorianDateToJulianDate(int year, int month, int day)
{
int jdn = (1461 * (year + 4800 + (month - 14)/12))/4 + (367 * (month - 2 - 12 * ((month - 14)/12)))
/ 12 - (3 * ((year + 4900 + (month - 14)/12)/100))/4 + day - 32075;
return jdn;
}
void NoThrowTimeStamp::convertJulianDateToGregorianDate(int jdn, int& outYear, int& outMonth, int& outDay)
{
int a = jdn + 32044;
int b = (4 * a +3 ) / 146097;
int c = a - (146097 * b) / 4;
int d = (4 * c + 3) / 1461;
int e = c - (1461 * d) / 4;
int m = (5 * e + 2) / 153;
outDay = e - (153 * m + 2) / 5 + 1;
outMonth = m + 3 - 12 * (m / 10);
outYear = 100 * b + d - 4800 + (m / 10);
}
// Encode timestamp from UNIX datetime structure
void NoThrowTimeStamp::encode(const struct tm* times, int fractions)
{

View File

@ -168,6 +168,10 @@ public:
static void add10msec(ISC_TIMESTAMP* v, SINT64 msec, SINT64 multiplier);
static void round_time(ISC_TIME& ntime, const int precision);
static int convertGregorianDateToWeekDate(const struct tm& times);
static int convertGregorianDateToJulianDate(int year, int month, int day);
static void convertJulianDateToGregorianDate(int jdn, int& outYear, int& outMonth, int& outDay);
static inline bool isLeapYear(const int year) noexcept
{
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);

View File

@ -971,7 +971,7 @@ const int HIGH_WORD = 0;
#endif
#endif
static const TEXT FB_SHORT_MONTHS[][4] =
inline const TEXT FB_SHORT_MONTHS[][4] =
{
"Jan", "Feb", "Mar",
"Apr", "May", "Jun",
@ -980,7 +980,7 @@ static const TEXT FB_SHORT_MONTHS[][4] =
"\0"
};
static const TEXT* const FB_LONG_MONTHS_UPPER[] =
inline const TEXT* const FB_LONG_MONTHS_UPPER[] =
{
"JANUARY",
"FEBRUARY",
@ -997,6 +997,32 @@ static const TEXT* const FB_LONG_MONTHS_UPPER[] =
0
};
// Starts with SUNDAY cuz tm.tm_wday starts with it
inline const TEXT FB_SHORT_DAYS[][4] =
{
"Sun",
"Mon",
"Tue",
"Wed",
"Thu",
"Fri",
"Sat",
"\0"
};
// Starts with SUNDAY cuz tm.tm_wday starts with it
inline const TEXT* const FB_LONG_DAYS_UPPER[] =
{
"SUNDAY",
"MONDAY",
"TUESDAY",
"WEDNESDAY",
"THURSDAY",
"FRIDAY",
"SATURDAY",
"\0"
};
const FB_SIZE_T FB_MAX_SIZEOF = ~FB_SIZE_T(0); // Assume FB_SIZE_T is unsigned
inline FB_SIZE_T fb_strlen(const char* str)

File diff suppressed because it is too large Load Diff

View File

@ -108,5 +108,7 @@ SQUAD CVT_get_quad(const dsc*, SSHORT, Firebird::DecimalStatus, ErrorFunction);
void CVT_string_to_datetime(const dsc*, ISC_TIMESTAMP_TZ*, bool*, const Firebird::EXPECT_DATETIME,
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);
#endif //COMMON_CVT_H

View File

@ -0,0 +1,321 @@
#include "boost/test/unit_test.hpp"
#include "../common/tests/CvtTestUtils.h"
#include "../jrd/cvt_proto.h"
using namespace Firebird;
using namespace Jrd;
using namespace CvtTestUtils;
BOOST_AUTO_TEST_SUITE(CVTSuite)
BOOST_AUTO_TEST_SUITE(CVTDatetimeFormat)
static void errFunc(const Firebird::Arg::StatusVector& v)
{
v.raise();
}
CVTCallback cb(errFunc);
BOOST_AUTO_TEST_SUITE(CVTDatetimeToFormatString)
template<typename T>
static void testCVTDatetimeToFormatString(T date, const string& format, const string& expected, Callbacks& cb)
{
dsc desc;
desc.dsc_dtype = getDSCTypeFromDateType<T>();
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_AUTO_TEST_SUITE(FunctionalTest)
BOOST_AUTO_TEST_CASE(CVTDatetimeToFormatStringTest_DATE)
{
testCVTDatetimeToFormatString(createDate(1, 1, 1), "YEAR.YYYY.YYY.YY.Y", "1.0001.001.01.1", cb);
testCVTDatetimeToFormatString(createDate(1234, 1, 1), "YEAR.YYYY.YYY.YY.Y", "1234.1234.234.34.4", cb);
testCVTDatetimeToFormatString(createDate(9999, 1, 1), "YEAR.YYYY.YYY.YY.Y", "9999.9999.999.99.9", cb);
testCVTDatetimeToFormatString(createDate(1, 1, 1), "YEAR.YYYY.YYY.YY.Y", "1.0001.001.01.1", cb);
testCVTDatetimeToFormatString(createDate(1234, 1, 1), "YEAR.YYYY.YYY.YY.Y", "1234.1234.234.34.4", cb);
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, 4, 1), "Q", "2", cb);
testCVTDatetimeToFormatString(createDate(1, 7, 1), "Q", "3", cb);
testCVTDatetimeToFormatString(createDate(1, 10, 1), "Q", "4", 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);
testCVTDatetimeToFormatString(createDate(1, 12, 1), "MM,RM.MON:MONTH", "12,XII.Dec:DECEMBER", cb);
testCVTDatetimeToFormatString(createDate(1, 1, 1), "WW/W", "01/1", cb);
testCVTDatetimeToFormatString(createDate(1, 6, 15), "WW-W", "24-3", cb);
testCVTDatetimeToFormatString(createDate(1, 12, 30), "WW.W", "52.5", 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);
testCVTDatetimeToFormatString(createDate(1, 1, 1), "DDD", "001", cb);
testCVTDatetimeToFormatString(createDate(1, 1, 12), "DDD", "012", cb);
testCVTDatetimeToFormatString(createDate(1, 6, 15), "DDD", "166", cb);
testCVTDatetimeToFormatString(createDate(1, 12, 31), "DDD", "365", cb);
testCVTDatetimeToFormatString(createDate(1, 1, 1), "J", "1721426", cb);
testCVTDatetimeToFormatString(createDate(2000, 12, 8), "J", "2451887", cb);
testCVTDatetimeToFormatString(createDate(9999, 12, 31), "J", "5373484", cb);
}
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, 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);
testCVTDatetimeToFormatString(createTime(0, 0, 0, 9999), "FF1.FF2/FF3;FF4:FF5-FF6,FF7-FF8 FF9", "9.99/999;9999:99990-999900,9999000-99990000 999900000", cb);
}
BOOST_AUTO_TEST_CASE(CVTDatetimeToFormatStringTest_TIMESTAMP)
{
ISC_TIMESTAMP timestamp = createTimeStamp(1982, 4, 21, 1, 34, 15, 2500);
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);
}
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, 160), "HH24:MI-TZH:TZM", "18:15-+02:40", cb);
testCVTDatetimeToFormatString(createTimeTZ(15, 35, 59, -160), "HH24:MI TZH:TZM", "12:55 -02:40", cb);
testCVTDatetimeToFormatString(createTimeTZ(0, 0, 0, 160), "TZM:TZH", "+40:02", cb);
testCVTDatetimeToFormatString(createTimeTZ(0, 0, 0, 160), "TZH MI TZM", "+02 40 +40", cb);
testCVTDatetimeToFormatString(createTimeTZ(0, 0, 0, -160), "TZH MI TZM", "-02 20 -40", cb);
}
BOOST_AUTO_TEST_CASE(CVTDatetimeToFormatStringTest_TIMESTAMP_TZ)
{
ISC_TIMESTAMP_TZ timestampTZ = createTimeStampTZ(1982, 4, 21, 1, 34, 15, 0, 500);
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(createTimeStampTZ(1982, 4, 21, 1, 34, 15, 70), "HH24:MI-TZH:TZM", "02:44-+01:10", cb);
testCVTDatetimeToFormatString(createTimeStampTZ(1982, 4, 21, 1, 34, 15, -70), "HH24:MI TZH:TZM", "00:24 -01:10", cb);
testCVTDatetimeToFormatString(createTimeStampTZ(1982, 4, 21, 0, 0, 0, 160), "TZM:TZH", "+40:02", cb);
testCVTDatetimeToFormatString(createTimeStampTZ(1982, 4, 21, 0, 0, 0, 160), "TZH MI TZM", "+02 40 +40", cb);
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);
testCVTDatetimeToFormatString(createDate(1981, 7, 12), "YYYY-\"Raw Text with \\\"Quotes\\\"\"-MON", "1981-Raw Text with \"Quotes\"-Jul", cb);
testCVTDatetimeToFormatString(createDate(1981, 7, 12), "YYYY-\"\\\\\\\"\\\\BS\\\\\\\"\\\\\"-YYYY", "1981-\\\"\\BS\\\"\\-1981", cb);
testCVTDatetimeToFormatString(createDate(1981, 7, 12), "\"Test1\"-Y\"Test2\"", "Test1-1Test2", cb);
testCVTDatetimeToFormatString(createDate(1981, 7, 12), "\"\"-Y\"Test2\"", "-1Test2", cb);
testCVTDatetimeToFormatString(createDate(1981, 7, 12), "\"Test1\"-Y\"\"", "Test1-1", cb);
testCVTDatetimeToFormatString(createDate(1981, 7, 12), "\"\"-Y\"\"", "-1", cb);
testCVTDatetimeToFormatString(createDate(1981, 7, 12), "\"\"\"\"", "", cb);
}
BOOST_AUTO_TEST_SUITE_END() // FunctionalTest
BOOST_AUTO_TEST_SUITE_END() // CVTDatetimeToFormatString
BOOST_AUTO_TEST_SUITE(CVTStringToFormatDateTime)
static void testCVTStringToFormatDateTime(const string& date, const string& format,
const ISC_TIMESTAMP_TZ& expected, 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;
const ISC_TIMESTAMP_TZ result = CVT_string_to_format_datetime(&desc, format, &cb);
struct tm resultTimes;
memset(&resultTimes, 0, sizeof(resultTimes));
int resultFractions;
NoThrowTimeStamp::decode_timestamp(result.utc_timestamp, &resultTimes, &resultFractions);
SSHORT resultOffset;
TimeZoneUtil::extractOffset(result, &resultOffset);
struct tm expectedTimes;
memset(&expectedTimes, 0, sizeof(expectedTimes));
int expectedFractions;
NoThrowTimeStamp::decode_timestamp(expected.utc_timestamp, &expectedTimes, &expectedFractions);
SSHORT expectedOffset;
TimeZoneUtil::extractOffset(expected, &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));
}
BOOST_AUTO_TEST_SUITE(FunctionalTest)
BOOST_AUTO_TEST_CASE(CVTStringToFormatDateTime_DATE)
{
testCVTStringToFormatDateTime("1", "YEAR", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("1234", "YEAR", createTimeStampTZ(1234, 1, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("9999", "YEAR", createTimeStampTZ(9999, 1, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("1", "YYYY", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("1234", "YYYY", createTimeStampTZ(1234, 1, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("9999", "YYYY", createTimeStampTZ(9999, 1, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("1", "YYY", createTimeStampTZ(2001, 1, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("450", "YYY", createTimeStampTZ(2450, 1, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("451", "YYY", createTimeStampTZ(1451, 1, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("999", "YYY", createTimeStampTZ(1999, 1, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("1", "YY", createTimeStampTZ(2001, 1, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("45", "YY", createTimeStampTZ(2045, 1, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("46", "YY", createTimeStampTZ(1946, 1, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("99", "YY", createTimeStampTZ(1999, 1, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("1", "Y", createTimeStampTZ(2001, 1, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("9", "Y", createTimeStampTZ(2009, 1, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("1", "MM", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("6", "MM", createTimeStampTZ(1, 6, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("12", "MM", createTimeStampTZ(1, 12, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("Jan", "MON", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("Jun", "MON", createTimeStampTZ(1, 6, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("Dec", "MON", createTimeStampTZ(1, 12, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("January", "MONTH", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("June", "MONTH", createTimeStampTZ(1, 6, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("December", "MONTH", createTimeStampTZ(1, 12, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("I", "RM", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("IV", "RM", createTimeStampTZ(1, 4, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("XII", "RM", createTimeStampTZ(1, 12, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("1", "DD", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("15", "DD", createTimeStampTZ(1, 1, 15, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("31", "DD", createTimeStampTZ(1, 1, 31, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("2451887", "J", createTimeStampTZ(2000, 12, 8, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("1721426", "J", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("5373484", "J", createTimeStampTZ(9999, 12, 31, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("1:1,1", "YEAR.MM.DD", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("1981-8/13", "YEAR.MM.DD", createTimeStampTZ(1981, 8, 13, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("9999 12;31", "YEAR.MM.DD", createTimeStampTZ(9999, 12, 31, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("25.Jan.25", "YY;MON;DD", createTimeStampTZ(2025, 1, 25, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("./.1981./-8--/13--", " YEAR. -.MM.,,-.DD//", createTimeStampTZ(1981, 8, 13, 0, 0, 0, 0), cb);
}
BOOST_AUTO_TEST_CASE(CVTStringToFormatDateTime_TIME)
{
testCVTStringToFormatDateTime("12 AM", "HH", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("1 AM", "HH", createTimeStampTZ(1, 1, 1, 1, 0, 0, 0), cb);
testCVTStringToFormatDateTime("11 AM", "HH", createTimeStampTZ(1, 1, 1, 11, 0, 0, 0), cb);
testCVTStringToFormatDateTime("12 PM", "HH", createTimeStampTZ(1, 1, 1, 12, 0, 0, 0), cb);
testCVTStringToFormatDateTime("1 PM", "HH", createTimeStampTZ(1, 1, 1, 13, 0, 0, 0), cb);
testCVTStringToFormatDateTime("11 PM", "HH", createTimeStampTZ(1, 1, 1, 23, 0, 0, 0), cb);
testCVTStringToFormatDateTime("12 AM", "HH12", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("1 AM", "HH12", createTimeStampTZ(1, 1, 1, 1, 0, 0, 0), cb);
testCVTStringToFormatDateTime("11 AM", "HH12", createTimeStampTZ(1, 1, 1, 11, 0, 0, 0), cb);
testCVTStringToFormatDateTime("12 PM", "HH12", createTimeStampTZ(1, 1, 1, 12, 0, 0, 0), cb);
testCVTStringToFormatDateTime("1 PM", "HH12", createTimeStampTZ(1, 1, 1, 13, 0, 0, 0), cb);
testCVTStringToFormatDateTime("11 PM", "HH12", createTimeStampTZ(1, 1, 1, 23, 0, 0, 0), cb);
testCVTStringToFormatDateTime("0", "HH24", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("12", "HH24", createTimeStampTZ(1, 1, 1, 12, 0, 0, 0), cb);
testCVTStringToFormatDateTime("23", "HH24", createTimeStampTZ(1, 1, 1, 23, 0, 0, 0), cb);
testCVTStringToFormatDateTime("0", "MI", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("30", "MI", createTimeStampTZ(1, 1, 1, 0, 30, 0, 0), cb);
testCVTStringToFormatDateTime("59", "MI", createTimeStampTZ(1, 1, 1, 0, 59, 0, 0), cb);
testCVTStringToFormatDateTime("0", "SS", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("30", "SS", createTimeStampTZ(1, 1, 1, 0, 0, 30, 0), cb);
testCVTStringToFormatDateTime("59", "SS", createTimeStampTZ(1, 1, 1, 0, 0, 59, 0), cb);
testCVTStringToFormatDateTime("0", "SSSSS", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0), cb);
testCVTStringToFormatDateTime("45315", "SSSSS", createTimeStampTZ(1, 1, 1, 12, 35, 15, 0), cb);
testCVTStringToFormatDateTime("86399", "SSSSS", createTimeStampTZ(1, 1, 1, 23, 59, 59, 0), cb);
testCVTStringToFormatDateTime("1", "FF1", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 1000), cb);
testCVTStringToFormatDateTime("5", "FF1", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 5000), cb);
testCVTStringToFormatDateTime("9", "FF1", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 9000), cb);
testCVTStringToFormatDateTime("1", "FF2", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 100), cb);
testCVTStringToFormatDateTime("10", "FF2", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 1000), cb);
testCVTStringToFormatDateTime("50", "FF2", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 5000), cb);
testCVTStringToFormatDateTime("99", "FF2", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 9900), cb);
testCVTStringToFormatDateTime("1", "FF3", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 10), cb);
testCVTStringToFormatDateTime("10", "FF3", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 100), cb);
testCVTStringToFormatDateTime("100", "FF3", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 1000), cb);
testCVTStringToFormatDateTime("500", "FF3", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 5000), cb);
testCVTStringToFormatDateTime("999", "FF3", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 9990), cb);
testCVTStringToFormatDateTime("1", "FF4", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 1), cb);
testCVTStringToFormatDateTime("10", "FF4", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 10), cb);
testCVTStringToFormatDateTime("100", "FF4", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 100), cb);
testCVTStringToFormatDateTime("1000", "FF4", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 1000), cb);
testCVTStringToFormatDateTime("5000", "FF4", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 5000), cb);
testCVTStringToFormatDateTime("9999", "FF4", createTimeStampTZ(1, 1, 1, 0, 0, 0, 0, 9999), cb);
testCVTStringToFormatDateTime("1 PM - 25 - 45 - 200", "HH.MI.SS.FF4", createTimeStampTZ(1, 1, 1, 13, 25, 45, 0, 200), cb);
testCVTStringToFormatDateTime("15:0:15:2", "HH24.MI.SS.FF1", createTimeStampTZ(1, 1, 1, 15, 0, 15, 0, 2000), cb);
}
BOOST_AUTO_TEST_CASE(CVTStringToFormatDateTime_TZ)
{
testCVTStringToFormatDateTime("12:00 2:30", "HH24:MI TZH:TZM", createTimeStampTZ(1, 1, 1, 12, 0, 0, 150, 0), cb);
testCVTStringToFormatDateTime("12:00 +2:30", "HH24:MI TZH:TZM", createTimeStampTZ(1, 1, 1, 12, 0, 0, 150, 0), cb);
testCVTStringToFormatDateTime("12:00 -2:30", "HH24:MI TZH:TZM", createTimeStampTZ(1, 1, 1, 12, 0, 0, -150, 0), cb);
testCVTStringToFormatDateTime("12:00 +0:30", "HH24:MI TZH:TZM", createTimeStampTZ(1, 1, 1, 12, 0, 0, 30, 0), cb);
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
BOOST_AUTO_TEST_SUITE_END() // CVTDatetimeFormat
BOOST_AUTO_TEST_SUITE_END() // CVTSuite

View File

@ -0,0 +1,126 @@
#ifndef CVT_TEST_UTILS_H
#define CVT_TEST_UTILS_H
#include "firebird.h"
#include "../common/dsc.h"
#include "../common/TimeZoneUtil.h"
#include "../common/TimeZones.h"
using Firebird::NoThrowTimeStamp;
using Firebird::TimeZoneUtil;
namespace CvtTestUtils {
#define DECOMPOSE_TM_STRUCT(times, fractions, timezone) "Year:" << times.tm_year + 1900 << \
" Month:" << times.tm_mon << \
" Day:" << times.tm_mday << \
" Hour:" << times.tm_hour << \
" Min:" << times.tm_min << \
" Sec:" << times.tm_sec << \
" Fract: " << fractions << \
" IsDST:" << times.tm_isdst << \
" WDay:" << times.tm_wday << \
" YDay:" << times.tm_yday << \
" TZ Offset:" << timezone \
template<typename T>
static constexpr int sign(T value)
{
return (T(0) < value) - (value < T(0));
}
static struct tm initTMStruct(int year, int month, int day)
{
struct tm times;
memset(&times, 0, sizeof(struct tm));
times.tm_year = year - 1900;
times.tm_mon = month - 1;
times.tm_mday = day;
mktime(&times);
return times;
}
static ISC_DATE createDate(int year, int month, int day)
{
struct tm times = initTMStruct(year, month, day);
return NoThrowTimeStamp::encode_date(&times);
}
static ISC_TIME createTime(int hours, int minutes, int seconds, int fractions = 0)
{
return NoThrowTimeStamp::encode_time(hours, minutes, seconds, fractions);
}
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);
times.tm_hour = hours;
times.tm_min = minutes;
times.tm_sec = seconds;
return NoThrowTimeStamp::encode_timestamp(&times, fractions);
}
static ISC_TIME_TZ createTimeTZ(int hours, int minutes, int seconds, int offsetInMinutes, int fractions = 0)
{
ISC_TIME_TZ timeTZ;
timeTZ.time_zone = TimeZoneUtil::makeFromOffset(sign(offsetInMinutes), abs(offsetInMinutes / 60),
abs(offsetInMinutes % 60));
timeTZ.utc_time = createTime(hours, minutes, seconds, fractions);
return timeTZ;
}
static ISC_TIMESTAMP_TZ createTimeStampTZ(int year, int month, int day, int hours, int minutes, int seconds,
int offsetInMinutes, int fractions = 0)
{
ISC_TIMESTAMP_TZ timestampTZ;
timestampTZ.time_zone = TimeZoneUtil::makeFromOffset(sign(offsetInMinutes), abs(offsetInMinutes / 60),
abs(offsetInMinutes % 60));
timestampTZ.utc_timestamp = createTimeStamp(year, month, day, hours, minutes, seconds, fractions);
return timestampTZ;
}
class CVTCallback : public Firebird::Callbacks
{
public:
explicit CVTCallback(ErrorFunction aErr) : Callbacks(aErr)
{}
public:
bool transliterate(const dsc* from, dsc* to, CHARSET_ID&) override { return true; }
CHARSET_ID getChid(const dsc* d) override { return 0; }
Jrd::CharSet* getToCharset(CHARSET_ID charset2) override { return nullptr; }
void validateData(Jrd::CharSet* toCharset, SLONG length, const UCHAR* q) override { }
ULONG validateLength(Jrd::CharSet* charSet, CHARSET_ID charSetId, ULONG length, const UCHAR* start,
const USHORT size) override { return 0; }
SLONG getLocalDate() override { return 0; }
ISC_TIMESTAMP getCurrentGmtTimeStamp() override { ISC_TIMESTAMP ts; return ts; }
USHORT getSessionTimeZone() override { return 1439; }
void isVersion4(bool& v4) override { }
};
template<typename T>
static UCHAR getDSCTypeFromDateType() { return 0; }
template<>
UCHAR getDSCTypeFromDateType<ISC_DATE>() { return dtype_sql_date; }
template<>
UCHAR getDSCTypeFromDateType<ISC_TIME>() { return dtype_sql_time; }
template<>
UCHAR getDSCTypeFromDateType<ISC_TIMESTAMP>() { return dtype_timestamp; }
template<>
UCHAR getDSCTypeFromDateType<ISC_TIME_TZ>() { return dtype_sql_time_tz; }
template<>
UCHAR getDSCTypeFromDateType<ISC_TIMESTAMP_TZ>() { return dtype_timestamp_tz; }
} // namespace CvtTestUtils
#endif // CVT_TEST_UTILS_H

View File

@ -3494,24 +3494,30 @@ dsc* BoolAsValueNode::execute(thread_db* tdbb, Request* request) const
//--------------------
static RegisterNode<CastNode> regCastNode({blr_cast});
static RegisterNode<CastNode> regCastNode({blr_cast, blr_cast_format});
CastNode::CastNode(MemoryPool& pool, ValueExprNode* aSource, dsql_fld* aDsqlField)
CastNode::CastNode(MemoryPool& pool, ValueExprNode* aSource, dsql_fld* aDsqlField, const string& aFormat)
: TypedNode<ValueExprNode, ExprNode::TYPE_CAST>(pool),
dsqlAlias("CAST"),
dsqlField(aDsqlField),
source(aSource),
itemInfo(NULL),
format(pool, aFormat),
artificial(false)
{
castDesc.clear();
}
// Parse a datatype cast.
DmlNode* CastNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR /*blrOp*/)
DmlNode* CastNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp)
{
fb_assert(blrOp == blr_cast || blrOp == blr_cast_format);
CastNode* node = FB_NEW_POOL(pool) CastNode(pool);
if (blrOp == blr_cast_format)
csb->csb_blr_reader.getString(node->format);
ItemInfo itemInfo;
PAR_desc(tdbb, csb, &node->castDesc, &itemInfo);
@ -3539,6 +3545,7 @@ string CastNode::internalPrint(NodePrinter& printer) const
NODE_PRINT(printer, castDesc);
NODE_PRINT(printer, source);
NODE_PRINT(printer, itemInfo);
NODE_PRINT(printer, format);
return "CastNode";
}
@ -3549,6 +3556,7 @@ ValueExprNode* CastNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
node->dsqlAlias = dsqlAlias;
node->source = doDsqlPass(dsqlScratch, source);
node->dsqlField = dsqlField;
node->format = format;
DDL_resolve_intl_type(dsqlScratch, node->dsqlField, NULL);
node->setParameterType(dsqlScratch, NULL, false);
@ -3593,8 +3601,16 @@ bool CastNode::setParameterType(DsqlCompilerScratch* /*dsqlScratch*/,
// Generate BLR for a data-type cast operation.
void CastNode::genBlr(DsqlCompilerScratch* dsqlScratch)
{
dsqlScratch->appendUChar(blr_cast);
if (format.hasData())
{
dsqlScratch->appendUChar(blr_cast_format);
dsqlScratch->appendString(0, format);
}
else
dsqlScratch->appendUChar(blr_cast);
dsqlScratch->putDtype(dsqlField, true);
GEN_expr(dsqlScratch, source);
}
@ -3640,6 +3656,7 @@ ValueExprNode* CastNode::copy(thread_db* tdbb, NodeCopier& copier) const
node->source = copier.copy(tdbb, source);
node->castDesc = castDesc;
node->itemInfo = itemInfo;
node->format = format;
return node;
}
@ -3652,7 +3669,7 @@ bool CastNode::dsqlMatch(DsqlCompilerScratch* dsqlScratch, const ExprNode* other
const CastNode* o = nodeAs<CastNode>(other);
fb_assert(o);
return dsqlField == o->dsqlField;
return dsqlField == o->dsqlField && format == o->format;
}
bool CastNode::sameAs(const ExprNode* other, bool ignoreStreams) const
@ -3666,7 +3683,7 @@ bool CastNode::sameAs(const ExprNode* other, bool ignoreStreams) const
const CastNode* const otherNode = nodeAs<CastNode>(other);
fb_assert(otherNode);
return DSC_EQUIV(&castDesc, &otherNode->castDesc, true);
return DSC_EQUIV(&castDesc, &otherNode->castDesc, true) && format == otherNode->format;
}
ValueExprNode* CastNode::pass1(thread_db* tdbb, CompilerScratch* csb)
@ -3706,12 +3723,12 @@ dsc* CastNode::execute(thread_db* tdbb, Request* request) const
const auto impure = request->getImpure<impure_value>(impureOffset);
return perform(tdbb, impure, value, &castDesc, itemInfo);
return perform(tdbb, impure, value, &castDesc, itemInfo, format);
}
// Cast from one datatype to another.
dsc* CastNode::perform(thread_db* tdbb, impure_value* impure, dsc* value,
const dsc* castDesc, const ItemInfo* itemInfo)
const dsc* castDesc, const ItemInfo* itemInfo, const Firebird::string& format)
{
// If validation is not required and the source value is either NULL
// or already in the desired data type, simply return it "as is"
@ -3767,7 +3784,58 @@ dsc* CastNode::perform(thread_db* tdbb, impure_value* impure, dsc* value,
if (!value)
return nullptr;
MOV_move(tdbb, value, &impure->vlu_desc);
if (format.hasData())
{
if (DTYPE_IS_TEXT(impure->vlu_desc.dsc_dtype))
{
string result = CVT_datetime_to_format_string(value, format, &EngineCallbacks::instance);
USHORT dscLength = DSC_string_length(&impure->vlu_desc);
string::size_type resultLength = result.length();
if (resultLength > dscLength)
{
ERR_post(Arg::Gds(isc_arith_except) << Arg::Gds(isc_string_truncation) <<
Arg::Gds(isc_trunc_limits) << Arg::Num(dscLength) << Arg::Num(resultLength));
}
USHORT dscOffset = 0;
if (impure->vlu_desc.dsc_dtype == dtype_cstring)
dscOffset = 1;
else if (impure->vlu_desc.dsc_dtype == dtype_varying)
{
dscOffset = sizeof(USHORT);
((vary*) impure->vlu_desc.dsc_address)->vary_length = resultLength;
}
memcpy(impure->vlu_desc.dsc_address + dscOffset, result.c_str(), resultLength);
}
else
{
ISC_TIMESTAMP_TZ timestampTZ = CVT_string_to_format_datetime(value, format, &EngineCallbacks::instance);
switch (impure->vlu_desc.dsc_dtype)
{
case dtype_sql_time:
*(ISC_TIME*) impure->vlu_desc.dsc_address = timestampTZ.utc_timestamp.timestamp_time;
break;
case dtype_sql_date:
*(ISC_DATE*) impure->vlu_desc.dsc_address = timestampTZ.utc_timestamp.timestamp_date;
break;
case dtype_timestamp:
*(ISC_TIMESTAMP*) impure->vlu_desc.dsc_address = timestampTZ.utc_timestamp;
break;
case dtype_sql_time_tz:
case dtype_ex_time_tz:
*(ISC_TIME_TZ*) impure->vlu_desc.dsc_address = TimeZoneUtil::timeStampTzToTimeTz(timestampTZ);
break;
case dtype_timestamp_tz:
case dtype_ex_timestamp_tz:
*(ISC_TIMESTAMP_TZ*) impure->vlu_desc.dsc_address = timestampTZ;
break;
}
}
}
else
MOV_move(tdbb, value, &impure->vlu_desc);
if (impure->vlu_desc.dsc_dtype == dtype_text)
INTL_adjust_text_descriptor(tdbb, &impure->vlu_desc);
@ -5781,55 +5849,7 @@ dsc* ExtractNode::execute(thread_db* tdbb, Request* request) const
case blr_extract_week:
{
// Algorithm for Converting Gregorian Dates to ISO 8601 Week Date by Rick McCarty, 1999
// http://personal.ecu.edu/mccartyr/ISOwdALG.txt
const int y = times.tm_year + 1900;
const int dayOfYearNumber = times.tm_yday + 1;
// Find the jan1Weekday for y (Monday=1, Sunday=7)
const int yy = (y - 1) % 100;
const int c = (y - 1) - yy;
const int g = yy + yy / 4;
const int jan1Weekday = 1 + (((((c / 100) % 4) * 5) + g) % 7);
// Find the weekday for y m d
const int h = dayOfYearNumber + (jan1Weekday - 1);
const int weekday = 1 + ((h - 1) % 7);
// Find if y m d falls in yearNumber y-1, weekNumber 52 or 53
int yearNumber, weekNumber;
if ((dayOfYearNumber <= (8 - jan1Weekday)) && (jan1Weekday > 4))
{
yearNumber = y - 1;
weekNumber = ((jan1Weekday == 5) || ((jan1Weekday == 6) &&
TimeStamp::isLeapYear(yearNumber))) ? 53 : 52;
}
else
{
yearNumber = y;
// Find if y m d falls in yearNumber y+1, weekNumber 1
int i = TimeStamp::isLeapYear(y) ? 366 : 365;
if ((i - dayOfYearNumber) < (4 - weekday))
{
yearNumber = y + 1;
weekNumber = 1;
}
}
// Find if y m d falls in yearNumber y, weekNumber 1 through 53
if (yearNumber == y)
{
int j = dayOfYearNumber + (7 - weekday) + (jan1Weekday - 1);
weekNumber = j / 7;
if (jan1Weekday > 4)
weekNumber--;
}
part = weekNumber;
part = NoThrowTimeStamp::convertGregorianDateToWeekDate(times);
break;
}

View File

@ -242,7 +242,8 @@ public:
class CastNode final : public TypedNode<ValueExprNode, ExprNode::TYPE_CAST>
{
public:
explicit CastNode(MemoryPool& pool, ValueExprNode* aSource = NULL, dsql_fld* aDsqlField = NULL);
explicit CastNode(MemoryPool& pool, ValueExprNode* aSource = NULL, dsql_fld* aDsqlField = NULL,
const Firebird::string& aFormat = NULL);
static DmlNode* parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp);
@ -274,13 +275,14 @@ public:
virtual dsc* execute(thread_db* tdbb, Request* request) const;
static dsc* perform(thread_db* tdbb, impure_value* impure, dsc* value,
const dsc* castDesc, const ItemInfo* itemInfo);
const dsc* castDesc, const ItemInfo* itemInfo, const Firebird::string& format = nullptr);
public:
MetaName dsqlAlias;
dsql_fld* dsqlField;
NestConst<ValueExprNode> source;
NestConst<ItemInfo> itemInfo;
Firebird::string format;
dsc castDesc;
bool artificial;
};

View File

@ -700,6 +700,7 @@ using namespace Firebird;
%token <metaNamePtr> ANY_VALUE
%token <metaNamePtr> CALL
%token <metaNamePtr> FORMAT
%token <metaNamePtr> NAMED_ARG_ASSIGN
// precedence declarations for expression evaluation
@ -4855,6 +4856,7 @@ non_charset_simple_type
| numeric_type
| float_type
| decfloat_type
| date_time_type
| BIGINT
{
$$ = newNode<dsql_fld>();
@ -4898,60 +4900,6 @@ non_charset_simple_type
$$->length = sizeof(SSHORT);
$$->flags |= FLD_has_prec;
}
| DATE
{
$$ = newNode<dsql_fld>();
stmt_ambiguous = true;
if (client_dialect <= SQL_DIALECT_V5)
{
// Post warning saying that DATE is equivalent to TIMESTAMP
ERRD_post_warning(Arg::Warning(isc_sqlwarn) << Arg::Num(301) <<
Arg::Warning(isc_dtype_renamed));
$$->dtype = dtype_timestamp;
$$->length = sizeof(GDS_TIMESTAMP);
}
else if (client_dialect == SQL_DIALECT_V6_TRANSITION)
yyabandon(YYPOSNARG(1), -104, isc_transitional_date);
else
{
$$->dtype = dtype_sql_date;
$$->length = sizeof(ULONG);
}
$$->flags |= FLD_has_prec;
}
| TIME without_time_zone_opt
{
$$ = newNode<dsql_fld>();
checkTimeDialect();
$$->dtype = dtype_sql_time;
$$->length = sizeof(SLONG);
$$->flags |= FLD_has_prec;
}
| TIME WITH TIME ZONE
{
$$ = newNode<dsql_fld>();
checkTimeDialect();
$$->dtype = dtype_sql_time_tz;
$$->length = sizeof(ISC_TIME_TZ);
$$->flags |= FLD_has_prec;
}
| TIMESTAMP without_time_zone_opt
{
$$ = newNode<dsql_fld>();
$$->dtype = dtype_timestamp;
$$->length = sizeof(GDS_TIMESTAMP);
$$->flags |= FLD_has_prec;
}
| TIMESTAMP WITH TIME ZONE
{
$$ = newNode<dsql_fld>();
$$->dtype = dtype_timestamp_tz;
$$->length = sizeof(ISC_TIMESTAMP_TZ);
$$->flags |= FLD_has_prec;
}
| BOOLEAN
{
$$ = newNode<dsql_fld>();
@ -8720,6 +8668,77 @@ named_argument
cast_specification
: CAST '(' value AS data_type_descriptor ')'
{ $$ = newNode<CastNode>($3, $5); }
| CAST '(' value AS cast_format_type cast_format_clause utf_string ')'
{ $$ = newNode<CastNode>($3, $5, *$7); }
;
%type <metaNamePtr> cast_format_clause
cast_format_clause
: FORMAT
;
%type <legacyField> date_time_type
date_time_type
: DATE
{
$$ = newNode<dsql_fld>();
stmt_ambiguous = true;
if (client_dialect <= SQL_DIALECT_V5)
{
// Post warning saying that DATE is equivalent to TIMESTAMP
ERRD_post_warning(Arg::Warning(isc_sqlwarn) << Arg::Num(301) <<
Arg::Warning(isc_dtype_renamed));
$$->dtype = dtype_timestamp;
$$->length = sizeof(GDS_TIMESTAMP);
}
else if (client_dialect == SQL_DIALECT_V6_TRANSITION)
yyabandon(YYPOSNARG(1), -104, isc_transitional_date);
else
{
$$->dtype = dtype_sql_date;
$$->length = sizeof(ULONG);
}
$$->flags |= FLD_has_prec;
}
| TIME without_time_zone_opt
{
$$ = newNode<dsql_fld>();
checkTimeDialect();
$$->dtype = dtype_sql_time;
$$->length = sizeof(SLONG);
$$->flags |= FLD_has_prec;
}
| TIME WITH TIME ZONE
{
$$ = newNode<dsql_fld>();
checkTimeDialect();
$$->dtype = dtype_sql_time_tz;
$$->length = sizeof(ISC_TIME_TZ);
$$->flags |= FLD_has_prec;
}
| TIMESTAMP without_time_zone_opt
{
$$ = newNode<dsql_fld>();
$$->dtype = dtype_timestamp;
$$->length = sizeof(GDS_TIMESTAMP);
$$->flags |= FLD_has_prec;
}
| TIMESTAMP WITH TIME ZONE
{
$$ = newNode<dsql_fld>();
$$->dtype = dtype_timestamp_tz;
$$->length = sizeof(ISC_TIMESTAMP_TZ);
$$->flags |= FLD_has_prec;
}
;
%type <legacyField> cast_format_type
cast_format_type
: character_type
| date_time_type
;
// case expressions
@ -9383,6 +9402,7 @@ non_reserved_word
| UNICODE_VAL
// added in FB 6.0
| ANY_VALUE
| FORMAT
;
%%

View File

@ -496,4 +496,6 @@
#define blr_default_arg (unsigned char) 227
#define blr_cast_format (unsigned char) 228
#endif // FIREBIRD_IMPL_BLR_H

View File

@ -972,3 +972,12 @@ FB_IMPL_MSG(JRD, 969, uninitialized_var, -625, "42", "000", "Variable @1 is not
FB_IMPL_MSG(JRD, 970, param_not_exist, -170, "07", "001", "Parameter @1 does not exist")
FB_IMPL_MSG(JRD, 971, param_no_default_not_specified, -170, "07", "001", "Parameter @1 has no default value and was not specified or was specified with DEFAULT")
FB_IMPL_MSG(JRD, 972, param_multiple_assignments, -170, "07", "001", "Parameter @1 has multiple assignments")
FB_IMPL_MSG(JRD, 973, invalid_date_format, -901, "HY", "000", "Cannot recognize \"@1\" part of date format")
FB_IMPL_MSG(JRD, 974, invalid_raw_string_in_date_format, -901, "HY", "000", "Cannot find closing \" for raw text in date format")
FB_IMPL_MSG(JRD, 975, invalid_data_type_for_date_format, -901, "HY", "000", "It is not possible to use this data type for date formatting")
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, 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\"")

View File

@ -5704,6 +5704,15 @@ const
isc_param_not_exist = 335545290;
isc_param_no_default_not_specified = 335545291;
isc_param_multiple_assignments = 335545292;
isc_invalid_date_format = 335545293;
isc_invalid_raw_string_in_date_format = 335545294;
isc_invalid_data_type_for_date_format = 335545295;
isc_incompatible_date_format_with_current_date_type = 335545296;
isc_value_for_pattern_is_out_of_range = 335545297;
isc_month_name_mismatch = 335545298;
isc_incorrect_hours_period = 335545299;
isc_data_for_format_is_exhausted = 335545300;
isc_trailing_part_of_string = 335545301;
isc_gfix_db_name = 335740929;
isc_gfix_invalid_sw = 335740930;
isc_gfix_incmp_sw = 335740932;