24 noiembrie 2011

Reprezentarea timpului în bazele de date Oracle globalizate

Dintr-o perspectivă filozofico-științifică, chestiunea temporalității este complicată. Nu spun că un astfel de subiect nu ar fi foarte interesant de dezbătut, dar, din păcate, la capitolul (meta-)fizică și filozofie nu stau prea bine. Adică nu mă pricep! Și, în plus, titlul articolului e “indecent” de explicit și cât se poate de pragmatic. Este vorba despre reprezentarea timpului într-o bază de date Oracle.

La o primă vedere subiectul pare plictisitor. Ce poate fi atât de interesant despre niște banale tipuri de date: DATE, TIMESTAMP... și, parcă, ar mai fi câteva? Ei bine, pe cuvânt vă spun că e chiar interesant!

A fost odată ca niciodată... DATE și TIMESTAMP

Toată lumea a auzit de DATE și TIMESTAMP. Principala diferență dintre ele este faptul că, spre deosebire de DATE, tipul TIMESTAMP are “miraculoasa” capacitate de a reține și fracțiuni de secundă. Acesta ar fi, de altfel, un prim criteriu de alegere între cele două tipuri atunci când dorim să modelăm dimensiunea timp în baza de date. Dacă avem nevoie de precizie la nivel de milisecundă, atunci mergem pe varianta TIMESTAMP, dacă nu, tipul DATE este perfect.

Să fim preciși, Bob!

Mai țineți minte filmul acela celebru, “The Phenomenon”? John Travolta joacă rolul unui tip obișnuit, care, peste noapte, devine un geniu. Într-una din scene, el este supus unui test de inteligență. Dialogul între el și Bob, cel care-i pune întrebările, e cam așa:

Bob: Răspunde cât poți de repede! Ce vârstă are un bărbat născut în 1928?
Travolta: E încă în viață?
Bob: Hmm... Dacă un bărbat este născut în 1928 și încă trăiește, ce vârstă are?
Travolta: În ce lună?
Bob: Dacă un bărbat s-a născut pe 3 Octombrie 1928 și este încă în viață, ce vârstă are?
Travolta: La ce ora?
Bob: Ora 10! P.M.!
Travolta: Unde?
Bob: Oriunde!
Travolta: Păi hai să fim preciși, Bob! Dacă tipu’ e încă în viață, născut în California, pe 3 Octombrie 1928, ora 10 PM, atunci are 67 de ani, 9 luni, 20 de zile, 40 de minute și 12 minute. Dacă e nascut în New York e cu trei ore mai bătrân.

Morala ar fi că, atunci când modelăm timpul în baza de date, este important să analizăm dacă originea informației “timp” este relevantă în logica sistemului informatic pe care îl dezvoltăm sau nu.

Să luăm exemplul de mai sus și să presupunem că ar exista în SUA un departament în care sunt centralizate toate datele cu privire la nașterile din țară. Fiecare maternitate consemnează nașterile la ora lor locală și trimit aceste date spre unitatea de centralizare, unde sunt stocate într-o bază de date Oracle. Data nașterii corespunde unei coloane de tip DATE într-o tabelă a cărei denumire și structură detaliată nu ne interesează. Cert este că, în final, se adună date și de la maternitățile din New York, și de la cele din California, de peste tot! În acest caz, sistemul are o mare problemă, deoarece coloana cu data nașterii conține “mere și pere”. Faptul că pentru doi copii avem aceeași dată a nașterii nu înseamnă, implicit, că ei chiar s-au nascut la același moment. Unul poate că s-a nascut la ora locală 8:00 PM în California, iar celălalt la ora 8:00 PM în New York, ceea ce înseamnă că, de fapt, nou născutul din New York a venit pe lume cu trei ore mai devreme.

Să ne amintim: fusuri orare

- Alo, Costeluș? Ce faci măi băiatule la New York? Sunt eu, Ghenosu, tovarășu’ tău de la Cluj! Uite, eu sunt la un Mall, îmi beau cafeaua de dimineață și m-am gândit sa-ți dau un apel să văd ce mai zici.
- No, bine fac mă Ghenosu, sforăi de rup patu’. Suna-mă peste vreo șapte ore ș-apoi îți povestesc eu mai multe!

