diff --git a/doc/sql.extensions/README.time_zone.md b/doc/sql.extensions/README.time_zone.md index 24829796eb..4038ec0893 100644 --- a/doc/sql.extensions/README.time_zone.md +++ b/doc/sql.extensions/README.time_zone.md @@ -103,6 +103,8 @@ void encodeTimeStampTz( ); ``` +When `decodeTimeTz` / `decodeTimeStampTz` is called with non-null `timeZoneBuffer` and ICU could not be loaded in the client, `timeZoneBuffer` returns the string `GMT*` and the others fields receives the timestamp GMT values. + ## Time zone string syntax ``` diff --git a/src/common/TimeZoneUtil.cpp b/src/common/TimeZoneUtil.cpp index 19d59f1c81..05d1d6f7ee 100644 --- a/src/common/TimeZoneUtil.cpp +++ b/src/common/TimeZoneUtil.cpp @@ -189,6 +189,8 @@ static InitInstance timeZoneStartup; //------------------------------------- +const char TimeZoneUtil::GMT_FALLBACK[5] = "GMT*"; + // Return the current user's time zone. USHORT TimeZoneUtil::getSystemTimeZone() { @@ -498,7 +500,7 @@ ISC_TIMESTAMP TimeZoneUtil::timeStampTzToTimeStamp(const ISC_TIMESTAMP_TZ& timeS struct tm times; int fractions; - decodeTimeStamp(tempTimeStampTz, ×, &fractions); + decodeTimeStamp(tempTimeStampTz, false, ×, &fractions); return TimeStamp::encode_timestamp(×, fractions); } @@ -595,16 +597,39 @@ void TimeZoneUtil::localTimeStampToUtc(ISC_TIMESTAMP_TZ& timeStampTz) timeStampTz.utc_timestamp.timestamp_time = ticks % TimeStamp::ISC_TICKS_PER_DAY; } -void TimeZoneUtil::decodeTime(const ISC_TIME_TZ& timeTz, Callbacks* cb, struct tm* times, int* fractions) +bool TimeZoneUtil::decodeTime(const ISC_TIME_TZ& timeTz, bool gmtFallback, Callbacks* cb, + struct tm* times, int* fractions) { - ISC_TIMESTAMP_TZ timeStampTz = cvtTimeTzToTimeStampTz(timeTz, cb); - decodeTimeStamp(timeStampTz, times, fractions); + bool tzLookup = true; + ISC_TIMESTAMP_TZ timeStampTz; + + try + { + timeStampTz = cvtTimeTzToTimeStampTz(timeTz, cb); + } + catch (const Exception&) + { + if (gmtFallback) + { + tzLookup = false; + timeStampTz.time_zone = TimeZoneUtil::GMT_ZONE; + timeStampTz.utc_timestamp = cb->getCurrentGmtTimeStamp(); + timeStampTz.utc_timestamp.timestamp_time = timeTz.utc_time; + } + else + throw; + } + + decodeTimeStamp(timeStampTz, false, times, fractions); + return tzLookup; } -void TimeZoneUtil::decodeTimeStamp(const ISC_TIMESTAMP_TZ& timeStampTz, struct tm* times, int* fractions) +bool TimeZoneUtil::decodeTimeStamp(const ISC_TIMESTAMP_TZ& timeStampTz, bool gmtFallback, + struct tm* times, int* fractions) { SINT64 ticks = timeStampTz.utc_timestamp.timestamp_date * TimeStamp::ISC_TICKS_PER_DAY + timeStampTz.utc_timestamp.timestamp_time; + bool icuFail = false; int displacement; if (timeStampTz.time_zone == GMT_ZONE) @@ -615,32 +640,45 @@ void TimeZoneUtil::decodeTimeStamp(const ISC_TIMESTAMP_TZ& timeStampTz, struct t { UErrorCode icuErrorCode = U_ZERO_ERROR; - Jrd::UnicodeUtil::ConversionICU& icuLib = Jrd::UnicodeUtil::getConversionICU(); - - UCalendar* icuCalendar = icuLib.ucalOpen( - getDesc(timeStampTz.time_zone)->icuName, -1, NULL, UCAL_GREGORIAN, &icuErrorCode); - - if (!icuCalendar) - status_exception::raise(Arg::Gds(isc_random) << "Error calling ICU's ucal_open."); - - icuLib.ucalSetMillis(icuCalendar, ticksToIcuDate(ticks), &icuErrorCode); - - if (U_FAILURE(icuErrorCode)) + try { + Jrd::UnicodeUtil::ConversionICU& icuLib = Jrd::UnicodeUtil::getConversionICU(); + + UCalendar* icuCalendar = icuLib.ucalOpen( + getDesc(timeStampTz.time_zone)->icuName, -1, NULL, UCAL_GREGORIAN, &icuErrorCode); + + if (!icuCalendar) + status_exception::raise(Arg::Gds(isc_random) << "Error calling ICU's ucal_open."); + + icuLib.ucalSetMillis(icuCalendar, ticksToIcuDate(ticks), &icuErrorCode); + + if (U_FAILURE(icuErrorCode)) + { + icuLib.ucalClose(icuCalendar); + status_exception::raise(Arg::Gds(isc_random) << "Error calling ICU's ucal_setMillis."); + } + + displacement = (icuLib.ucalGet(icuCalendar, UCAL_ZONE_OFFSET, &icuErrorCode) + + icuLib.ucalGet(icuCalendar, UCAL_DST_OFFSET, &icuErrorCode)) / U_MILLIS_PER_MINUTE; + + if (U_FAILURE(icuErrorCode)) + { + icuLib.ucalClose(icuCalendar); + status_exception::raise(Arg::Gds(isc_random) << "Error calling ICU's ucal_get."); + } + icuLib.ucalClose(icuCalendar); - status_exception::raise(Arg::Gds(isc_random) << "Error calling ICU's ucal_setMillis."); } - - displacement = (icuLib.ucalGet(icuCalendar, UCAL_ZONE_OFFSET, &icuErrorCode) + - icuLib.ucalGet(icuCalendar, UCAL_DST_OFFSET, &icuErrorCode)) / U_MILLIS_PER_MINUTE; - - if (U_FAILURE(icuErrorCode)) + catch (const Exception&) { - icuLib.ucalClose(icuCalendar); - status_exception::raise(Arg::Gds(isc_random) << "Error calling ICU's ucal_get."); + if (gmtFallback) + { + icuFail = true; + displacement = 0; + } + else + throw; } - - icuLib.ucalClose(icuCalendar); } ticks += displacement * 60 * ISC_TIME_SECONDS_PRECISION; @@ -650,6 +688,8 @@ void TimeZoneUtil::decodeTimeStamp(const ISC_TIMESTAMP_TZ& timeStampTz, struct t ts.timestamp_time = ticks % TimeStamp::ISC_TICKS_PER_DAY; TimeStamp::decode_timestamp(ts, times, fractions); + + return !icuFail; } ISC_TIMESTAMP_TZ TimeZoneUtil::getCurrentSystemTimeStamp() diff --git a/src/common/TimeZoneUtil.h b/src/common/TimeZoneUtil.h index ed36611ede..69bc5ba18a 100644 --- a/src/common/TimeZoneUtil.h +++ b/src/common/TimeZoneUtil.h @@ -59,8 +59,9 @@ public: }; public: - static const USHORT GMT_ZONE = 65535; + static const char GMT_FALLBACK[5]; // "GMT*" + static const USHORT GMT_ZONE = 65535; static const unsigned MAX_LEN = 32; static const unsigned MAX_SIZE = MAX_LEN + 1; @@ -99,8 +100,10 @@ public: static void localTimeStampToUtc(ISC_TIMESTAMP& timeStamp, Callbacks* cb); static void localTimeStampToUtc(ISC_TIMESTAMP_TZ& timeStampTz); - static void decodeTime(const ISC_TIME_TZ& timeTz, Callbacks* cb, struct tm* times, int* fractions = NULL); - static void decodeTimeStamp(const ISC_TIMESTAMP_TZ& timeStampTz, struct tm* times, int* fractions = NULL); + static bool decodeTime(const ISC_TIME_TZ& timeTz, bool gmtFallback, Callbacks* cb, + struct tm* times, int* fractions = NULL); + static bool decodeTimeStamp(const ISC_TIMESTAMP_TZ& timeStampTz, bool gmtFallback, + struct tm* times, int* fractions = NULL); static ISC_TIMESTAMP_TZ getCurrentSystemTimeStamp(); static ISC_TIMESTAMP_TZ getCurrentGmtTimeStamp(); diff --git a/src/common/cvt.cpp b/src/common/cvt.cpp index f5ed0c0e10..76f7acca82 100644 --- a/src/common/cvt.cpp +++ b/src/common/cvt.cpp @@ -2208,6 +2208,7 @@ static void datetime_to_text(const dsc* from, dsc* to, Callbacks* cb) // Convert a date or time value into a timestamp for manipulation + bool tzLookup = true; tm times; memset(×, 0, sizeof(struct tm)); @@ -2222,7 +2223,7 @@ static void datetime_to_text(const dsc* from, dsc* to, Callbacks* cb) break; case dtype_sql_time_tz: - TimeZoneUtil::decodeTime(*(ISC_TIME_TZ*) from->dsc_address, cb, ×, &fractions); + tzLookup = TimeZoneUtil::decodeTime(*(ISC_TIME_TZ*) from->dsc_address, true, cb, ×, &fractions); timezone = ((ISC_TIME_TZ*) from->dsc_address)->time_zone; break; @@ -2237,7 +2238,7 @@ static void datetime_to_text(const dsc* from, dsc* to, Callbacks* cb) case dtype_timestamp_tz: cb->isVersion4(version4); // Used in the conversion to text some lines below. - TimeZoneUtil::decodeTimeStamp(*(ISC_TIMESTAMP_TZ*) from->dsc_address, ×, &fractions); + tzLookup = TimeZoneUtil::decodeTimeStamp(*(ISC_TIMESTAMP_TZ*) from->dsc_address, true, ×, &fractions); timezone = ((ISC_TIMESTAMP_TZ*) from->dsc_address)->time_zone; break; @@ -2302,7 +2303,13 @@ static void datetime_to_text(const dsc* from, dsc* to, Callbacks* cb) if (from->dsc_dtype == dtype_sql_time_tz || from->dsc_dtype == dtype_timestamp_tz) { *p++ = ' '; - p += TimeZoneUtil::format(p, sizeof(temp) - (p - temp), timezone); + if (tzLookup) + p += TimeZoneUtil::format(p, sizeof(temp) - (p - temp), timezone); + else + { + strncpy(p, TimeZoneUtil::GMT_FALLBACK, sizeof(temp) - (p - temp)); + p += strlen(TimeZoneUtil::GMT_FALLBACK); + } } // Move the text version of the date/time value into the destination @@ -3455,7 +3462,7 @@ namespace ISC_TIMESTAMP CommonCallbacks::getCurrentGmtTimeStamp() { - return TimeZoneUtil::timeStampTzToTimeStamp(TimeZoneUtil::getCurrentSystemTimeStamp(), TimeZoneUtil::GMT_ZONE); + return TimeZoneUtil::getCurrentGmtTimeStamp().utc_timestamp; } USHORT CommonCallbacks::getSessionTimeZone() diff --git a/src/dsql/ExprNodes.cpp b/src/dsql/ExprNodes.cpp index 98c8f0ba1e..bb5afd6630 100644 --- a/src/dsql/ExprNodes.cpp +++ b/src/dsql/ExprNodes.cpp @@ -5448,7 +5448,7 @@ dsc* ExtractNode::execute(thread_db* tdbb, jrd_req* request) const case blr_extract_second: case blr_extract_millisecond: TimeZoneUtil::decodeTime(*(ISC_TIME_TZ*) value->dsc_address, - &EngineCallbacks::instance, ×, &fractions); + false, &EngineCallbacks::instance, ×, &fractions); break; case blr_extract_timezone_hour: @@ -5508,7 +5508,7 @@ dsc* ExtractNode::execute(thread_db* tdbb, jrd_req* request) const break; default: - TimeZoneUtil::decodeTimeStamp(*(ISC_TIMESTAMP_TZ*) value->dsc_address, ×, &fractions); + TimeZoneUtil::decodeTimeStamp(*(ISC_TIMESTAMP_TZ*) value->dsc_address, false, ×, &fractions); } break; diff --git a/src/jrd/SysFunction.cpp b/src/jrd/SysFunction.cpp index 857ea9279c..b5226ab015 100644 --- a/src/jrd/SysFunction.cpp +++ b/src/jrd/SysFunction.cpp @@ -3785,7 +3785,7 @@ dsc* evlFirstLastDay(thread_db* tdbb, const SysFunction* function, const NestVal break; case dtype_timestamp_tz: - TimeZoneUtil::decodeTimeStamp(*(ISC_TIMESTAMP_TZ*) valueDsc->dsc_address, ×, &fractions); + TimeZoneUtil::decodeTimeStamp(*(ISC_TIMESTAMP_TZ*) valueDsc->dsc_address, false, ×, &fractions); break; default: diff --git a/src/yvalve/utl.cpp b/src/yvalve/utl.cpp index 8a394df9c0..52110b99c5 100644 --- a/src/yvalve/utl.cpp +++ b/src/yvalve/utl.cpp @@ -674,7 +674,8 @@ void UtilInterface::decodeTimeTz(CheckStatusWrapper* status, const ISC_TIME_TZ* { tm times; int intFractions; - TimeZoneUtil::decodeTime(*timeTz, CVT_commonCallbacks, ×, &intFractions); + bool tzLookup = TimeZoneUtil::decodeTime(*timeTz, timeZoneBuffer != nullptr, CVT_commonCallbacks, + ×, &intFractions); if (hours) *hours = times.tm_hour; @@ -689,7 +690,12 @@ void UtilInterface::decodeTimeTz(CheckStatusWrapper* status, const ISC_TIME_TZ* *fractions = (unsigned) intFractions; if (timeZoneBuffer) - TimeZoneUtil::format(timeZoneBuffer, timeZoneBufferLength, timeTz->time_zone); + { + if (tzLookup) + TimeZoneUtil::format(timeZoneBuffer, timeZoneBufferLength, timeTz->time_zone); + else + strncpy(timeZoneBuffer, TimeZoneUtil::GMT_FALLBACK, timeZoneBufferLength); + } } catch (const Exception& ex) { @@ -720,7 +726,7 @@ void UtilInterface::decodeTimeStampTz(CheckStatusWrapper* status, const ISC_TIME { tm times; int intFractions; - TimeZoneUtil::decodeTimeStamp(*timeStampTz, ×, &intFractions); + bool tzLookup = TimeZoneUtil::decodeTimeStamp(*timeStampTz, timeZoneBuffer != nullptr, ×, &intFractions); if (year) *year = times.tm_year + 1900; @@ -744,7 +750,12 @@ void UtilInterface::decodeTimeStampTz(CheckStatusWrapper* status, const ISC_TIME *fractions = (unsigned) intFractions; if (timeZoneBuffer) - TimeZoneUtil::format(timeZoneBuffer, timeZoneBufferLength, timeStampTz->time_zone); + { + if (tzLookup) + TimeZoneUtil::format(timeZoneBuffer, timeZoneBufferLength, timeStampTz->time_zone); + else + strncpy(timeZoneBuffer, TimeZoneUtil::GMT_FALLBACK, timeZoneBufferLength); + } } catch (const Exception& ex) {