Below are the results of analysis performed by AI.
Title: Converting TIME WITH TIME ZONE to TIME (e.g. assigning CURRENT_TIME to a TIME variable or column) costs about 4 times more than the equivalent TIMESTAMP conversion with a region session time zone; TimeZoneUtil::timeTzToTime() performs three ICU calendar computations per value
Summary
Since 4.0, CURRENT_TIME returns TIME WITH TIME ZONE, so assigning it to a TIME (without time zone) variable or column requires a conversion to the session time zone, and some overhead compared to 3.0 is expected.
However, with a region session time zone (here Europe/Warsaw) this conversion costs about 1.3 µs per value (1.46 - 0.14 s per 1 million on 5.0.5, compared with the same loop using LOCALTIME), while the equivalent TIMESTAMP WITH TIME ZONE to TIMESTAMP conversion of CURRENT_TIMESTAMP costs about 0.3 µs (0.45 - 0.14), about 4 times less.
With an offset session time zone (+02:00) the same TIME conversion costs about 0.05-0.07 µs.
The reason is that TimeZoneUtil::timeTzToTime() performs three ICU calendar computations per value, and for CURRENT_TIME the 2nd and 3rd of them cancel each other out (see Profile and Possible improvement below).
4.0.8, 5.0.5 and 6.0.0 behave the same.
This is not the case covered by #7092: its test case assigns CURRENT_TIME to a TIME WITH TIME ZONE variable, and that case is fast now (0.12-0.15 s per 1 million).
It is also a different path than #7854 (CURRENT_TIMESTAMP to TIMESTAMP, fixed by #7859, which caches ICU calendars); the remaining cost of that conversion is the 0.3 µs mentioned above.
The same overhead shows up in DML: inserting 300,000 rows into a table whose TIME column has DEFAULT CURRENT_TIME takes 0.64-0.66 s on 4.0.8, 5.0.5 and 6.0.0, versus 0.23-0.27 s with DEFAULT LOCALTIME (0.21-0.22 s on 3.0.14).
Environment
- Firebird 3.0.14.33856 (release), 4.0.8.3328 (snapshot, 97f2656), 5.0.5.1903 (snapshot, 14bfcdf), 6.0.0.2197 (snapshot, e1e14ac), Linux x64, SuperServer, official tarballs.
- ICU from the OS: libicuuc/libicui18n 74.2; Firebird time zone database 2026e.
- Session time zone
Europe/Warsaw (default, taken from the server environment).
Test case
set stats on;
set term ^;
-- T0: empty loop
execute block as declare n integer = 1; declare d integer; begin while (n < 1000000) do begin n = n + 1; end end^
-- T1: #7092 test case (target TIME WITH TIME ZONE)
execute block as declare n integer = 1; declare d time with time zone; begin while (n < 1000000) do begin n = n + 1; d = current_time; end end^
-- T2: target TIME
execute block as declare n integer = 1; declare d time; begin while (n < 1000000) do begin n = n + 1; d = current_time; end end^
-- T3: LOCALTIME
execute block as declare n integer = 1; declare d time; begin while (n < 1000000) do begin n = n + 1; d = localtime; end end^
-- T4: target TIMESTAMP WITH TIME ZONE
execute block as declare n integer = 1; declare d timestamp with time zone; begin while (n < 1000000) do begin n = n + 1; d = current_timestamp; end end^
-- T5: target TIMESTAMP
execute block as declare n integer = 1; declare d timestamp; begin while (n < 1000000) do begin n = n + 1; d = current_timestamp; end end^
-- T6: LOCALTIMESTAMP
execute block as declare n integer = 1; declare d timestamp; begin while (n < 1000000) do begin n = n + 1; d = localtimestamp; end end^
set term ;^
Results (seconds per 1 million iterations, median of 3 runs, versions interleaved)
| Test |
3.0.14 |
4.0.8 Europe/Warsaw |
5.0.5 Europe/Warsaw |
6.0.0 Europe/Warsaw |
4.0.8 +02:00 |
5.0.5 +02:00 |
6.0.0 +02:00 |
| T0 empty loop |
0.07 |
0.09 |
0.11 |
0.10 |
0.09 |
0.11 |
0.10 |
| T1 TIME WITH TIME ZONE = CURRENT_TIME (#7092) |
n/a |
0.12 |
0.15 |
0.14 |
0.12 |
0.14 |
0.13 |
| T2 TIME = CURRENT_TIME |
0.08 |
1.50 |
1.46 |
1.47 |
0.19 |
0.19 |
0.18 |
| T3 TIME = LOCALTIME |
0.08 |
0.12 |
0.14 |
0.13 |
0.12 |
0.14 |
0.13 |
| T4 TIMESTAMP WITH TIME ZONE = CURRENT_TIMESTAMP |
n/a |
0.12 |
0.14 |
0.13 |
0.12 |
0.14 |
0.13 |
| T5 TIMESTAMP = CURRENT_TIMESTAMP |
0.09 |
0.45 |
0.45 |
0.42 |
0.13 |
0.17 |
0.15 |
| T6 TIMESTAMP = LOCALTIMESTAMP |
0.08 |
0.11 |
0.14 |
0.13 |
0.10 |
0.14 |
0.13 |
recreate global temporary table g_t_cur (id integer, t time default current_time) on commit delete rows;
recreate global temporary table g_t_loc (id integer, t time default localtime) on commit delete rows;
commit;
set stats on;
set term ^;
execute block as declare n integer = 0; begin while (n < 300000) do begin insert into g_t_cur (id) values (:n); n = n + 1; end end^
execute block as declare n integer = 0; begin while (n < 300000) do begin insert into g_t_loc (id) values (:n); n = n + 1; end end^
set term ;^
Profile (5.0.5.1903 with debug symbols, perf record -g during T2)
89.9% CVT_move_common
88.7% Firebird::TimeZoneUtil::timeTzToTime(ISC_TIME_TZ const&, Firebird::Callbacks*)
63.4% icu_74::Calendar::computeFields
48.0% Firebird::TimeZoneUtil::localTimeStampToUtc(ISC_TIMESTAMP_TZ&)
39.0% Firebird::TimeZoneUtil::decodeTimeStamp(...)
20.5% Firebird::TimeZoneUtil::timeStampTzToTimeStamp(...)
23.6% (self) icu_74::ClockMath::floorDivide
20.8% (self) uprv_floor_74
timeTzToTime() (src/common/TimeZoneUtil.cpp, the same in v4.0-release, v5.0-release and master):
decodeTimeStamp(tsTz, false, NO_OFFSET, ×, &fractions); // 1st ICU computation (UTC at TIME_TZ_BASE_DATE -> local)
tsTz.utc_timestamp.timestamp_date = cb->getLocalDate();
tsTz.utc_timestamp.timestamp_time = TimeStamp::encode_time(...);
localTimeStampToUtc(tsTz); // 2nd (local at current date -> UTC)
return timeStampTzToTimeStamp(tsTz, cb->getSessionTimeZone())...; // 3rd (UTC -> session zone)
So every value goes through three full ICU calendar field computations, while TIMESTAMP WITH TIME ZONE to TIMESTAMP needs one.
Possible improvement
When the source zone of the TIME WITH TIME ZONE value equals the session time zone, which is always the case for CURRENT_TIME, the 2nd and 3rd steps convert a local time to UTC and back in the same zone on the same date.
Apart from local times that fall into a DST gap, they return the result of the 1st step unchanged, so they could be skipped.
Caching the converted value per request (as #7092 did for CURRENT_TIME itself) would also cover the common CURRENT_TIME case.
Context
Applications written for 3.0 commonly assign CURRENT_TIME and CURRENT_TIMESTAMP to TIME / TIMESTAMP columns and variables (defaults, triggers, procedures).
Replacing them with LOCALTIME / LOCALTIMESTAMP removes the overhead, but existing code keeps working without errors and only becomes slower after the upgrade, so it is easy to miss.
Below are the results of analysis performed by AI.
Title: Converting TIME WITH TIME ZONE to TIME (e.g. assigning CURRENT_TIME to a TIME variable or column) costs about 4 times more than the equivalent TIMESTAMP conversion with a region session time zone;
TimeZoneUtil::timeTzToTime()performs three ICU calendar computations per valueSummary
Since 4.0,
CURRENT_TIMEreturnsTIME WITH TIME ZONE, so assigning it to aTIME(without time zone) variable or column requires a conversion to the session time zone, and some overhead compared to 3.0 is expected. However, with a region session time zone (hereEurope/Warsaw) this conversion costs about 1.3 µs per value (1.46 - 0.14 s per 1 million on 5.0.5, compared with the same loop usingLOCALTIME), while the equivalentTIMESTAMP WITH TIME ZONEtoTIMESTAMPconversion ofCURRENT_TIMESTAMPcosts about 0.3 µs (0.45 - 0.14), about 4 times less. With an offset session time zone (+02:00) the same TIME conversion costs about 0.05-0.07 µs. The reason is thatTimeZoneUtil::timeTzToTime()performs three ICU calendar computations per value, and forCURRENT_TIMEthe 2nd and 3rd of them cancel each other out (see Profile and Possible improvement below). 4.0.8, 5.0.5 and 6.0.0 behave the same.This is not the case covered by #7092: its test case assigns
CURRENT_TIMEto aTIME WITH TIME ZONEvariable, and that case is fast now (0.12-0.15 s per 1 million). It is also a different path than #7854 (CURRENT_TIMESTAMPtoTIMESTAMP, fixed by #7859, which caches ICU calendars); the remaining cost of that conversion is the 0.3 µs mentioned above.The same overhead shows up in DML: inserting 300,000 rows into a table whose
TIMEcolumn hasDEFAULT CURRENT_TIMEtakes 0.64-0.66 s on 4.0.8, 5.0.5 and 6.0.0, versus 0.23-0.27 s withDEFAULT LOCALTIME(0.21-0.22 s on 3.0.14).Environment
Europe/Warsaw(default, taken from the server environment).Test case
Results (seconds per 1 million iterations, median of 3 runs, versions interleaved)
Profile (5.0.5.1903 with debug symbols,
perf record -gduring T2)timeTzToTime()(src/common/TimeZoneUtil.cpp, the same in v4.0-release, v5.0-release and master):So every value goes through three full ICU calendar field computations, while
TIMESTAMP WITH TIME ZONEtoTIMESTAMPneeds one.Possible improvement
When the source zone of the
TIME WITH TIME ZONEvalue equals the session time zone, which is always the case forCURRENT_TIME, the 2nd and 3rd steps convert a local time to UTC and back in the same zone on the same date. Apart from local times that fall into a DST gap, they return the result of the 1st step unchanged, so they could be skipped. Caching the converted value per request (as #7092 did forCURRENT_TIMEitself) would also cover the commonCURRENT_TIMEcase.Context
Applications written for 3.0 commonly assign
CURRENT_TIMEandCURRENT_TIMESTAMPtoTIME/TIMESTAMPcolumns and variables (defaults, triggers, procedures). Replacing them withLOCALTIME/LOCALTIMESTAMPremoves the overhead, but existing code keeps working without errors and only becomes slower after the upgrade, so it is easy to miss.