Chiar daca scenariul de mai sus e ficțiune pură (orice asemănare cu personaje reale este întâmplătoare), sper că este suficient de sugestiv pentru a sublinia faptul că ora pe glob este diferită de la o regiune la alta. Acesta este un lucru bine știut și este legat de “diferențele de fus orar”.

Din această perspectivă, câteva precizări sunt necesare. Fiecare fus orar corespunde unei regiuni și are asociat un așa-numit “meridian central”. De-o parte și de alta a meridianului central, 7°30` longitudine vest și 7°30` longitudine est, se presupune că avem aceeași oră, cea dată de meridianul central. Conceptul e bun pentru că avem o împarțire uniformă a acestor zone. În practică, însă nu e chiar așa de simplu pentru că această delimitare nu se potrivește, nicidecum, cu granițele țărilor implicate. Poate pentru câțiva fani ai lui John Lenon, care încă mai fredonează “Imagine there’s no country...”, o împărțire uniformă a acestor zone nu ar fi o mare problemă, însă, în practică, pentru anumite state, a avea de-a face cu ore diferite în cadrul acelorași granițe nu este fezabil, din rațiuni economice, organizatorice etc. Un exemplu concludent îl reprezintă China, care deși are un teritoriu vast, acoperind aproape cinci zone de fus orar, folosește doar unul singur. Pe de altă parte, SUA, Argentina și altele operează cu fusuri orare distincte. Reținem, deci, că fusurile orare nu au o distribuție uniformă, ci ele au fost ajustate astfel încât să țină cont de granițele statale.

Fusurile orare se pot exprima prin diferențe pozitive sau negative față de timpul universal coordonat (prescurtat UTC), care reprezintă elementul de referință (vechiul GMT, deși nu este același lucru). Spre exemplu, fusul orar pentru Belarus este UTC+03, iar pentru Barbados este UTC-04, unde +03 și -04 reprezintă diferențe orare.

Din păcate, lucrurile se complică de îndată ce în ecuație intervin factori noi, cum ar fi, de exemplu, trecerea la ora de vară. E ceea ce în engleză apare sub denumirea de “Daylight Saving Time”, de unde și prescurtarea de DST. În anumite regiuni se folosește ora de vară, în altele nu. Și pentru ca lucrurile să devină și mai complicate, anumite state nu foloseau această metodă, dar au decis să o folosească, și invers. Mai mult, regulile după care are loc trecerea la ora de vară diferă și pot fi modificate prin deciziile guvernelor. Na, că se încâlcește treaba!

TZ TZ TZ...

Din fericire, un anume Arthur David Olson s-a gândit prin 1986 să centralizeze informații detaliate despre fusurile orare, incluzând exprimarea acestora relativ la UTC, momentele de tranziție la ora de vară, dar și regulile după care aceste tranziții se efectuează. Inițiativa s-a bucurat de succes, mai ales că ea era necesară în contextul globalizării. Numeroși voluntari au pus umărul, în final rezultând ceea ce regăsim astăzi sub denumirea de “bază de date TZ” sau “zoneinfo”. Începând cu 14 Octombrie 2011, ea este administrată de ICANN (Internet Corporation for Asigned Names and Numbers), prin departamentul IANA.

Într-o formă sau alta, baza de date TZ fusese oricum adoptată de numeroase sisteme de operare bazate pe UNIX, dar și de limbaje precum Java, PHP, Perl și altele. În domeniul sistemelor de gestiune a bazelor de date, o întâlnim în MySql, PostgreSQL și Oracle.

Important de reținut este faptul că se introduce o nouă metoda de exprimare a fusului orar, folosind convenția “Regiune/Locație”. De pilda, fusul orar pentru România este “Europe/Bucharest”. Restul informațiilor privind tranziția la ora de vara se regăsesc în baza de date TZ, ce poate fi folosită pentru a efectua, de pildă, conversia într-un alt fus orar.

