Tuesday, May 2, 2017

Oracle Datetime (1) - Concepts

(1)-Concepts      (2)-Examples      (3)-Assignments      (4)-Comparisons      (5)-SQL Arithmetic      (6)-PLSQL Arithmetic


This small Oracle Datetime Cookbook is made of a series of 6 Blogs:
Oracle Datetime (1) - Concepts
Oracle Datetime (2) - Examples
Oracle Datetime (3) - Assignments
Oracle Datetime (4) - Comparisons
Oracle Datetime (5) - SQL Arithmetic
Oracle Datetime (6) - PLSQL Arithmetic

1. Data Types


Oracle Datetime consists of 4 basic Data Types:
DATE                             stores as a literal constant, no TimeZone. 
TIMESTAMP                        stores as a literal constant, no TimeZone. 
TIMESTAMP WITH TIME ZONE         stores with explicit TimeZone.
TIMESTAMP WITH LOCAL TIME ZONE   stores with implicit sessiontimezone as default TimeZone.
The difference between DATE and TIMESTAMP is precision (fractional part of the SECOND), and the difference between TIMESTAMP WITH TIME ZONE and TIMESTAMP WITH LOCAL TIME ZONE is the later one with a default TimeZone, but the common usage is same.

Therefore, there are two essential Data Types: TIMESTAMP and TIMESTAMP WITH TIME ZONE. Even TIMESTAMP can be considered as a Subtype of TIMESTAMP WITH TIME ZONE without time zone.


2. Oracle built-in Functions

SYSDATE            returns current Datetime in the server (OS) time zone in datatype DATE.
SYSTIMESTAMP       returns current Datetime in the server (OS) time zone (Unix TZ variable) 
                   in datatype TIMESTAMP WITH TIME ZONE.

CURRENT_DATE       returns current Datetime in the session time zone in datatype DATE.
CURRENT_TIMESTAMP  returns current Datetime in the session time zone in datatype 
                   TIMESTAMP WITH TIME ZONE.

LOCALTIMESTAMP     returns the current Datetime in the session time zone in datatype TIMESTAMP.
The first two are from OS Server point of view, irrelevant to Oracle, just like date command.
SYSDATE is SYSTIMESTAMP by dropping Time Zone info.

The next two are the counterparts from Oracle session point of view, depending on each Oracle session setting.
CURRENT_DATE and LOCALTIMESTAMP are CURRENT_TIMESTAMP by removing Time Zone info.

The last one is an Oracle special mixed variant (not in SQL-92), API similar to TIMESTAMP WITH TIME ZONE, internal storage as TIMESTAMP relative to DBTIMEZONE (which is invented only for TIMESTAMP WITH LOCAL TIME ZONE). This automatic conversion probably implicates certain performance difference, as tested, it is about 30% (1000,000 calls takes less than 1 second).

In fact, LOCALTIMESTAMP is a cast of CURRENT_TIMESTAMP as data type timestamp_unconstrained (see Oracle package SYS.STANDARD spec and body), whereas internally timestamp_unconstrained is defined as Typ=180.

type TIMESTAMP is new DATE_BASE;
SUBTYPE TIMESTAMP_UNCONSTRAINED IS TIMESTAMP(9);
SUBTYPE TIMESTAMP_TZ_UNCONSTRAINED IS TIMESTAMP(9) WITH TIME ZONE;

FUNCTION localtimestamp RETURN timestamp_unconstrained
IS t timestamp_tz_unconstrained := current_timestamp;
BEGIN
 RETURN (cast(t AS timestamp_unconstrained));
END;
  
declare
  l_tz_unconstrained  timestamp_unconstrained := (cast(current_timestamp AS timestamp_unconstrained));
  l_dump              varchar2(100);
begin
  select (dump(l_tz_unconstrained, 1016)) into l_dump from dual;
  dbms_output.put_line('timestamp_unconstrained DUMP: '||l_dump);
end;
/

timestamp_unconstrained DUMP: Typ=180 Len=11: 78,78,b,8,b,23,20,5,2a,38,48

For performance discussion on datetime indexing, see Blog: Tony’s Tirade against TIMESTAMP WITH TIME ZONE

Here is a basic test and its output:

col dbtimezone        format a15
col sessiontimezone   format a15
col sysdate           format a25
col systimestamp      format a35
col current_date      format a25
col current_timestamp format a50
col localtimestamp    format a35

ALTER SESSION SET NLS_DATE_FORMAT         ='YYYY*MON*DD HH24:MI:SS';    
ALTER SESSION SET NLS_TIMESTAMP_FORMAT    ='YYYY*MON*DD HH24:MI:SS.FF9';
ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT ='YYYY-MON-DD HH24:MI:SS.FF9 TZR TZD';

-- ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT ='SYYYY-MON-DD HH24:MI:SS.ff9 TZR TZD'; 
-- ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT ='YYYY-MON-DD HH24:MI:SS.ff9 TZR TZD AD'; 

alter session set time_zone = 'Europe/Paris';

select dbtimezone, sessiontimezone,
    sysdate, systimestamp,
    current_date, current_timestamp, localtimestamp
