Sometimes, you get simple questions but the most obvious answer is wrong. Yesterday, my colleague Dion had one for us;
How many hours does one day have?
Nope, 24 is not the correct answer. Well, at least in my timezone… We have something delightful that’s called “Daylight Savings”. So, one day a year has 23 hours and another has 25.
So, how do we know – using PL/SQL- how many hours a day has?
UTC
We figured our problem could be solved by converting our date to the UTC timezone. UTC doesn’t have Daylight Savings, so if we convert our date to UTC, and then calculate the hours between (UTC ALWAYS has 24 hours luckily) we get the correct hours.
And if we sent a fake date & hour (i.e. with daylight savings, 2 am turns to 3 am) we ran into an ORA-01878 – Specified field not found in datetime. So, we just added a small bonus so this wouldn’t affect our function if someone decided to put in some wrong date and time, cause you can’t always now when Daylight savings is happening.
So, when the ORA-01878 is happening, we catch it in an EXCEPTION handler, take one day earlier, cast it to UTC and added the day we removed before casting.
Code
set serveroutput on
clear screen
DECLARE
l_daylightstarts DATE := to_date('27/03/2022', 'DD/MM/YYYY' );
l_daylightends DATE := to_date('30/10/2022', 'DD/MM/YYYY' );
l_regularday DATE := to_date('18/03/2022', 'DD/MM/YYYY' );
l_faketime DATE := to_date('27/03/2022 02:00', 'DD/MM/YYYY HH24:MI' );
--Convert local time to UTC.
FUNCTION fnc_local_to_utc(p_local IN DATE) RETURN DATE IS
BEGIN
return CAST((FROM_TZ(p_local, 'Europe/Berlin') AT TIME ZONE 'UTC') AS DATE);
EXCEPTION
--Needed for date times that don't exist in timezone
WHEN OTHERS THEN
return CAST((FROM_TZ(p_local - 1, 'Europe/Berlin') AT TIME ZONE 'UTC') AS DATE) +1;
END;
--Calculate hours of day
PROCEDURE prc_hours_of_day(p_day IN DATE) IS
BEGIN
dbms_output.put_line( p_day
|| ' has: '
|| round((fnc_local_to_utc(p_day + 1) - fnc_local_to_utc( p_day ))*24)
|| ' hours. ');
END;
BEGIN
prc_hours_of_day(l_daylightstarts);
prc_hours_of_day(l_daylightends);
prc_hours_of_day(l_regularday);
--Just to be sure it also works for times that don't exist.
prc_hours_of_day(l_faketime);
end;
/
---------------------------------
27-03-2022 00:00:00 has: 23 hours.
30-10-2022 00:00:00 has: 25 hours.
18-03-2022 00:00:00 has: 24 hours.
27-03-2022 02:00:00 has: 23 hours.