Dat fiind că fusul orar poate fi specificat atat relativ la UTC cât și prin convenția “Regiune/Locație”, nu înseamnă că cele două metode sunt echivalente. Doar atunci când se folosește “Regiune/Locație” sunt accesate datele din “zoneinfo”, putându-se determina corect regulile de tranziție la/de la ora de vară, desigur, atunci când este cazul.

”Zoneinfo” în Oracle

Oracle a decis să nu reinventeze roata prin adoptarea unui sistem propriu de gestiune a fusurilor orare, ci folosește un fișier de tip “zoneinfo”, atât pe server, cât și pe client. Pe server, putem afla ce fișier “zoneinfo” este folosit, interogând V$TIMEZONE_FILE:
SQL> select * from v$timezone_file;

FILENAME                VERSION
-------------------- ----------
timezlrg_11.dat              11
Da, ați ghicit! Atunci când Oracle trebuie să acceseze informații cu privire la fusurile orare, nu se uita într-o tabelă internă în dicționar, ci fix în acest fișier, care, implicit, se găsește în directorul $ORACLE_HOME/oracore/zoneinfo. Este adevărat că anumite date sunt externalizate în V$TIMEZONE_NAMES, dar popularea se face tot pe baza fișierului “zoneinfo”.

Versiunea de fișier “zoneinfo” pe server poate fi schimbată printr-o procedură de upgrade, proces detaliat în documentație. Un astfel de upgrade poate fi necesar atunci când baza de date conține informație de timp cu fusuri orare afectate de diferite modificări, cum ar fi regulile de tranzitie la ora de vară sau diferențele relative la UTC.

Deși nu este evident, o versiune locală de “zoneinfo” este folosită și pe client. Spun că nu este evident pentru că, spre exemplu, dacă utilizați un client de tip “instant”, nu veți găsi nici un fisier separat cu date despre fusurile orare, așa cum avem pe server. Acestea sunt înglobate și compilate direct în librariile de conectare. Totuși, pentru a determina versiunea de fișier “zoneinfo” se poate utiliza utilitarul “genezi”, furnizat de Oracle.
C:\Documents and Settings\Administrator>genezi -v
Client Shared Library 32-bit - 11.2.0.1.0

Windows XP Version V5.1 Service Pack 3
CPU                 : 2 - type 586, 1 Physical Cores
Process Affinity    : 0x0x00000000
Memory (Avail/Total): Ph:212M/1013M, Ph+PgF:1581M/2441M, VA:1895M/2047M
Operating in Instant Client mode.
Small timezone file = timezone_11.dat
Large timezone file = timezlrg_11.dat
În exemplul de mai sus, se poate observa că se utilizează versiunea 11, care coincide cu cea utilizată și pe server. Deși, începând cu versiunea 11.2, Oracle permite o interoperabilitate între un client și un server cu versiuni diferite de “zoneinfo”, acest lucru nu este recomandat.

Este interesant de ce Oracle furnizează date de “zoneinfo” și pe client, ținând cont că acestea ar putea fi preluate de pe server. Ei bine, nu! Librăriile client, prin API-ul pe care îl expune, trebuie să dea posibilitatea aplicațiilor să opereze cu informație temporală, chiar și fără o conexiune la baza de date.

Fiecare bază de date are configurat fusul său orar. Acesta este specificat fie la crearea bazei de date prin clauza SET TIME_ZONE a comenzii CREATE DATABASE, fie, ulterior, printr-o comanda ALTER DATABASE SET TIME_ZONE. Dacă la crearea bazei de date nu se utilizează clauza SET TIME_ZONE, atunci se folosește implicit fusul orar configurat în sistemul de operare. Pentru a determina fusul orar al bazei de date putem folosi funcția DBTIMEZONE:
SQL> select dbtimezone from dual;

DBTIMEZONE
----------
+00:00
În exemplul de mai sus, baza de date utilizează fusul orar UTC, de altfel, cel recomandat de Oracle.