from dual;

DBTIMEZONE          : +01:00
SESSIONTIMEZONE     : Europe/Paris
SYSDATE             : 2017*MAY*02 07:52:50
SYSTIMESTAMP        : 2017-MAY-02 07:52:50.123456000 +02:00
CURRENT_DATE        : 2017*MAY*02 07:52:50
CURRENT_TIMESTAMP   : 2017-MAY-02 07:52:50.123458000 EUROPE/PARIS CEST
LOCALTIMESTAMP      : 2017*MAY*02 07:52:50.123458000

Oracle MOS Note*: The Priority of NLS Parameters Explained (Where To Define NLS Parameters) (Doc ID 241047.1)
  NLS_TIME_FORMAT and NLS_TIME_TZ_FORMAT, are currently used for internal purposes only. 
  We strongly suggest to NOT define them. If they are visible in the NLS_INSTANCE_PARAMETERS 
  then please DO remove them and bounce the database. If set they may cause errors 
  like ORA-1821: date format not recognized, ORA-6512: at "SYS.DBMS_SCHEDULER" 
  when submitting / running DBMS_SCHEDULER jobs.
    ALTER SESSION SET NLS_TIME_FORMAT         ='HH.MI.SSXFF AM';
    ALTER SESSION SET NLS_TIME_TZ_FORMAT      ='HH.MI.SSXFF AM TZR';

3. Time Zone and Time Zone Abbreviation


(a). Time Zone can be represented in either Named TZ (TZR), or Offset TZ ( TZH:TZM) format
for example:

alter session set time_zone='Europe/Paris';
alter session set time_zone='+01:00';

Named TZ is DST aware; If the Named region is DST sensitive, it is varied between Standard and DST. Otherwise it is static with a constant offset. Offset TZ is DST unaware with a constant offset.

(b). Time Zone Abbreviation is DST unaware, defined with a constant offset
It is used to distinguish ambiguous overlap timestamp during transit from DST to Standard when ERROR_ON_OVERLAP_TIME is enabled. If ERROR_ON_OVERLAP_TIME is disabled, it takes Standard as the default in case of ambiguity.

Here is an example:

alter session set ERROR_ON_OVERLAP_TIME=FALSE;
select TIMESTAMP '2017-10-29 02:52:00 Europe/Paris' from dual;
  2017-OCT-29 02:52:00 EUROPE/PARIS CET 

alter session set ERROR_ON_OVERLAP_TIME=TRUE;
select TIMESTAMP '2017-10-29 02:52:00 Europe/Paris' from dual;
   ORA-01883: overlap was disabled during a region transition

select TIMESTAMP '2017-10-29 02:52:00 Europe/Paris CEST',
       TIMESTAMP '2017-10-29 02:52:00 Europe/Paris CET' from dual;
  2017-OCT-29 02:52:00 EUROPE/PARIS CEST                         
  2017-OCT-29 02:52:00 EUROPE/PARIS CET

CET is a special twofold shortcut. It denotes a Time Zone Region (TZR, such as Europe/Paris), and it also denotes a static Time Zone Abbreviation (TZD: Time Zone Designator, such as CEST). The difference is that TZR CET is DST aware, in Winter it is (UTC +1), in Summer, it is (UTC +2). Whereas TZD CET is static, it represents fixed Central European Time (UTC +1). So in Winter, TZR CET is TZD CET (UTC +1); whereas in Summer, TZR CET is TZD CEST (UTC +2). We can say that TZD is same as Offset TZ ( TZH:TZM), or TZD is a symbolic notation of TZH:TZM. For example,

select TIMESTAMP '2017-10-29 02:52:00 CET CET' from dual;
  2017-OCT-29 02:52:00 CET CET

select * from v$timezone_names where tzname = 'CET' and tzabbrev = 'CET';

TZNAME    TZABBREV 
--------- ---------
CET       CET 


select TIMESTAMP '2017-10-28 02:52:00 CET CEST',
       TIMESTAMP '2017-10-28 02:52:00 Europe/Paris CEST',
       TIMESTAMP '2017-10-30 02:52:00 CET CET',
       TIMESTAMP '2017-10-30 02:52:00 Europe/Paris CET' from dual;

2017-OCT-28 02:52:00 CET CEST       
2017-OCT-28 02:52:00 EUROPE/PARIS CEST
2017-OCT-30 02:52:00 CET CET  
2017-OCT-30 02:52:00 EUROPE/PARIS CET

4. Server Time Zone


Oracle does not have support to get Named TZ of Server. SYSTIMESTAMP output has an offset from UTC, defined not to include an actual named timezone, for example,


select extract(TIMEZONE_OFFSET from systimestamp), extract(TIMEZONE_REGION from systimestamp) from dual;
  +000000000 02:00:00.000000000  
   UNKNOWN

There exists an internal implementation in DBMS_SCHEDULER to get Named TZ (one DBMS_SCHEDULER attribute):
    select DBMS_SCHEDULER.GET_SYS_TIME_ZONE_NAME from dual;
    select * from dba_scheduler_global_attribute where attribute_name='DEFAULT_TIMEZONE';

