diff --git a/doc/sql.extensions/README.time_zone.md b/doc/sql.extensions/README.time_zone.md index 4a7b533344..20651a2e98 100644 --- a/doc/sql.extensions/README.time_zone.md +++ b/doc/sql.extensions/README.time_zone.md @@ -21,7 +21,7 @@ a routine that changes the current time zone and later run `SET TIME ZONE LOCAL` to its initially received value. A time zone may be a string with a time zone region (for example, `America/Sao_Paulo`) or a hours:minutes displacement -(for example, `-03:00`) from GMT. +(for example, `-03:00` or `+3`) from GMT. A time/timestamp with time zone is considered equal to another time/timestamp with time zone if their conversion to UTC are equal, for example, `time '10:00 -02' = time '09:00 -03'`, since both are the same as `time '12:00 GMT'`. @@ -32,7 +32,7 @@ in America/New_York, 2:30 AM on March 12, 2017 does not exist and is interpreted 3:30 AM UTC-04). For the second case, when DST ends in America/New_York, 1:30 AM on November 5, 2017 repeats twice and is interpreted as 1:30 AM UTC-04 instead of 1:30 AM UTC-05. -`EXTENDED TIME/TIMESTAMP WITH TIME ZONE` are intended for use only when communicating with a cliens, +`EXTENDED TIME/TIMESTAMP WITH TIME ZONE` are intended for use only when communicating with clients, they solve a problem of representing correct time on clients missing ICU library. One can't use extended datatypes in tables, procedures, etc. The only way to use that datatypes is datatype coercion including SET BIND statement (see [README.set_bind](./README.set_bind.md) for further details). @@ -48,6 +48,33 @@ TIMESTAMP [ { WITH | WITHOUT } TIME ZONE ] EXTENDED { TIME | TIMESTAMP } WITH TIME ZONE ``` +## Region-based `TIME WITH TIME ZONE` semantics + +By definition region-based time zones depends on a moment (date and time - or timestamp) to +know its UTC offset in relation to GMT. +But Firebird also supports region-based time zones in `TIME WITH TIME ZONE` values. + +When constructing a `TIME WITH TIME ZONE` value from a literal or conversion its UTC value must +be computed and cannot be changed, so the current date may not be used. In this case the fixed date +`2020-01-01` is used. So when comparing `TIME WITH TIME ZONE` with different time zones the +comparation is done is a manner similar to they being `TIMESTAMP WITH TIME ZONE` values in the +given date. + +However when converting between `TIMESTAMP` types to `TIME WITH TIME ZONE` that fixed date is +not used, otherwise some weird conversions may be seen when the current date has a different +offset (due to DST changes) than one in `2020-01-01`. In this case when converting +a `TIME WITH TIME ZONE` to `TIMESTAMP WITH TIME ZONE` the time portion is maintained +(if possible). For example, if current date is `2020-05-03` the effective offset in time zone +`America/Los_Angeles` is `-420` while its effective offset in `2020-01-01` is `-480`, but +`cast(time '10:00:00 America/Los_Angeles' as timestamp with time zone)` will result in +`2020-05-03 10:00:00.0000 America/Los_Angeles` instead of having the time portion adjusted. + +But in a date when DST starts there is a missing hour, for example in `America/Los_Angeles` +in `2021-03-14` which there is no `02:00:00` to `02:59:59` hours. In this case the conversion +is done like constructing a literal and the hour is adjusted to its next valid value. +For example, in `2021-03-14` a `cast(time '02:10:00 America/Los_Angeles' as timestamp with time zone)` +will result in `2021-03-14 03:10:00.0000 America/Los_Angeles`. + ### Storage TIME/TIMESTAMP WITH TIME ZONE has respectively the same storage of TIME/TIMESTAMP WITHOUT TIME ZONE @@ -63,6 +90,7 @@ For example, a `00:00` displacement is encoded as `(1 * (0 * 60 + 0)) + 1439 = 1 EXTENDED TIME/TIMESTAMP WITH TIME ZONE have additionally more 2 bytes always containing absolute time zone offset in minutes. + ### API structs ``` @@ -342,16 +370,16 @@ DATABASE_VERSION `RDB$TIME_ZONE_UTIL.TRANSITIONS` returns the set of rules between the start and end timestamps. Input parameters: - - `TIME_ZONE_NAME` type `CHAR(63)` - - `FROM_TIMESTAMP` type `TIMESTAMP WITH TIME ZONE` - - `TO_TIMESTAMP` type `TIMESTAMP WITH TIME ZONE` + - `RDB$TIME_ZONE_NAME` type `CHAR(63)` + - `RDB$FROM_TIMESTAMP` type `TIMESTAMP WITH TIME ZONE` + - `RDB$TO_TIMESTAMP` type `TIMESTAMP WITH TIME ZONE` Output parameters: - - `START_TIMESTAMP` type `TIMESTAMP WITH TIME ZONE` - the transition' start timestamp - - `END_TIMESTAMP` type `TIMESTAMP WITH TIME ZONE` - the transition's end timestamp - - `ZONE_OFFSET` type `SMALLINT` - number of minutes related to the zone's offset - - `DST_OFFSET` type `SMALLINT` - number of minutes related to the zone's DST offset - - `EFFECTIVE_OFFSET` type `SMALLINT` - effective offset (`ZONE_OFFSET + DST_OFFSET`) + - `RDB$START_TIMESTAMP` type `TIMESTAMP WITH TIME ZONE` - the transition' start timestamp + - `RDB$END_TIMESTAMP` type `TIMESTAMP WITH TIME ZONE` - the transition's end timestamp + - `RDB$ZONE_OFFSET` type `SMALLINT` - number of minutes related to the zone's offset + - `RDB$DST_OFFSET` type `SMALLINT` - number of minutes related to the zone's DST offset + - `RDB$EFFECTIVE_OFFSET` type `SMALLINT` - effective offset (`ZONE_OFFSET + DST_OFFSET`) ``` select * @@ -364,13 +392,13 @@ select * Returns: ``` - START_TIMESTAMP END_TIMESTAMP ZONE_OFFSET DST_OFFSET EFFECTIVE_OFFSET -============================ ============================ =========== ========== ================ -2016-10-16 03:00:00.0000 GMT 2017-02-19 01:59:59.9999 GMT -180 60 -120 -2017-02-19 02:00:00.0000 GMT 2017-10-15 02:59:59.9999 GMT -180 0 -180 -2017-10-15 03:00:00.0000 GMT 2018-02-18 01:59:59.9999 GMT -180 60 -120 -2018-02-18 02:00:00.0000 GMT 2018-10-21 02:59:59.9999 GMT -180 0 -180 -2018-10-21 03:00:00.0000 GMT 2019-02-17 01:59:59.9999 GMT -180 60 -120 + RDB$START_TIMESTAMP RDB$END_TIMESTAMP RDB$ZONE_OFFSET RDB$DST_OFFSET RDB$EFFECTIVE_OFFSET +============================ ============================ =============== ============== ==================== +2016-10-16 03:00:00.0000 GMT 2017-02-19 01:59:59.9999 GMT -180 60 -120 +2017-02-19 02:00:00.0000 GMT 2017-10-15 02:59:59.9999 GMT -180 0 -180 +2017-10-15 03:00:00.0000 GMT 2018-02-18 01:59:59.9999 GMT -180 60 -120 +2018-02-18 02:00:00.0000 GMT 2018-10-21 02:59:59.9999 GMT -180 0 -180 +2018-10-21 03:00:00.0000 GMT 2019-02-17 01:59:59.9999 GMT -180 60 -120 ``` # Updating the time zone database diff --git a/src/common/TimeZoneUtil.cpp b/src/common/TimeZoneUtil.cpp index 6a44ea86a8..4f41cbf041 100644 --- a/src/common/TimeZoneUtil.cpp +++ b/src/common/TimeZoneUtil.cpp @@ -190,6 +190,8 @@ static InitInstance timeZoneStartup; //------------------------------------- + +const ISC_DATE TimeZoneUtil::TIME_TZ_BASE_DATE = 58849; // 2020-01-01 const char TimeZoneUtil::GMT_FALLBACK[5] = "GMT*"; InitInstance TimeZoneUtil::tzDataPath; @@ -524,69 +526,41 @@ void TimeZoneUtil::extractOffset(const ISC_TIMESTAMP_TZ& timeStampTz, SSHORT* of *offset = displacement; } -// Converts a time-tz to a time in a given zone. -ISC_TIME TimeZoneUtil::timeTzToTime(const ISC_TIME_TZ& timeTz, USHORT toTimeZone, Callbacks* cb) -{ - ISC_TIMESTAMP_TZ tempTimeStampTz; - tempTimeStampTz.utc_timestamp.timestamp_date = cb->getLocalDate(); - tempTimeStampTz.utc_timestamp.timestamp_time = 0; - tempTimeStampTz.time_zone = cb->getSessionTimeZone(); - TimeZoneUtil::localTimeStampToUtc(tempTimeStampTz); - - tempTimeStampTz.utc_timestamp.timestamp_time = timeTz.utc_time; - tempTimeStampTz.time_zone = timeTz.time_zone; - - return timeStampTzToTimeStamp(tempTimeStampTz, toTimeZone).timestamp_time; -} - -// Converts a timestamp-tz to a timestamp in a given zone. -ISC_TIMESTAMP TimeZoneUtil::timeStampTzToTimeStamp(const ISC_TIMESTAMP_TZ& timeStampTz, USHORT toTimeZone) -{ - ISC_TIMESTAMP_TZ tempTimeStampTz = timeStampTz; - tempTimeStampTz.time_zone = toTimeZone; - - struct tm times; - int fractions; - decodeTimeStamp(tempTimeStampTz, false, TimeZoneUtil::NO_OFFSET, ×, &fractions); - - return TimeStamp::encode_timestamp(×, fractions); -} - // Converts a time from local to UTC. -void TimeZoneUtil::localTimeToUtc(ISC_TIME& time, Callbacks* cb) +void TimeZoneUtil::localTimeToUtc(ISC_TIME& time, ISC_USHORT timeZone) { ISC_TIME_TZ timeTz; timeTz.utc_time = time; - timeTz.time_zone = cb->getSessionTimeZone(); - localTimeToUtc(timeTz, cb); + timeTz.time_zone = timeZone; + localTimeToUtc(timeTz); time = timeTz.utc_time; } // Converts a time from local to UTC. -void TimeZoneUtil::localTimeToUtc(ISC_TIME_TZ& timeTz, Callbacks* cb) +void TimeZoneUtil::localTimeToUtc(ISC_TIME_TZ& timeTz) { - ISC_TIMESTAMP_TZ tempTimeStampTz; - tempTimeStampTz.utc_timestamp.timestamp_date = cb->getCurrentGmtTimeStamp().timestamp_date; - tempTimeStampTz.utc_timestamp.timestamp_time = timeTz.utc_time; - tempTimeStampTz.time_zone = timeTz.time_zone; - localTimeStampToUtc(tempTimeStampTz); + ISC_TIMESTAMP_TZ tsTz; + tsTz.utc_timestamp.timestamp_date = TIME_TZ_BASE_DATE; + tsTz.utc_timestamp.timestamp_time = timeTz.utc_time; + tsTz.time_zone = timeTz.time_zone; + localTimeStampToUtc(tsTz); - timeTz.utc_time = tempTimeStampTz.utc_timestamp.timestamp_time; + timeTz.utc_time = tsTz.utc_timestamp.timestamp_time; } // Converts a timestamp from its local datetime fields to UTC. void TimeZoneUtil::localTimeStampToUtc(ISC_TIMESTAMP& timeStamp, Callbacks* cb) { - ISC_TIMESTAMP_TZ tempTimeStampTz; - tempTimeStampTz.utc_timestamp.timestamp_date = timeStamp.timestamp_date; - tempTimeStampTz.utc_timestamp.timestamp_time = timeStamp.timestamp_time; - tempTimeStampTz.time_zone = cb->getSessionTimeZone(); + ISC_TIMESTAMP_TZ tsTz; + tsTz.utc_timestamp.timestamp_date = timeStamp.timestamp_date; + tsTz.utc_timestamp.timestamp_time = timeStamp.timestamp_time; + tsTz.time_zone = cb->getSessionTimeZone(); - localTimeStampToUtc(tempTimeStampTz); + localTimeStampToUtc(tsTz); - timeStamp.timestamp_date = tempTimeStampTz.utc_timestamp.timestamp_date; - timeStamp.timestamp_time = tempTimeStampTz.utc_timestamp.timestamp_time; + timeStamp.timestamp_date = tsTz.utc_timestamp.timestamp_date; + timeStamp.timestamp_time = tsTz.utc_timestamp.timestamp_time; } // Converts a timestamp from its local datetime fields to UTC. @@ -643,35 +617,15 @@ void TimeZoneUtil::localTimeStampToUtc(ISC_TIMESTAMP_TZ& timeStampTz) timeStampTz.utc_timestamp = TimeStamp::ticksToTimeStamp(ticks); } -bool TimeZoneUtil::decodeTime(const ISC_TIME_TZ& timeTz, bool gmtFallback, SLONG gmtOffset, Callbacks* cb, +bool TimeZoneUtil::decodeTime(const ISC_TIME_TZ& timeTz, bool gmtFallback, SLONG gmtOffset, struct tm* times, int* fractions) { - bool tzLookup = true; ISC_TIMESTAMP_TZ timeStampTz; + timeStampTz.utc_timestamp.timestamp_date = TIME_TZ_BASE_DATE; + timeStampTz.utc_timestamp.timestamp_time = timeTz.utc_time; + timeStampTz.time_zone = timeTz.time_zone; - try - { -#ifdef DEV_BUILD - if (gmtFallback && getenv("MISSING_ICU_EMULATION")) - (Arg::Gds(isc_random) << "Emulating missing ICU").raise(); -#endif - timeStampTz = cvtTimeTzToTimeStampTz(timeTz, cb); - } - catch (const Exception&) - { - if (gmtFallback) - { - tzLookup = false; - timeStampTz.time_zone = displacementToOffsetZone(gmtOffset == TimeZoneUtil::NO_OFFSET ? 0 : gmtOffset); - timeStampTz.utc_timestamp = cb->getCurrentGmtTimeStamp(); - timeStampTz.utc_timestamp.timestamp_time = timeTz.utc_time; - } - else - throw; - } - - decodeTimeStamp(timeStampTz, gmtFallback, gmtOffset, times, fractions); - return tzLookup; + return decodeTimeStamp(timeStampTz, gmtFallback, gmtOffset, times, fractions); } bool TimeZoneUtil::decodeTimeStamp(const ISC_TIMESTAMP_TZ& timeStampTz, bool gmtFallback, SLONG gmtOffset, @@ -727,7 +681,7 @@ bool TimeZoneUtil::decodeTimeStamp(const ISC_TIMESTAMP_TZ& timeStampTz, bool gmt throw; icuFail = true; - displacement = gmtOffset == TimeZoneUtil::NO_OFFSET ? 0 : gmtOffset; + displacement = gmtOffset == NO_OFFSET ? 0 : gmtOffset; } } @@ -829,8 +783,42 @@ void TimeZoneUtil::validateGmtTimeStamp(NoThrowTimeStamp& ts) ts.value() = getCurrentGmtTimeStamp().utc_timestamp; } +// Converts a time-tz to a time. +ISC_TIME TimeZoneUtil::timeTzToTime(const ISC_TIME_TZ& timeTz, Callbacks* cb) +{ + ISC_TIMESTAMP_TZ tsTz; + tsTz.utc_timestamp.timestamp_date = TIME_TZ_BASE_DATE; + tsTz.utc_timestamp.timestamp_time = timeTz.utc_time; + tsTz.time_zone = timeTz.time_zone; + + struct tm times; + int fractions; + decodeTimeStamp(tsTz, false, NO_OFFSET, ×, &fractions); + + tsTz.utc_timestamp.timestamp_date = cb->getLocalDate(); + tsTz.utc_timestamp.timestamp_time = + TimeStamp::encode_time(times.tm_hour, times.tm_min, times.tm_sec, fractions); + + localTimeStampToUtc(tsTz); + + return timeStampTzToTimeStamp(tsTz, cb->getSessionTimeZone()).timestamp_time; +} + +// Converts a timestamp-tz to a timestamp in a given zone. +ISC_TIMESTAMP TimeZoneUtil::timeStampTzToTimeStamp(const ISC_TIMESTAMP_TZ& timeStampTz, USHORT toTimeZone) +{ + ISC_TIMESTAMP_TZ tsTz = timeStampTz; + tsTz.time_zone = toTimeZone; + + struct tm times; + int fractions; + decodeTimeStamp(tsTz, false, NO_OFFSET, ×, &fractions); + + return TimeStamp::encode_timestamp(×, fractions); +} + // Converts a time to timestamp-tz. -ISC_TIMESTAMP_TZ TimeZoneUtil::cvtTimeToTimeStampTz(const ISC_TIME& time, Callbacks* cb) +ISC_TIMESTAMP_TZ TimeZoneUtil::timeToTimeStampTz(const ISC_TIME& time, Callbacks* cb) { // SQL: source => TIMESTAMP WITHOUT TIME ZONE => TIMESTAMP WITH TIME ZONE @@ -844,66 +832,82 @@ ISC_TIMESTAMP_TZ TimeZoneUtil::cvtTimeToTimeStampTz(const ISC_TIME& time, Callba } // Converts a time to time-tz. -ISC_TIME_TZ TimeZoneUtil::cvtTimeToTimeTz(const ISC_TIME& time, Callbacks* cb) +ISC_TIME_TZ TimeZoneUtil::timeToTimeTz(const ISC_TIME& time, Callbacks* cb) { - ISC_TIMESTAMP_TZ tsTz = cvtTimeToTimeStampTz(time, cb); + ISC_TIMESTAMP_TZ tsTz; + tsTz.time_zone = cb->getSessionTimeZone(); + tsTz.utc_timestamp.timestamp_date = TIME_TZ_BASE_DATE; + tsTz.utc_timestamp.timestamp_time = time; + + localTimeStampToUtc(tsTz); ISC_TIME_TZ timeTz; - timeTz.utc_time = tsTz.utc_timestamp.timestamp_time; timeTz.time_zone = tsTz.time_zone; + timeTz.utc_time = tsTz.utc_timestamp.timestamp_time; return timeTz; } // Converts a time-tz to timestamp-tz. -ISC_TIMESTAMP_TZ TimeZoneUtil::cvtTimeTzToTimeStampTz(const ISC_TIME_TZ& timeTz, Callbacks* cb) +ISC_TIMESTAMP_TZ TimeZoneUtil::timeTzToTimeStampTz(const ISC_TIME_TZ& timeTz, Callbacks* cb) { // SQL: Copy date fields from CURRENT_DATE and time and time zone fields from the source. + struct tm localTimes; + TimeStamp::decode_date(cb->getLocalDate(), &localTimes); + ISC_TIMESTAMP_TZ tsTz; - tsTz.utc_timestamp.timestamp_date = cb->getLocalDate(); - tsTz.utc_timestamp.timestamp_time = timeTzToTime(timeTz, cb->getSessionTimeZone(), cb); - tsTz.time_zone = cb->getSessionTimeZone(); - localTimeStampToUtc(tsTz); tsTz.time_zone = timeTz.time_zone; + tsTz.utc_timestamp.timestamp_date = TIME_TZ_BASE_DATE; + tsTz.utc_timestamp.timestamp_time = timeTz.utc_time; + + struct tm times; + int fractions; + decodeTimeStamp(tsTz, false, NO_OFFSET, ×, &fractions); + + times.tm_mday = localTimes.tm_mday; + times.tm_mon = localTimes.tm_mon; + times.tm_year = localTimes.tm_year; + + tsTz.utc_timestamp = TimeStamp::encode_timestamp(×, fractions); + localTimeStampToUtc(tsTz); return tsTz; } // Converts a time-tz to timestamp. -ISC_TIMESTAMP TimeZoneUtil::cvtTimeTzToTimeStamp(const ISC_TIME_TZ& timeTz, Callbacks* cb) +ISC_TIMESTAMP TimeZoneUtil::timeTzToTimeStamp(const ISC_TIME_TZ& timeTz, Callbacks* cb) { // SQL: source => TIMESTAMP WITH TIME ZONE => TIMESTAMP WITHOUT TIME ZONE - ISC_TIMESTAMP_TZ tsTz = cvtTimeTzToTimeStampTz(timeTz, cb); + ISC_TIMESTAMP_TZ tsTz = timeTzToTimeStampTz(timeTz, cb); return timeStampTzToTimeStamp(tsTz, cb->getSessionTimeZone()); } -// Converts a time-tz to time. -ISC_TIME TimeZoneUtil::cvtTimeTzToTime(const ISC_TIME_TZ& timeTz, Callbacks* cb) -{ - return timeTzToTime(timeTz, cb->getSessionTimeZone(), cb); -} - // Converts a timestamp-tz to timestamp. -ISC_TIMESTAMP TimeZoneUtil::cvtTimeStampTzToTimeStamp(const ISC_TIMESTAMP_TZ& timeStampTz, Callbacks* cb) +ISC_TIMESTAMP TimeZoneUtil::timeStampTzToTimeStamp(const ISC_TIMESTAMP_TZ& timeStampTz, Callbacks* cb) { return timeStampTzToTimeStamp(timeStampTz, cb->getSessionTimeZone()); } // Converts a timestamp-tz to time-tz. -ISC_TIME_TZ TimeZoneUtil::cvtTimeStampTzToTimeTz(const ISC_TIMESTAMP_TZ& timeStampTz) +ISC_TIME_TZ TimeZoneUtil::timeStampTzToTimeTz(const ISC_TIMESTAMP_TZ& timeStampTz) { + struct tm times; + int fractions; + decodeTimeStamp(timeStampTz, false, NO_OFFSET, ×, &fractions); + ISC_TIME_TZ timeTz; - timeTz.utc_time = timeStampTz.utc_timestamp.timestamp_time; + timeTz.utc_time = TimeStamp::encode_time(times.tm_hour, times.tm_min, times.tm_sec, fractions); timeTz.time_zone = timeStampTz.time_zone; + localTimeToUtc(timeTz); return timeTz; } // Converts a timestamp to timestamp-tz. -ISC_TIMESTAMP_TZ TimeZoneUtil::cvtTimeStampToTimeStampTz(const ISC_TIMESTAMP& timeStamp, Callbacks* cb) +ISC_TIMESTAMP_TZ TimeZoneUtil::timeStampToTimeStampTz(const ISC_TIMESTAMP& timeStamp, Callbacks* cb) { // SQL: Copy time and time zone fields from the source. @@ -917,21 +921,15 @@ ISC_TIMESTAMP_TZ TimeZoneUtil::cvtTimeStampToTimeStampTz(const ISC_TIMESTAMP& ti } // Converts a timestamp to time-tz. -ISC_TIME_TZ TimeZoneUtil::cvtTimeStampToTimeTz(const ISC_TIMESTAMP& timeStamp, Callbacks* cb) +ISC_TIME_TZ TimeZoneUtil::timeStampToTimeTz(const ISC_TIMESTAMP& timeStamp, Callbacks* cb) { // SQL: source => TIMESTAMP WITH TIME ZONE => TIME WITH TIME ZONE - ISC_TIMESTAMP_TZ tsTz = cvtTimeStampToTimeStampTz(timeStamp, cb); - - ISC_TIME_TZ timeTz; - timeTz.utc_time = tsTz.utc_timestamp.timestamp_time; - timeTz.time_zone = tsTz.time_zone; - - return timeTz; + return timeStampTzToTimeTz(timeStampToTimeStampTz(timeStamp, cb)); } // Converts a date to timestamp-tz. -ISC_TIMESTAMP_TZ TimeZoneUtil::cvtDateToTimeStampTz(const ISC_DATE& date, Callbacks* cb) +ISC_TIMESTAMP_TZ TimeZoneUtil::dateToTimeStampTz(const ISC_DATE& date, Callbacks* cb) { // SQL: source => TIMESTAMP WITHOUT TIME ZONE => TIMESTAMP WITH TIME ZONE diff --git a/src/common/TimeZoneUtil.h b/src/common/TimeZoneUtil.h index f490456dc9..781508361d 100644 --- a/src/common/TimeZoneUtil.h +++ b/src/common/TimeZoneUtil.h @@ -52,13 +52,7 @@ class NoThrowTimeStamp; class TimeZoneUtil { public: - enum Bind - { - BIND_LEGACY, - BIND_NATIVE - }; - -public: + static const ISC_DATE TIME_TZ_BASE_DATE; static const char GMT_FALLBACK[5]; // "GMT*" static const USHORT GMT_ZONE = 65535; @@ -102,17 +96,15 @@ public: static void extractOffset(const ISC_TIMESTAMP_TZ& timeStampTz, SSHORT* offset); static void extractOffset(const ISC_TIMESTAMP_TZ& timeStampTz, int* sign, unsigned* tzh, unsigned* tzm); - static ISC_TIME timeTzToTime(const ISC_TIME_TZ& timeTz, USHORT toTimeZone, Callbacks* cb); - static ISC_TIMESTAMP timeStampTzToTimeStamp(const ISC_TIMESTAMP_TZ& timeStampTz, USHORT toTimeZone); - - static void localTimeToUtc(ISC_TIME& time, Callbacks* cb); - static void localTimeToUtc(ISC_TIME_TZ& timeTz, Callbacks* cb); + static void localTimeToUtc(ISC_TIME& time, ISC_USHORT timeZone); + static void localTimeToUtc(ISC_TIME_TZ& timeTz); static void localTimeStampToUtc(ISC_TIMESTAMP& timeStamp, Callbacks* cb); static void localTimeStampToUtc(ISC_TIMESTAMP_TZ& timeStampTz); static const SLONG NO_OFFSET = MAX_SLONG; - static bool decodeTime(const ISC_TIME_TZ& timeTz, bool gmtFallback, SLONG gmtOffset, Callbacks* cb, + + static bool decodeTime(const ISC_TIME_TZ& timeTz, bool gmtFallback, SLONG gmtOffset, struct tm* times, int* fractions = NULL); static bool decodeTimeStamp(const ISC_TIMESTAMP_TZ& timeStampTz, bool gmtFallback, SLONG gmtOffset, struct tm* times, int* fractions = NULL); @@ -122,20 +114,22 @@ public: static void validateGmtTimeStamp(NoThrowTimeStamp& ts); - static ISC_TIMESTAMP_TZ cvtTimeToTimeStampTz(const ISC_TIME& time, Callbacks* cb); - static ISC_TIME_TZ cvtTimeToTimeTz(const ISC_TIME& time, Callbacks* cb); + static ISC_TIME timeTzToTime(const ISC_TIME_TZ& timeTz, Callbacks* cb); + static ISC_TIMESTAMP timeStampTzToTimeStamp(const ISC_TIMESTAMP_TZ& timeStampTz, USHORT toTimeZone); - static ISC_TIMESTAMP_TZ cvtTimeTzToTimeStampTz(const ISC_TIME_TZ& timeTz, Callbacks* cb); - static ISC_TIMESTAMP cvtTimeTzToTimeStamp(const ISC_TIME_TZ& timeTz, Callbacks* cb); - static ISC_TIME cvtTimeTzToTime(const ISC_TIME_TZ& timeTz, Callbacks* cb); + static ISC_TIMESTAMP_TZ timeToTimeStampTz(const ISC_TIME& time, Callbacks* cb); + static ISC_TIME_TZ timeToTimeTz(const ISC_TIME& time, Callbacks* cb); - static ISC_TIME_TZ cvtTimeStampTzToTimeTz(const ISC_TIMESTAMP_TZ& timeStampTz); - static ISC_TIMESTAMP cvtTimeStampTzToTimeStamp(const ISC_TIMESTAMP_TZ& timeStampTz, Callbacks* cb); + static ISC_TIMESTAMP_TZ timeTzToTimeStampTz(const ISC_TIME_TZ& timeTz, Callbacks* cb); + static ISC_TIMESTAMP timeTzToTimeStamp(const ISC_TIME_TZ& timeTz, Callbacks* cb); - static ISC_TIMESTAMP_TZ cvtTimeStampToTimeStampTz(const ISC_TIMESTAMP& timeStamp, Callbacks* cb); - static ISC_TIME_TZ cvtTimeStampToTimeTz(const ISC_TIMESTAMP& timeStamp, Callbacks* cb); + static ISC_TIME_TZ timeStampTzToTimeTz(const ISC_TIMESTAMP_TZ& timeStampTz); + static ISC_TIMESTAMP timeStampTzToTimeStamp(const ISC_TIMESTAMP_TZ& timeStampTz, Callbacks* cb); - static ISC_TIMESTAMP_TZ cvtDateToTimeStampTz(const ISC_DATE& date, Callbacks* cb); + static ISC_TIMESTAMP_TZ timeStampToTimeStampTz(const ISC_TIMESTAMP& timeStamp, Callbacks* cb); + static ISC_TIME_TZ timeStampToTimeTz(const ISC_TIMESTAMP& timeStamp, Callbacks* cb); + + static ISC_TIMESTAMP_TZ dateToTimeStampTz(const ISC_DATE& date, Callbacks* cb); }; class TimeZoneRuleIterator diff --git a/src/common/cvt.cpp b/src/common/cvt.cpp index 8b0749dc99..1ab427c6a8 100644 --- a/src/common/cvt.cpp +++ b/src/common/cvt.cpp @@ -920,7 +920,10 @@ void CVT_string_to_datetime(const dsc* desc, { // Fetch current date tm times2; - Firebird::TimeStamp::getCurrentTimeStamp().decode(×2); + TimeStamp baseTimeStamp; + baseTimeStamp.value().timestamp_date = TimeZoneUtil::TIME_TZ_BASE_DATE; + baseTimeStamp.value().timestamp_time = 0; + baseTimeStamp.decode(×2); times.tm_year = times2.tm_year; times.tm_mon = times2.tm_mon; @@ -1002,7 +1005,7 @@ void CVT_string_to_datetime(const dsc* desc, ISC_TIME_TZ timeTz; timeTz.utc_time = date->utc_timestamp.timestamp_time; timeTz.time_zone = zone; - date->utc_timestamp.timestamp_time = TimeZoneUtil::timeTzToTime(timeTz, sessionTimeZone, cb); + date->utc_timestamp.timestamp_time = TimeZoneUtil::timeTzToTime(timeTz, cb); } else if (expect_type == expect_timestamp) *(ISC_TIMESTAMP*) date = TimeZoneUtil::timeStampTzToTimeStamp(*date, sessionTimeZone); @@ -1563,13 +1566,13 @@ void CVT_move_common(const dsc* from, dsc* to, DecimalStatus decSt, Callbacks* c case dtype_sql_time_tz: case dtype_ex_time_tz: *(ISC_TIMESTAMP*) to->dsc_address = - TimeZoneUtil::cvtTimeTzToTimeStamp(*(ISC_TIME_TZ*) from->dsc_address, cb); + TimeZoneUtil::timeTzToTimeStamp(*(ISC_TIME_TZ*) from->dsc_address, cb); return; case dtype_timestamp_tz: case dtype_ex_timestamp_tz: *(ISC_TIMESTAMP*) to->dsc_address = - TimeZoneUtil::cvtTimeStampTzToTimeStamp(*(ISC_TIMESTAMP_TZ*) from->dsc_address, cb); + TimeZoneUtil::timeStampTzToTimeStamp(*(ISC_TIMESTAMP_TZ*) from->dsc_address, cb); return; default: @@ -1588,7 +1591,7 @@ void CVT_move_common(const dsc* from, dsc* to, DecimalStatus decSt, Callbacks* c case dtype_ex_time_tz: d.makeTimeTz((ISC_TIME_TZ*)(to->dsc_address)); CVT_move_common(from, &d, decSt, cb); - TimeZoneUtil::extractOffset(TimeZoneUtil::cvtTimeTzToTimeStampTz(*(ISC_TIME_TZ*)(to->dsc_address), cb), + TimeZoneUtil::extractOffset(TimeZoneUtil::timeTzToTimeStampTz(*(ISC_TIME_TZ*)(to->dsc_address), cb), &((ISC_TIME_TZ_EX*)(to->dsc_address))->ext_offset); return; @@ -1607,13 +1610,13 @@ void CVT_move_common(const dsc* from, dsc* to, DecimalStatus decSt, Callbacks* c case dtype_sql_time: *(ISC_TIMESTAMP_TZ*) to->dsc_address = - TimeZoneUtil::cvtTimeToTimeStampTz(*(ISC_TIME*) from->dsc_address, cb); + TimeZoneUtil::timeToTimeStampTz(*(ISC_TIME*) from->dsc_address, cb); return; case dtype_ex_time_tz: case dtype_sql_time_tz: *((ISC_TIMESTAMP_TZ*) to->dsc_address) = - TimeZoneUtil::cvtTimeTzToTimeStampTz(*(ISC_TIME_TZ*) from->dsc_address, cb); + TimeZoneUtil::timeTzToTimeStampTz(*(ISC_TIME_TZ*) from->dsc_address, cb); return; case dtype_ex_timestamp_tz: @@ -1622,12 +1625,12 @@ void CVT_move_common(const dsc* from, dsc* to, DecimalStatus decSt, Callbacks* c case dtype_sql_date: *(ISC_TIMESTAMP_TZ*) to->dsc_address = - TimeZoneUtil::cvtDateToTimeStampTz(*(GDS_DATE*) from->dsc_address, cb); + TimeZoneUtil::dateToTimeStampTz(*(GDS_DATE*) from->dsc_address, cb); return; case dtype_timestamp: *(ISC_TIMESTAMP_TZ*) to->dsc_address = - TimeZoneUtil::cvtTimeStampToTimeStampTz(*(ISC_TIMESTAMP*) from->dsc_address, cb); + TimeZoneUtil::timeStampToTimeStampTz(*(ISC_TIMESTAMP*) from->dsc_address, cb); return; default: @@ -1656,7 +1659,7 @@ void CVT_move_common(const dsc* from, dsc* to, DecimalStatus decSt, Callbacks* c case dtype_timestamp_tz: case dtype_ex_timestamp_tz: *(GDS_DATE*) to->dsc_address = - TimeZoneUtil::cvtTimeStampTzToTimeStamp(*(ISC_TIMESTAMP_TZ*) from->dsc_address, cb).timestamp_date; + TimeZoneUtil::timeStampTzToTimeStamp(*(ISC_TIMESTAMP_TZ*) from->dsc_address, cb).timestamp_date; return; default: @@ -1680,7 +1683,7 @@ void CVT_move_common(const dsc* from, dsc* to, DecimalStatus decSt, Callbacks* c case dtype_sql_time_tz: case dtype_ex_time_tz: - *(ISC_TIME*) to->dsc_address = TimeZoneUtil::cvtTimeTzToTime(*(ISC_TIME_TZ*) from->dsc_address, cb); + *(ISC_TIME*) to->dsc_address = TimeZoneUtil::timeTzToTime(*(ISC_TIME_TZ*) from->dsc_address, cb); return; case dtype_timestamp: @@ -1690,7 +1693,7 @@ void CVT_move_common(const dsc* from, dsc* to, DecimalStatus decSt, Callbacks* c case dtype_timestamp_tz: case dtype_ex_timestamp_tz: *(GDS_TIME*) to->dsc_address = - TimeZoneUtil::cvtTimeStampTzToTimeStamp(*(ISC_TIMESTAMP_TZ*) from->dsc_address, cb).timestamp_time; + TimeZoneUtil::timeStampTzToTimeStamp(*(ISC_TIMESTAMP_TZ*) from->dsc_address, cb).timestamp_time; return; default: @@ -1714,18 +1717,18 @@ void CVT_move_common(const dsc* from, dsc* to, DecimalStatus decSt, Callbacks* c return; case dtype_sql_time: - *(ISC_TIME_TZ*) to->dsc_address = TimeZoneUtil::cvtTimeToTimeTz(*(ISC_TIME*) from->dsc_address, cb); + *(ISC_TIME_TZ*) to->dsc_address = TimeZoneUtil::timeToTimeTz(*(ISC_TIME*) from->dsc_address, cb); return; case dtype_timestamp: *(ISC_TIME_TZ*) to->dsc_address = - TimeZoneUtil::cvtTimeStampToTimeTz(*(ISC_TIMESTAMP*) from->dsc_address, cb); + TimeZoneUtil::timeStampToTimeTz(*(ISC_TIMESTAMP*) from->dsc_address, cb); return; case dtype_timestamp_tz: case dtype_ex_timestamp_tz: *(ISC_TIME_TZ*) to->dsc_address = - TimeZoneUtil::cvtTimeStampTzToTimeTz(*(ISC_TIMESTAMP_TZ*) from->dsc_address); + TimeZoneUtil::timeStampTzToTimeTz(*(ISC_TIMESTAMP_TZ*) from->dsc_address); return; case dtype_ex_time_tz: @@ -2197,7 +2200,7 @@ static void datetime_to_text(const dsc* from, dsc* to, Callbacks* cb) case dtype_sql_time_tz: case dtype_ex_time_tz: tzLookup = TimeZoneUtil::decodeTime(*(ISC_TIME_TZ*) from->dsc_address, - true, TimeZoneUtil::NO_OFFSET, cb, ×, &fractions); + true, TimeZoneUtil::NO_OFFSET, ×, &fractions); timezone = ((ISC_TIME_TZ*) from->dsc_address)->time_zone; break; diff --git a/src/dsql/ExprNodes.cpp b/src/dsql/ExprNodes.cpp index f5e908ae0a..78a78a547d 100644 --- a/src/dsql/ExprNodes.cpp +++ b/src/dsql/ExprNodes.cpp @@ -4253,15 +4253,18 @@ dsc* CurrentTimeNode::execute(thread_db* tdbb, jrd_req* request) const // Use the request timestamp. fb_assert(!request->req_gmt_timestamp.isEmpty()); - ISC_TIME time = request->req_gmt_timestamp.value().timestamp_time; - TimeStamp::round_time(time, precision); + ISC_TIMESTAMP_TZ currentTimeStamp; + currentTimeStamp.utc_timestamp = request->req_gmt_timestamp.value(); + currentTimeStamp.time_zone = tdbb->getAttachment()->att_current_timezone; impure->vlu_desc.dsc_dtype = dtype_sql_time_tz; impure->vlu_desc.dsc_length = type_lengths[dtype_sql_time_tz]; impure->vlu_desc.dsc_address = (UCHAR*) &impure->vlu_misc.vlu_sql_time_tz; - impure->vlu_misc.vlu_sql_time_tz.utc_time = time; impure->vlu_misc.vlu_sql_time_tz.time_zone = tdbb->getAttachment()->att_current_timezone; + impure->vlu_misc.vlu_sql_time_tz.utc_time = TimeZoneUtil::timeStampTzToTimeTz(currentTimeStamp).utc_time; + + TimeStamp::round_time(impure->vlu_misc.vlu_sql_time_tz.utc_time, precision); return &impure->vlu_desc; } @@ -5474,7 +5477,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, - false, TimeZoneUtil::NO_OFFSET, &EngineCallbacks::instance, ×, &fractions); + false, TimeZoneUtil::NO_OFFSET, ×, &fractions); break; case blr_extract_timezone_hour: diff --git a/src/jrd/SysFunction.cpp b/src/jrd/SysFunction.cpp index fc19f1a72d..af1f15bc03 100644 --- a/src/jrd/SysFunction.cpp +++ b/src/jrd/SysFunction.cpp @@ -3596,7 +3596,10 @@ dsc* evlDateDiff(thread_db* tdbb, const SysFunction* function, const NestValueAr timestamp1.value().timestamp_date = 0; if (value1Dsc->dsc_dtype == dtype_sql_time && value2Dsc->isDateTimeTz()) - TimeZoneUtil::localTimeToUtc(timestamp1.value().timestamp_time, &EngineCallbacks::instance); + { + TimeZoneUtil::localTimeToUtc(timestamp1.value().timestamp_time, + EngineCallbacks::instance->getSessionTimeZone()); + } break; case dtype_sql_date: @@ -3629,7 +3632,10 @@ dsc* evlDateDiff(thread_db* tdbb, const SysFunction* function, const NestValueAr timestamp2.value().timestamp_date = 0; if (value2Dsc->dsc_dtype == dtype_sql_time && value1Dsc->isDateTimeTz()) - TimeZoneUtil::localTimeToUtc(timestamp2.value().timestamp_time, &EngineCallbacks::instance); + { + TimeZoneUtil::localTimeToUtc(timestamp2.value().timestamp_time, + EngineCallbacks::instance->getSessionTimeZone()); + } break; case dtype_sql_date: diff --git a/src/jrd/inf.cpp b/src/jrd/inf.cpp index 38b1a5a742..331efcdd0c 100644 --- a/src/jrd/inf.cpp +++ b/src/jrd/inf.cpp @@ -488,7 +488,7 @@ void INF_database_info(thread_db* tdbb, case isc_info_creation_date: { - const ISC_TIMESTAMP ts = TimeZoneUtil::cvtTimeStampTzToTimeStamp( + const ISC_TIMESTAMP ts = TimeZoneUtil::timeStampTzToTimeStamp( dbb->dbb_creation_date, &EngineCallbacks::instance); length = INF_convert(ts.timestamp_date, p); diff --git a/src/yvalve/utl.cpp b/src/yvalve/utl.cpp index 333e1639f4..da17724dec 100644 --- a/src/yvalve/utl.cpp +++ b/src/yvalve/utl.cpp @@ -674,8 +674,7 @@ void decodeTimeTzWithFallback(CheckStatusWrapper* status, const ISC_TIME_TZ* tim { tm times; int intFractions; - bool tzLookup = TimeZoneUtil::decodeTime(*timeTz, true, gmtFallback, CVT_commonCallbacks, - ×, &intFractions); + bool tzLookup = TimeZoneUtil::decodeTime(*timeTz, true, gmtFallback, ×, &intFractions); if (hours) *hours = times.tm_hour; @@ -722,7 +721,7 @@ void UtilInterface::encodeTimeTz(CheckStatusWrapper* status, ISC_TIME_TZ* timeTz { timeTz->utc_time = encodeTime(hours, minutes, seconds, fractions); timeTz->time_zone = TimeZoneUtil::parse(timeZone, strlen(timeZone)); - TimeZoneUtil::localTimeToUtc(*timeTz, CVT_commonCallbacks); + TimeZoneUtil::localTimeToUtc(*timeTz); } catch (const Exception& ex) {