Sesiunile conectate la baza de date pot specifica propriul fus orar. Acesta este, în general, fusul orar al aplicației client, determinat după cum urmează:
  • implicit, prin preluarea configurării din sistemul de operare. De obicei, formatul +-HH:MM este folosit;
  • prin definirea variabilei de sistem ORA_SDTZ, pe client;
  • printr-o comandă ALTER SESSION SET TIME_ZONE.

Pentru a determina fusul orar al sesiunii putem folosi următoarea interogare:
SQL> select sessiontimezone from dual;

SESSIONTIMEZONE
---------------
+02:00

Variantele TIMESTAMP globalizate

Versiunile extinse pentru tipul TIMESTAMP sunt TIMESTAMP WITH TIME ZONE și TIMESTAMP WITH LOCAL TIME ZONE. Acestea sunt suficient de “deștepte” pentru a ști cum să opereze cu diferite fusuri orare. Tipul DATE n-are habar de fusuri orare și nici nu există variantele extinse ca în cazul tipului TIMESTAMP.

Tipul TIMESTAMP WITH TIME ZONE memorează în structura sa informații complete cu privire la fusul orar, fie relativ la UTC, fie sub forma “Regiune/Locație”. Două valori de tip TIMESTAMP WITH TIME ZONE sunt considerate a fi identice dacă reprezentarea lor in format UTC conduce spre aceeași valoare.

Tipul TIMESTAMP WITH LOCAL TIME ZONE, spre deosebire de tipul TIMESTAMP WITH TIME ZONE, nu înglobează, intrinsec, atributele specifice fusului orar. În schimb, toate valorile de acest tip, înainte de a fi stocate, sunt normalizate la fusul orar al bazei de date. Atunci când aceste date sunt accesate, ele sunt automat convertite la fusul orar al sesiunii. Pentru exemplificare, să considerăm următorul scenariu:
SQL> create table test_timp (
  2    timp_standard timestamp,
  3    timp_fus_orar timestamp with time zone,
  4    timp_local    timestamp with local time zone
  5  );

Table created.

SQL> insert into test_timp values (systimestamp, systimestamp, systimestamp);

1 row created.

SQL> select * from test_timp;

TIMP_STANDARD                TIMP_FUS_ORAR                       TIMP_LOCAL
---------------------------- ----------------------------------- ----------------------------
21-NOV-11 07.53.23.029778 PM 21-NOV-11 07.53.23.029778 PM +02:00 21-NOV-11 07.53.23.029778 PM
Se poate observa că toate cele trei valori din coloanele de mai sus sunt identice. Între valoarea de tip TIMESTAMP și cea de tip TIMESTAMP WITH LOCAL TIME ZONE nu se observă nici o diferență. Notă discordantă face coloana TIMP_FUS_ORAR care, fiind de tip TIMESTAMP WITH TIME ZONE, afișează și informația de fus orar, în acest caz relativ la UTC.

În continuare, să vedem ce se întamplă dacă interogăm dintr-o sesiune cu un alt fus orar:
SQL> alter session set time_zone='Asia/Shanghai';

Session altered.

SQL> select * from test_timp;

TIMP_STANDARD                TIMP_FUS_ORAR                       TIMP_LOCAL
---------------------------- ----------------------------------- ----------------------------
21-NOV-11 07.53.23.029778 PM 21-NOV-11 07.53.23.029778 PM +02:00 22-NOV-11 01.53.23.029778 AM
Observăm modificarea valorii afișată pentru coloana de tip TIMESTAMP WITH LOCAL TIME ZONE, care acum apare folosind fusul orar din Shanghai, cel configurat la nivelul sesiunii. Valorile din coloanele TIMESTAMP și TIMESTAMP WITH TIME ZONE rămân nemodificate.

Ce varianta de TIMESTAMP alegem?

În condițiile unei oferte atât de generoase, întrebarea e cât se poate de legitimă. Avem de ales între TIMESTAMP, TIMESTAMP WITH TIME ZONE și TIMESTAMP WITH LOCAL TIMEZONE.

În primul rând, optiunea pentru un anumit tip de date trebuie să țină cont de specificul aplicației și de procesul modelat. Dacă aplicația nu va fi utilizată într-un mediu globalizat, atunci opțiunea firească ar fi cea de a utiliza tipul TIMESTAMP sau, după caz, DATE.