It works in AIX, Solaris, Linux in most case, except HP.

UNIX environment variable TZ has two TZ format: POSIX and Olson.

It seems that Java is able to get a unified Name TZ with Class TimeZone (see JDK 8 "tzdb.dat"):

import java.util.TimeZone;
public class TimeZoneTest {
   public static void main(String args[]) {
      TimeZone tz = TimeZone.getDefault();
      // TimeZone tz = Calendar.getInstance().getTimeZone();
      System.out.println("ID=" + tz.getID());
      System.out.println("Name=" + tz.getDisplayName()); 
   }
}

Here one set of test results on different UNIX:
(Unix TZ environment format: AIX in POSIX. Linux, Solaris in Olson. HP in special format)

OS AIX Solaris Linux HP
echo $TZ CET-1CEST,M3.5.0,M10.5.0 Europe/Zurich Europe/Zurich MET-1METDST
dbms_scheduler.get_sys_time_zone_name Europe/Vienna Europe/Zurich Europe/Zurich
TimeZoneTest.java ID Europe/Paris Europe/Zurich Europe/Zurich Europe/Paris
TimeZoneTest.java Name Central European Time Central European Time Central European Time Central European Time

Offset TZ in SYSTIMESTAMP is determined by the shell TZ of UNIX process, from which the Oracle session is spawned. Therefore, we can have 3 different connection scenarios, each with its own shell TZ. If they are all configured differently, we may end up 3 different Offset TZ in SYSTIMESTAMP (although this is not a recommended practice):
(1). TZ of UNIX process which starts up DB. 
       This TZ is used by Oracle processes to get SYSTIMESTAMP and SYSDATE when recording logs and traces.
       Content in alert.log and trace files are stamped in SYSDATE.
       Datetime in alert/log.xml is enhanced and marked in SYSTIMESTAMP, i.e, with Time Zone info.
     
     DBMS_SCHEDULER.GET_SYS_TIME_ZONE_NAME returns this shell TZ (in Named TZ).

     By the way, Oracle 12.2.0.1 introduced UNIFORM_LOG_TIMESTAMP_FORMAT 
     to specify a uniform timestamp format in trace (.trc) files and alert log. 
     
(2). TZ of UNIX process which starts a local connection via Bequeath Protocol (sqlplus / as sysdba)

(3). TZ of UNIX process which starts TNS Listener, and Oracle session is connected via Listener.


DBMS_SCHEDULER.GET_SYS_TIME_ZONE_NAME Test


DBMS_SCHEDULER.GET_SYS_TIME_ZONE_NAME is a call of subroutine "jsxsgetsystimezonename" in SYS.DBMS_ISCHED.GET_SYS_TIME_ZONE_NAME.
Oracle MOS "DBMS_SCHEDULER or DBMS_JOB And DST / Timezones Explained (Doc ID 467722.1)" wrote:

  SELECT DBMS_SCHEDULER.GET_SYS_TIME_ZONE_NAME FROM DUAL; is not related to the DEFAULT_TIMEZONE.
  (select value from dba_scheduler_global_attribute where attribute_name='DEFAULT_TIMEZONE';)
  DBMS_SCHEDULER.GET_SYS_TIME_ZONE_NAME provides the TimeZone set on the OS level trough the TZ variable, 
  it will return the named timezone only if the OS TZ setting is also known in Oracle.
  Only when no OS TZ variable is set it will report the DEFAULT_TIMEZONE.
We will make 4 different TZ tests and watch the output (The output contains more info and not all fits to above MOS Docu).
The test is performed in Linux - Oracle 19.17 with following time and DEFAULT_TIMEZONE settings:

$ timedatectl
            Local time: Sun 2023-06-11 13:57:12 GMT
        Universal time: Sun 2023-06-11 13:57:12 UTC
              RTC time: Sun 2023-06-11 13:57:12
             Time zone: Etc/GMT (GMT, +0000)
           NTP enabled: no
      NTP synchronized: yes
       RTC in local TZ: no
            DST active: n/a

SQL> select value from dba_scheduler_global_attribute where attribute_name='DEFAULT_TIMEZONE';
            Europe/Zurich


Case-1 unset TZ


GET_SYS_TIME_ZONE_NAME return depends on session time_zone setting.

--===================================== unset TZ ===============================================

$ unset TZ
$ export TZ

SQL> startup force

SQL> host echo $TZ       -- in case of PC Window Sqlplus, check Date&Time -> Time Zone setting
       -- no value return

SQL> select value from dba_scheduler_global_attribute where attribute_name='DEFAULT_TIMEZONE';
            Europe/Zurich

SQL> select SESSIONTIMEZONE, sys.DBMS_SCHEDULER.GET_SYS_TIME_ZONE_NAME from dual;
            +00:00           Etc/GMT
  
SQL> alter session set time_zone = 'Europe/Paris';
SQL> select SESSIONTIMEZONE, sys.DBMS_SCHEDULER.GET_SYS_TIME_ZONE_NAME from dual;
            Europe/Paris     -- no value for GET_SYS_TIME_ZONE_NAME
  
SQL> alter session set time_zone = 'Etc/GMT';
SQL> select SESSIONTIMEZONE, sys.DBMS_SCHEDULER.GET_SYS_TIME_ZONE_NAME from dual;
            +00:00           Etc/GMT

SQL> alter session set time_zone = '+02:00';
SQL> select SESSIONTIMEZONE, sys.DBMS_SCHEDULER.GET_SYS_TIME_ZONE_NAME from dual;
            +02:00           -- no value for GET_SYS_TIME_ZONE_NAME

SQL> alter session set time_zone = '+00:00';
SQL> select SESSIONTIMEZONE, sys.DBMS_SCHEDULER.GET_SYS_TIME_ZONE_NAME from dual;
            +00:00           Etc/GMT


Case-2 TZ=""


All GET_SYS_TIME_ZONE_NAME returns "UTC".

--=============== TZ="" (without: export TZ="", output can be different) ===================
$ set TZ=""
$ export TZ=""

SQL> startup force

SQL> host echo $TZ       -- in case of PC Window Sqlplus, check Date&Time -> Time Zone setting
       -- no value return

SQL> select SESSIONTIMEZONE, sys.DBMS_SCHEDULER.GET_SYS_TIME_ZONE_NAME from dual;
            +00:00           UTC
  
SQL> alter session set time_zone = 'Europe/Paris';
SQL> select SESSIONTIMEZONE, sys.DBMS_SCHEDULER.GET_SYS_TIME_ZONE_NAME from dual;
            +00:00           UTC
  
SQL> alter session set time_zone = 'Etc/GMT';
SQL> select SESSIONTIMEZONE, sys.DBMS_SCHEDULER.GET_SYS_TIME_ZONE_NAME from dual;
            +00:00           UTC

SQL> alter session set time_zone = '+02:00';
SQL> select SESSIONTIMEZONE, sys.DBMS_SCHEDULER.GET_SYS_TIME_ZONE_NAME from dual;
            +00:00           UTC

SQL> alter session set time_zone = '+00:00';
SQL> select SESSIONTIMEZONE, sys.DBMS_SCHEDULER.GET_SYS_TIME_ZONE_NAME from dual;
            +00:00           UTC

SQL> alter session set time_zone = 'UTC';
SQL> select SESSIONTIMEZONE, sys.DBMS_SCHEDULER.GET_SYS_TIME_ZONE_NAME from dual;
            UTC              UTC


Case-3 TZ="Europe/Paris"


All GET_SYS_TIME_ZONE_NAME returns "Europe/Paris".

--===================================== TZ="Europe/Paris" ====================================
$ set TZ="Europe/Paris"
$ export TZ="Europe/Paris"

SQL> startup force

SQL> host echo $TZ       -- in case of PC Window Sqlplus, check Date&Time -> Time Zone setting
       Europe/Paris

SQL> select SESSIONTIMEZONE, sys.DBMS_SCHEDULER.GET_SYS_TIME_ZONE_NAME from dual;
            +02:00           Europe/Paris
  
SQL> alter session set time_zone = 'Europe/Paris';
SQL> select SESSIONTIMEZONE, sys.DBMS_SCHEDULER.GET_SYS_TIME_ZONE_NAME from dual;
            Europe/Paris     Europe/Paris
  
SQL> alter session set time_zone = 'Etc/GMT';
SQL> select SESSIONTIMEZONE, sys.DBMS_SCHEDULER.GET_SYS_TIME_ZONE_NAME from dual;
           Etc/GMT           Europe/Paris

SQL> alter session set time_zone = '+02:00';
SQL> select SESSIONTIMEZONE, sys.DBMS_SCHEDULER.GET_SYS_TIME_ZONE_NAME from dual;
            +02:00           Europe/Paris

SQL> alter session set time_zone = '+00:00';
SQL> select SESSIONTIMEZONE, sys.DBMS_SCHEDULER.GET_SYS_TIME_ZONE_NAME from dual;
            +00:00           Europe/Paris


Case-4 TZ="Europe/Pariz" (wrong name: Pariz)


There are no values for GET_SYS_TIME_ZONE_NAME.

--============================= TZ="Europe/Pariz" (wrong name: Pariz) ========================
$ set TZ="Europe/Pariz"
$ export TZ="Europe/Pariz"

SQL> startup force

SQL> host echo $TZ       -- in case of PC Window Sqlplus, check Date&Time -> Time Zone setting
       Europe/Pariz

SQL> select SESSIONTIMEZONE, sys.DBMS_SCHEDULER.GET_SYS_TIME_ZONE_NAME from dual;
            +00:00           -- no value for GET_SYS_TIME_ZONE_NAME
  
SQL> alter session set time_zone = 'Europe/Paris';
SQL> select SESSIONTIMEZONE, sys.DBMS_SCHEDULER.GET_SYS_TIME_ZONE_NAME from dual;
            Europe/Paris     -- no value for GET_SYS_TIME_ZONE_NAME
  