Dacă aplicația trebuie să țină cont de globalizare, deoarece utilizatorii o pot accesa din zone diferite de pe glob, atunci tipurile TIMESTAMP WITH TIME ZONE și TIMESTAMP WITH LOCAL TIMEZONE ar trebui luate în considerare.

Chiar și asa, rețineți faptul că, deși aplicația este dezvoltată spre a fi utilizată într-un mediu global, anumite procese pot ignora acest aspect. Spre exemplu, să presupunem că aplicația este înzestrată cu un modul de audit prin care, ori de câte ori un utilizator deschide o operație, momentul accesării acesteia este preluat, prin invocarea funcției SYSTIMESTAMP, și consemnat în baza de date. La o primă vedere un TIMESTAMP WITH TIME ZONE s-ar preta, dat fiind că utilizatori diferiți pot accesa operațiile aplicației din zone diferite de pe glob. Totuși, funcția SYSTIMESTAMP returnează timpul curent așa cum este el furnizat de sistemul de operare pe care serverul bazei de date rulează. Prin urmare, functia SYSTIMESTAMP este “imună” la fusul orar al sesiunii client, iar în final, toate intrările consemnate în tabela de audit ar fi reprezentate în fusul orar al sistemului de operare în cauză. În acest caz, un TIMESTAMP WITH TIME ZONE nu se justifică. Iată și un exemplu:
SQL> alter  session set time_zone='America/New_York';

Session altered.

SQL> select systimestamp from dual;

SYSTIMESTAMP
-----------------------------------
23-NOV-11 10.47.35.634598 AM +02:00

SQL> alter  session set time_zone='+00:00';

Session altered.

SQL> select systimestamp from dual;

SYSTIMESTAMP
-----------------------------------
23-NOV-11 10.47.35.730595 AM +02:00
Se poate observa că, indiferent de fusul orar configurat la nivelul sesiunii, rezultatul funcției SYSTIMESTAMP este același.

Dacă, totuși, discutăm de funcționalități ale aplicației care trebuie să țină cont de contextul global, atunci întrebarea care se pune mai departe este legată de opțiunea pentru tipul TIMESTAMP WITH TIME ZONE sau pentru TIMESTAMP WITH LOCAL TIME ZONE.

Figura de mai jos prezintă principalii pași de urmat atunci când se decide care este cel mai potrivit tip pentru stocarea timpului.

Figura 1 - Alegerea tipului de date pentru stocarea informației temporale

În principiu, ori de câte ori informația de timp trebuie stocată împreună cu fusul orar de origine, este indicat a se folosi tipul TIMESTAMP WITH TIME ZONE. De asemenea, dacă modelăm un sistem care are de-a face cu date clendaristice ce implică fusuri orare ce operează cu DST, eventual susceptibile a se modifica frecvent, atunci TIMESTAMP WITH TIME ZONE este opțiunea potrivită. Mai mult ca sigur, sistemele de planificare ce opereză în medii globalizate trebuie să ia în considerare acest tip de date, dat fiind că acestea, prin natura lor, stochează și date în viitor, care sunt susceptibile a avea altă semnificație dacă definiția fusului orar corespunzător se modifică.

Pe de altă parte, TIMESTAMP WITH LOCAL TIME ZONE ar trebui folosit atunci când fusul orar de origine nu prezintă interes, iar ajustările de tip DST nu sunt critice pentru sistemul modelat. Daca ajustările DST sunt importante, atunci pot apărea probleme la conversia timpului din fusul orar al sesiuni, posibil sensibil la DST, în fusul orar al bazei de date, “imun” la DST, dar și invers. În orice caz, dacă importantă este doar ordinea relativă între evenimentele înregistrate, atunci TIMESTAMP WITH LOCAL TIME ZONE e un bun candidat.

E bine de știut

Trebuie să ținem seama și de anumite limitări și posibile probleme în utilizarea acestor tipuri de date.