SQL> alter session set time_zone = 'Etc/GMT';
SQL> select SESSIONTIMEZONE, sys.DBMS_SCHEDULER.GET_SYS_TIME_ZONE_NAME from dual;
           Etc/GMT           -- no value for GET_SYS_TIME_ZONE_NAME

SQL> alter session set time_zone = '+02:00';
SQL> select SESSIONTIMEZONE, sys.DBMS_SCHEDULER.GET_SYS_TIME_ZONE_NAME from dual;
            +02:00           -- no value for GET_SYS_TIME_ZONE_NAME

SQL> alter session set time_zone = '+00:00';
SQL> select SESSIONTIMEZONE, sys.DBMS_SCHEDULER.GET_SYS_TIME_ZONE_NAME from dual;
            +00:00           -- no value for GET_SYS_TIME_ZONE_NAME


gdb debug


For Case-1 unset TZ, if we gdb the Sqlplus session, in case of DBMS_SCHEDULER.GET_SYS_TIME_ZONE_NAME returning "Etc/GMT", we can see following output.
(no Breakpoint stopped in case of "no value return")

break *OCIStringAssignText
break *jsxsGetSysTimeZoneName+938
display /s $rbx
display /s $rsp

Breakpoint 2, 0x0000000004b50f00 in OCIStringAssignText ()
2: x/s $rsp  0x7ffc9e55bd28:    "\372Yt\f"
1: x/s $rbx  0xa5dc026d:        "Etc/GMT"
(gdb) c
Continuing.

Breakpoint 1, 0x000000000c7459fa in jsxsGetSysTimeZoneName ()
2: x/s $rsp  0x7ffc9e55bd30:    "GMT"
1: x/s $rbx  0xa5dc026d:        "Etc/GMT"
The call stack and part of jsxsGetSysTimeZoneName code lines are as follows:

Breakpoint 1, 0x0000000004313c70 in LdiDateComparei ()
(gdb) bt
#0  0x0000000004313c70 in LdiDateComparei ()
#1  0x000000000434768a in ltzGetIndex ()
#2  0x0000000004347560 in ltzGet ()
#3  0x0000000004327884 in sLdiGetLazyDt_int ()
#4  0x00000000043274ec in sLdiGetDate ()
#5  0x000000000c745750 in jsxsGetSysTimeZoneName ()     
                  0x000000000c745716 <+198>:	 callq  0x43274a0 
                  ...
                  0x000000000c74585c <+524>:	 callq  0x1032ad40 
                  ...
                  0x000000000c74599c <+844>:	callq  0x1032ad40 
                  
                  0x000000000c7459d4 <+900>:   jne    0xc7459e1 
                  0x000000000c7459d6 <+902>:   mov    %rdx,%rax
                  0x000000000c7459d9 <+905>:   add    %rcx,%rdx
                  0x000000000c7459dc <+908>:   callq  0x6fcac60 <__intel_sse2_strlen>
                  0x000000000c7459e1 <+913>:   mov    %rbx,%rdx
                  0x000000000c7459e4 <+916>:   mov    %eax,%ecx
                  0x000000000c7459e6 <+918>:   mov    -0x78(%rbp),%rdi
                  0x000000000c7459ea <+922>:   lea    -0x98(%rbp),%r8
                  0x000000000c7459f1 <+929>:   mov    -0x38(%rbp),%rsi
                  0x000000000c7459f5 <+933>:   callq  0x4b50f00 
               => 0x000000000c7459fa <+938>:   xor    %eax,%eax
                  0x000000000c7459fc <+940>:   mov    %ax,(%r15)
                  0x000000000c745a00 <+944>:   lea    -0x28(%rbp),%rsp
                  0x000000000c745a04 <+948>:   pop    %rbx
                  0x000000000c745a05 <+949>:   pop    %r15
                  0x000000000c745a07 <+951>:   pop    %r14
                  0x000000000c745a09 <+953>:   pop    %r13
                  0x000000000c745a0b <+955>:   pop    %r12
                  0x000000000c745a0d <+957>:   pop    %rbp
                  0x000000000c745a0e <+958>:   retq
#6  0x0000000005833a3d in spefcmpa ()
#7  0x000000000580df7b in spefmccallstd ()
#8  0x00000000057aed2b in peftrusted ()
#9  0x000000000426b69d in psdexsp ()
#10 0x0000000012de0e84 in rpiswu2 ()
#11 0x000000000384646a in kxe_push_env_internal_pp_ ()
#12 0x00000000038b9675 in kkx_push_env_for_ICD_for_new_session ()
#13 0x000000000426b083 in psdextp ()
#14 0x00000000057a8bb7 in pefccal ()
#15 0x00000000057a846f in pefcal ()
#16 0x000000000566b34b in pevm_FCAL ()
#17 0x000000000564c8ae in pfrinstr_FCAL ()
#18 0x00000000130ea88c in pfrrun_no_tool ()
#19 0x00000000130e91f6 in pfrrun ()
#20 0x00000000130f4dbb in plsql_run ()

DBMS_SCHEDULER.SET_SCHEDULER_ATTRIBUTE Test (on Linux)


-- $echo $TZ
--    Europe/Zurich

exec DBMS_SCHEDULER.SET_SCHEDULER_ATTRIBUTE ('DEFAULT_TIMEZONE', 'Europe/Zurich');
select DBMS_SCHEDULER.GET_SYS_TIME_ZONE_NAME value from dual;
  -- 'Europe/Zurich'
select value from dba_scheduler_global_attribute where attribute_name='DEFAULT_TIMEZONE';
  -- 'Europe/Zurich'

exec DBMS_SCHEDULER.SET_SCHEDULER_ATTRIBUTE ('DEFAULT_TIMEZONE', 'Europe/London');
select DBMS_SCHEDULER.GET_SYS_TIME_ZONE_NAME value from dual;
  -- 'Europe/Zurich'
select value from dba_scheduler_global_attribute where attribute_name='DEFAULT_TIMEZONE';
  -- 'Europe/London'

exec DBMS_SCHEDULER.SET_SCHEDULER_ATTRIBUTE ('DEFAULT_TIMEZONE', NULL);
select DBMS_SCHEDULER.GET_SYS_TIME_ZONE_NAME value from dual;
  -- 'Europe/Zurich'
select value from dba_scheduler_global_attribute where attribute_name='DEFAULT_TIMEZONE';
  -- null return


5. ORA-01878: specified field not found in datetime or interval


ORA-01878 recurs due to illegal datetime obtained in DST aware Named TZ. Often datetime is initiated by Oracle built-in functions (sysdate, systimestamp, current_date, current_timestamp, localtimestamp), and then manipulated by arithmetic operators. All of year application runs without problem except once or twice it gets runtime ORA-01878 during Standard/DST switches, which is hard to reproduce, and hence held off by wait and hesitate to next switch.

(a). Time Zone Appending
when assigning from TIMESTAMP (or DATE) to TIMESTAMP WITH LOCAL TIME ZONE, default sessiontimezone is appended, and results in a non-exist datetime. For example,

alter session set time_zone = 'Europe/Paris';
declare
  l_date         DATE := to_date('2017-03-26 01:52:00', 'yyyy-mm-dd hh24:mi:ss'); 
  l_timestp_ltz  TIMESTAMP WITH LOCAL TIME ZONE;
begin
  l_timestp_ltz := l_date + 8/1440;
end;
/

ORA-01878: specified field not found in datetime or interval
ORA-06512: at line 5


Note: Clock Changes in Paris, Île-de-France, France 2017
  26 Mar 2017 - Daylight Saving Time Started
     When local standard time was about to reach
     Sunday, 26 March 2017, 02:00:00 clocks were turned forward 1 hour to 
     Sunday, 26 March 2017, 03:00:00 local daylight time instead.

  29 Oct 2017 - Daylight Saving Time Ended
     When local daylight time was about to reach
     Sunday, 29 October 2017, 03:00:00 clocks were turned backward 1 hour to 
     Sunday, 29 October 2017, 02:00:00 local standard time instead.

Following test code shows that localtimestamp evolution in expression (on the fly) is session time_zone irrelevant. However, when it refers to a SQL or PLSQL defined localtimestamp column or variable, it is appended with session time_zone, i.e, landed on that time_zone, hence has to be validated againt that time_zone.

create table ltz_tab(loc_ltz TIMESTAMP WITH LOCAL TIME ZONE);
insert into ltz_tab values(localtimestamp);
commit;     

alter session set time_zone = 'Europe/Paris';

declare
  l_intv      interval day (6) to second (0) :=  localtimestamp - timestamp'2017-03-26 02:17:39'; 
  l_ltz_new   timestamp with local time zone;
  l_val       number;
  l_boolean   boolean;
  l_ltz_init  timestamp with local time zone;
begin
  -- NO ORA-01878 in expression (on the fly)
  dbms_output.put_line('ltz calc = '||(localtimestamp - l_intv));  
  
  -- ORA-01878 when SQL compare
  begin
    select 1 into l_val from ltz_tab where loc_ltz > (localtimestamp - l_intv);  
  exception when others then dbms_output.put_line('ltz SQL compare: '||SQLERRM);
  end;
  
  -- ORA-01878 in SQL assigment
  begin
    select (localtimestamp - l_intv) into l_ltz_new from dual;       
  exception when others then dbms_output.put_line('ltz SQL assigment: '||SQLERRM);
  end;
  
  -- ORA-01878 in PL/SQL compare
  begin
    l_boolean := l_ltz_new  > (localtimestamp - l_intv);                         
  exception when others then dbms_output.put_line('ltz PL/SQL compare: '||SQLERRM);
  end;
  
  -- ORA-01878 in PL/SQL assigment
  begin
    l_ltz_new  := (localtimestamp - l_intv);                         
  exception when others then dbms_output.put_line('ltz PL/SQL assigment: '||SQLERRM);
  end;
  
  -- workaround-1, using time zone aware variable, '2017-03-26 02:17:39' mapped to '2017-03-26 03:17:39'
  l_ltz_init := localtimestamp;
  l_ltz_new  := l_ltz_init - l_intv; 
  dbms_output.put_line('ltz (workaround-1) = '||l_ltz_new); 
  
  -- workaround-2, using time zone aware function. current_timestamp is localtimestamp with session time zone
  l_ltz_new  := current_timestamp - l_intv; 
  dbms_output.put_line('ltz (workaround-2) = '||l_ltz_new); 