O primă limitare este aceea că o coloană de tip TIMESTAMP WITH TIME ZONE nu poate fi utilizată în compunerea cheii primare. Mai mult, un index pe o astfel de coloană, deși nu foarte evident, nu este un index de tip B-TREE, ci un index funcțional. Deși acest lucru nu ar trebui să reprezinte o mare problemă, totuși, impactul trebuie evaluat. Spre exemplu, o operație de tip SHRINK pe segmentul tabelei cu un astfel de index nu va funcționa.

Un al doilea aspect îl reprezintă necesitatea aplicării de actualizări a fișierului “zoneinfo” și, concomitent, a datelor din coloanele de tip TIMESTAMP WITH TIME ZONE. Mai mult, în aceste cazuri, o inter-operabilitate cu clientul de Oracle trebuie asigurată. Astfel de actualizări nu sunt chiar “floare la ureche” și presupun perioade de inaccesibilitate ale bazei de date pe perioada actualizării. De aceea, criteriul mentenabilității și al accesibilității sunt importante.

Dacă avem de-a face cu medii de baze de date distribuite atunci, în acest context al globalizării, trebuie să asigurăm o bună inter-operabilitate între bazele de date participante, prin actualizarea la aceeași versiune a fișierului de tip “zoneinfo”.

Nu trebuie neglijată nici expertiza necesară celor care dezvoltă și întrețin o aplicație globalizată. Dat fiind că aceste tipuri de date nu sunt foarte des utilizate, ele tind să fie mai puțin cunoscute, de unde și numeroase neînțelegeri și frustrări legate de modul lor de operare.

Iată câteva din cele mai comune capcane.

Capcana 1: Fusul orar al bazei de date

Există mitul conform căruia fusul orar al bazei de date trebuie să reflecte regiunea în care aceasta este localizată.

Acest lucru este profund greșit și poate cauza probleme. Fusul orar al bazei de date este relevant doar în contextul tipului de date TIMESTAMP WITH LOCAL TIME ZONE, atunci când timpul furnizat într-un alt fus orar este convertit, intern, la fusul orar al bazei de date. Mai mult, dat fiind că diferența relativă la UTC reflectată de acest fus orar nu ar trebui să se modifice, convenția “Regiune/Locație” ar trebui evitată sau, cel mult, configurată la un fus orar care nu folosește DST. Regula de aur, totuși, ar fi ca pentru fusul orar al bazei de date să se folosească întotdeauna +00:00. Din punctul meu de vedere, posibilitatea de a configura fusul orar al bazei de date ar trebui ascuns utilizatorului, pentru că nu are nici o relevanță din punctul său de vedere.

Capcana 2: SYSDATE și SYSTIMESTAMP în contextul globalizării

Un alt mit esta acela că SYSDATE și SYSTIMESTAMP ar lua în considerare fusul orar al clientului. Greșit! Am exemplificat deja acest lucru și am văzut că SYSTIMESTAMP este “imun” la fusul orar al sesiunii. Această funcție se raportează întotdeauna la sistemul de operare al server-ului/server-elor pe care baza de date rulează.

Ori de câte ori raportarea la fusul orar al sesiunii curente este necesară, următoarele funcții sunt cele potrivite:
  • CURRENT_DATE: returnează data curentă în fusul orar al sesiunii, într-un tip DATE
  • LOCALTIMESTAMP: returnează timpul curent în fusul orar al sesiunii, într-un tip TIMESTAMP 
  • CURRENT_TIMESTAMP: returnează timpul curent în fusul orar al sesiunii, într-un tip TIMESTAMP WITH TIME ZONE.

Iată un exemplu:
SQL> alter session set time_zone='America/New_York';

Session altered.

SQL> ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT ='DD/MM/YYYY HH24:MI:SS TZR TZD' NLS_TIMESTAMP_FORMAT ='DD.MM.YYYY HH24:MI:SS' NLS_DATE_FORMAT ='DD-MM-YYYY HH24:MI:SS';

Session altered.

SQL> select systimestamp, current_date, current_timestamp, localtimestamp from dual;