end;
/

---- Output ----
  ltz calc = 2017-03-26 02:17:39
  ltz SQL compare: ORA-01878: specified field not found in datetime or interval
  ltz SQL assigment: ORA-01878: specified field not found in datetime or interval
  ltz PL/SQL compare: ORA-01878: specified field not found in datetime or interval
  ltz PL/SQL assigment: ORA-01878: specified field not found in datetime or interval
  ltz (workaround-1) = 2017-03-26 03:17:39
  ltz (workaround-2) = 2017-03-26 03:17:39
If we make the following 5 tests with time_zone = 'Asia/Singapore', only the first and last have no errors, all other 3 hit: ORA-01878 since there does not exist Singapore time in interval ['1982-JAN-01 00:00:00', '1982-JAN-01 00:30:00'). (see Singapore Standard Time)

alter session set time_zone = 'Asia/Singapore';

select to_timestamp_tz('1982-DEC-31 23:59:59', 'YYYY-MON-DD hh24:mi:ss') from dual;
select to_timestamp_tz('1982-JAN-01 00:00:00', 'YYYY-MON-DD hh24:mi:ss') from dual;
select to_timestamp_tz('1982-JAN-01 00:00:01', 'YYYY-MON-DD hh24:mi:ss') from dual;
select to_timestamp_tz('1982-JAN-01 00:29:59', 'YYYY-MON-DD hh24:mi:ss') from dual;
select to_timestamp_tz('1982-JAN-01 00:30:00', 'YYYY-MON-DD hh24:mi:ss') from dual;
In 16 February 1942 (till 11 September 1945), Singapore Time offset was changed from GMT+07:30 to GMT+09:00 (east shift 1 and half hour) (see https://en.wikipedia.org/wiki/Singapore_Time).

Here some tests:

alter session set time_zone = 'ASIA/Singapore';
--OK
select '-1 second OK', to_timestamp_tz(to_char(date'1942-02-16' - interval '1' second, 'DD.MM.YYYY HH24:MI:SS'), 'DD.MM.YYYY HH24:MI:SS') from dual;
--ERR ORA-01878
select '+0 second ERR', to_timestamp_tz(to_char(date'1942-02-16', 'DD.MM.YYYY HH24:MI:SS'), 'DD.MM.YYYY HH24:MI:SS.FF') from dual;
select '+1 hour ERR', to_timestamp_tz(to_char(date'1942-02-16' + interval '1' hour, 'DD.MM.YYYY HH24:MI:SS'), 'DD.MM.YYYY HH24:MI:SS') from dual;
select '+89:59 minute to second ERR', to_timestamp_tz(to_char(date'1942-02-16' + interval '89:59' minute to second, 'DD.MM.YYYY HH24:MI:SS'), 'DD.MM.YYYY HH24:MI:SS') from dual;
--OK
select '+90 minute OK', to_timestamp_tz(to_char(date'1942-02-16' + interval '90' minute, 'DD.MM.YYYY HH24:MI:SS'), 'DD.MM.YYYY HH24:MI:SS') from dual;

--No problem if TZNAME are Hong_Kong or Paris
  alter session set time_zone = 'Asia/Hong_Kong';
  alter session set time_zone = 'Europe/Paris';
We can also look the time difference:

alter session set time_zone = 'ASIA/Singapore';

-- Delta = 07:30:00 in 1942-FEB-15
select 
  from_tz(timestamp '1942-02-15 10:00:00', 'GMT')             London_Time, 
  from_tz(timestamp '1942-02-15 10:00:00', 'Asia/Singapore')  Singapore_Time,
  from_tz(timestamp '1942-02-15 10:00:00', 'GMT') - from_tz(timestamp '1942-02-15 10:00:00', 'Asia/Singapore') Delta
from dual;

-- Delta = 09:00:00 in 1942-FEB-16
select 
  from_tz(timestamp '1942-02-16 10:00:00', 'GMT')             London_Time, 
  from_tz(timestamp '1942-02-16 10:00:00', 'Asia/Singapore')  Singapore_Time,
  from_tz(timestamp '1942-02-16 10:00:00', 'GMT') - from_tz(timestamp '1942-02-16 10:00:00', 'Asia/Singapore') Delta
from dual;

-- TS_GMT_TZ = 1942-FEB-15 02:30:00 GMT GMT  in 1942-FEB-15
select cast(timestamp'1942-02-15 10:00:00' as timestamp with time zone) ts,
       cast(timestamp'1942-02-15 10:00:00 Asia/Singapore' at time zone 'GMT' as timestamp) ts_gmt,
       cast(timestamp'1942-02-15 10:00:00 Asia/Singapore' at time zone 'GMT' as timestamp with time zone) ts_gmt_tz from dual;

–-TS_GMT_TZ = 1942-FEB-16 01:00:00 GMT GMT in 1942-FEB-16
select cast(timestamp'1942-02-16 10:00:00' as timestamp with time zone) ts,
       cast(timestamp'1942-02-16 10:00:00 Asia/Singapore' at time zone 'GMT' as timestamp) ts_gmt,
       cast(timestamp'1942-02-16 10:00:00 Asia/Singapore' at time zone 'GMT' as timestamp with time zone) ts_gmt_tz from dual;
(b). Time Zone Converting
When performing TIMESTAMP WITH TIME ZONE arithmetic, implicit timezone conversion is involved. For example,

alter session set time_zone = 'Europe/London';
declare
  l_timestp_tz    TIMESTAMP WITH TIME ZONE;
  l_timestp_tz2   TIMESTAMP WITH TIME ZONE;
begin
  l_timestp_tz   := to_timestamp_tz('2017-03-26 01:52:00 Europe/Paris', 'yyyy-mm-dd hh24:mi:ss tzr');
  dbms_output.put_line('l_timestp_tz  = '||l_timestp_tz);
  
  l_timestp_tz2  := l_timestp_tz + numtodsinterval(8,  'Minute');     -- OK
  dbms_output.put_line('l_timestp_tz + interval = '||l_timestp_tz2);
  l_timestp_tz2  := l_timestp_tz + 8/1440;                            -- ORA-01878
  dbms_output.put_line('l_timestp_tz + number   = '||l_timestp_tz2);
end;
/
  
l_timestp_tz  = 2017-MAR-26 01:52:00 EUROPE/PARIS CET
l_timestp_tz + interval = 2017-MAR-26 03:00:00 EUROPE/PARIS CEST
l_timestp_tz + number   = 2017-MAR-26 02:00:00 EUROPE/LONDON BST

Run the same code in 'Europe/Paris', hit ORA-01878:

alter session set time_zone = 'Europe/Paris';
declare
  l_timestp_tz   TIMESTAMP WITH TIME ZONE;
  l_timestp_tz2  TIMESTAMP WITH TIME ZONE;
begin
  l_timestp_tz   := to_timestamp_tz('2017-03-26 01:52:00 Europe/Paris', 'yyyy-mm-dd hh24:mi:ss tzr');
  dbms_output.put_line('l_timestp_tz  = '||l_timestp_tz);
  
  l_timestp_tz2  := l_timestp_tz + numtodsinterval(8,  'Minute');     -- OK
  dbms_output.put_line('l_timestp_tz + interval = '||l_timestp_tz2);
  l_timestp_tz2  := l_timestp_tz + 8/1440;                            -- ORA-01878, line 10
  dbms_output.put_line('l_timestp_tz + number   = '||l_timestp_tz2);
end;
/                                     

l_timestp_tz  = 2017-MAR-26 01:52:00 EUROPE/PARIS CET
l_timestp_tz + interval = 2017-MAR-26 03:00:00 EUROPE/PARIS CEST

ORA-01878: specified field not found in datetime or interval
ORA-06512: at line 10

It looks like that interval arithmetic is performed in natural sense, whereas number arithmetic is a pure math computation.

With Oracle function from_tz, we can construct a TIMESTAMP WITH TIME ZONE value by appending a time zone to a TIMESTAMP value. Following three tests showed different behaviour of Plsql vs Sql (Note that outer from_tz is a wrong usage since inner from_tz returns datatype TIMESTAMP WITH TIME ZONE, not datatype TIMESTAMP).

alter session set time_zone = 'Europe/Paris';
 
select sessiontimezone, dbtimezone from dual;
 
  SESSIONTIMEZONE  DBTIME
  ---------------  ------
  Europe/Paris     +01:00
 
 
begin
  dbms_output.put_line(
       from_tz(from_tz(timestamp'2021-03-28 02:00:00', 'UTC'), sessiontimezone)
  );
end;
/
 
  ORA-01878: specified field not found in datetime or interval
  ORA-06512: at line 2
 
 
select
    from_tz(from_tz(timestamp'2021-03-28 02:00:00', 'UTC'), sessiontimezone)
  from dual;
 
  ERROR at line 2:
  ORA-00932: inconsistent datatypes: expected TIMESTAMP got TIMESTAMP WITH TIME ZONE
 
 
with function func_ret (p_ts timestamp) return timestamp with time zone as
     begin
       return from_tz(from_tz(p_ts, 'UTC'), sessiontimezone);
     end;
select func_ret(timestamp'2021-03-28 02:00:00')
  from dual
/
 
  ORA-01878: specified field not found in datetime or interval
  ORA-06512: at line 5
By the way, tzname 'CET' and 'UTC' are different in respect of DST. In 'CET', Daylight Saving Time (DST) is in effect (DST aware); but in 'UTC', Daylight Saving Time has never been used (Not DST aware), so 'UTC' is the same as Offset TZ (00:00).

select tzname, tzabbrev from v$timezone_names where tzname in ('CET', 'UTC');
 
  TZNAME     TZABBREV
  ---------- ----------
  CET        LMT
  CET        CEST
  CET        CET
  UTC        GMT