SYSTIMESTAMP               CURRENT_DATE        CURRENT_TIMESTAMP                        LOCALTIMESTAMP     
-------------------------- ------------------- ---------------------------------------- -------------------
23/11/2011 13:55:41 +02:00 23-11-2011 06:55:41 23/11/2011 06:55:41 AMERICA/NEW_YORK EST 23.11.2011 06:55:41
Se observă că, exeptând SYSTIMESTAMP, toate celelalte coloane sunt “sensibile” la fusul orar al sesiunii, în acest caz “America/New_York”.

Capcana 3: Aritmetica TIMESTAMP-urilor

Aceasta este una dintre cele mai comune greșeli. Să luăm, spre exemplu, o operație întâlnită frecvent, aceea a adăugării de zile la o valoare de tip date. Spre exemplu SYSDATE+1 înseamnă “mâine pe vremea asta”. Atunci cand efectuăm aceeași operație pe o valoare/expresie de tip TIMESTAMP, rezultatul evaluării nu este un TIMESTAMP, ci DATE. Asta înseamnă că se pierde din precizia la nivel de milisecunde și, evident, informația de fus orar în cazul TIMESTAMP-urilor extinse.
SQL> select dump(sysdate+1) from dual;

DUMP(SYSDATE+1)                    
-----------------------------------
Typ=13 Len=8: 219,7,11,24,14,23,9,0

SQL> select dump(systimestamp+1) from dual;

DUMP(SYSTIMESTAMP+1)                
------------------------------------
Typ=13 Len=8: 219,7,11,24,14,23,31,0
Observăm că în cel de-al doilea caz, tipul expresiei evaluate nu se modifică. “Typ=13” înseamnă DATE.

Atenție trebuie acordadă și comparațiilor atunci când avem de-a face cu tipuri distincte: DATE, TIMESTAMP, TIMESTAMP WITH LOCAL TIME ZONE și TIMESTAMP WITH TIME ZONE. Oracle convertește unul din membrii comparației la tipul extins. Spre exemplu, daca se efectuează o comparție între o valoare TIMESTAMP și una TIMESTAMP WITH TIME ZONE, valoarea TIMESTAMP este convertită într-un TIMESTAMP WITH TIME ZONE, folosindu-se fusul orar al sesiunii curente. Acest lucru poate fi sau nu ceea ce se dorește prin logica operației.

Capcana 4: Job-uri automate

Începând cu Oracle 10g, putem folosi atât DBMS_JOB, cât și DBMS_SCHEDULER. Pentru aplicațiile globalizate în care ora de pornire a unui job este dependentă de diferite fusuri orare, DBMS_JOB nu este soluția recomandată, dat fiind că data de pornire a job-ului aparține tipului DATE, care, evident, nu are habar de fusuri orare. Prin urmare, DBMS_SCHEDULER ar trebui luat în considerare. Acesta permite specificarea momentului de pornire a job-ului în formatul TIMESTAMP WITH TIME ZONE. Dacă un fus orar “sensibil” la DST este folosit pentru a specifica momentul de pornire, iar acesta se suprapune peste momentul de tranziție la/de la ora de vară, atunci trebuie să se țină cont că respectivul job nu va rula la ora specificată.

Un alt aspect interesant este legat de operațiile sensibile la fusul orar al sesiunii și care sunt apelate dintr-un job administrat prin DBMS_JOB. Sesiunea pe care DBMS_JOB o deschide are, implicit, fusul orar configurat pe +00:00, adica UTC. Astfel, o procedură lansată din sesiunea curentă s-ar putea comporta total diferit sub o altă sesiune, lansată de DBMS_JOB.

În loc de concluzie

Dezvoltarea de aplicații globalizate necesită o bună cunoaștere a facilităților pe care baza de date Oracle le oferă din acest punct de vedere. Echipa este importantă: atât programatorii trebuie să știe cum să jongleze cu logica fusurilor orare, cât și administratorul bazei de date, cel responsabil de actualizarea fișierului “zoneinfo”, dar și a celorlalte aspecte privind configurarea bazei de date și a clienților.

0 commentarii: