Funzioni per operare sulle date
Giorno della settimana
A partire da IB6 è disponibile la funzione predefinita EXTRACT(). La chiamata:
EXTRACT(WEEKDAY FROM D)
restituisce 1=Lunedì, 2=Martedì… 7=Domenica.
Esempio
SELECT
D,
EXTRACT( WEEKDAY FROM D) AS AMERICAN,
EXTRACT( WEEKDAY FROM D-1) + 1 AS ISO8601
FROM T;
D AMERICAN ISO8601
=========== ======== ===========
29-NOV-2001 4 4
30-NOV-2001 5 5
1-DIC-2001 6 6
2-DIC-2001 0 7 <<<< Domenica
3-DIC-2001 1 1
4-DIC-2001 2 2
5-DIC-2001 3 3
Primo/ultimo giorno del mese
Il primo giorno del mese:
D - EXTRACT(DAY FROM D) + 1;
L'ultimo giorno del mese:
LDM = D - EXTRACT(DAY FROM D) + 32;
LDM = LDM - EXTRACT(DAY FROM LDM);
ovvero, in un'unica espressione:
D - EXTRACT(DAY FROM D) + 32 - EXTRACT(DAY FROM D - EXTRACT(DAY FROM D) + 32)
Primo giorno del prossimo mese:
FDNM = D - EXTRACT(DAY FROM D) + 32;
FDNM = FDNM - EXTRACT(DAY FROM FDNM) + 1;
ovvero, in un'unica espressione:
D - EXTRACT(DAY FROM D) + 33 - EXTRACT(DAY FROM D - EXTRACT(DAY FROM D) + 32)
Esempio
Sia D='2002-3-22', risulta:
- primo giorno del mese: '2002-3-1';
- ultimo giorno del mese: '2002-3-31';
- primo giorno del prossimo mese '2002-4-1'.
Numero di giorni in un mese
Questa procedura calcola il numero di giorni in un mese:
CREATE PROCEDURE MonthLength (D DATE) RETURNS (ML INTEGER) AS
DECLARE VARIABLE TMP DATE;
BEGIN
TMP = D - EXTRACT(DAY FROM D) + 32;
ML = EXTRACT(DAY FROM (TMP - EXTRACT(DAY FROM TMP)));
END
ovvero, in un'unica espressione:
EXTRACT(DAY FROM (D - EXTRACT(DAY FROM D) + 32 - EXTRACT(DAY FROM D - EXTRACT(DAY FROM D) + 32)))
Settimana dell'anno
Lo standard ISO 8601, riguardante le notazioni utilizzate per date e tempi, specifica che il primo
giorno della settimana è lunedì; afferma inoltre che la prima settimana dell'anno è quella che include
il primo giovedì (in altri termini la settimana appartiene all'anno che ne possiede la frazione maggiore).
La formula base è:
(EXTRACT(YEARDAY FROM D) - EXTRACT(WEEKDAY FROM D-1) + 7) / 7
che restituisce un valore fra 0 e 53. Se vogliamo la conformità allo standard ISO, dobbiamo apportare alcuni
cambiamenti nel caso dei valori 0 e 53.
Il risultato 0 indica una settimana appartenente "all'anno precedente". L'ultima settimana
del precedente anno potrebbe essere la 52ª o la 53ª; per scoprire quale, applichiamo la formula stessa (con
argomento l'ultimo giorno dell'anno precedente).
Se il risultato fosse 53, dovremmo controllare se la settimana sia proprio la 53ª o, invece, la prima del
prossimo anno. La 53ª settimana è valida solo se contiene giovedì (vale a dire il 31 dicembre è giovedì o
venerdì).
Si possono facilmente evitare questi aggiustamenti se calcoliamo la formula per il giovedì della settimana cui siamo
interessati:
CREATE PROCEDURE YearWeek (D DATE)
RETURNS (WEEK_NO VARCHAR(8)) AS
DECLARE VARIABLE W INTEGER; /* numero della settimana */
DECLARE VARIABLE Y INTEGER; /* anno a cui appartiene la settimana */
BEGIN
D = D - EXTRACT(WEEKDAY FROM D-1) + 3; /* sposta a giovedì */
W = (EXTRACT(YEARDAY FROM D) - EXTRACT(WEEKDAY FROM D-1) + 7) / 7e0;
Y = EXTRACT(YEAR FROM D);
IF (W<10) THEN
WEEK_NO = '0';
ELSE
WEEK_NO = '';
WEEK_NO = Y || '/' || WEEK_NO || W;
SUSPEND;
END
Il codice originale:
CREATE PROCEDURE YearWeek (D DATE)
RETURNS (WEEK_NO VARCHAR(8)) AS
DECLARE VARIABLE W INTEGER; /* numero della settimana */
DECLARE VARIABLE Y INTEGER; /* anno a cui appartiene la settimana */
BEGIN
W = (EXTRACT(YEARDAY FROM D) - EXTRACT(WEEKDAY FROM D-1) + 7) / 7e0;
Y = EXTRACT(YEAR FROM D);
IF (W=0) THEN BEGIN
Y = Y - 1;
D = D - EXTRACT(YEARDAY FROM D) - 1; /* ultimo giorno dell'anno precedente; D è qui utilizzata come variabile temporanea */
W = (EXTRACT(YEARDAY FROM D) - EXTRACT(WEEKDAY FROM D-1) + 7) / 7e0;
END
ELSE
IF (W=53 AND 4>EXTRACT(WEEKDAY FROM (D - EXTRACT(DAY FROM D) + 31))) THEN BEGIN
Y = Y + 1;
W = 1;
END
/* Quanto segue è sola formattazione; si potrebbero anche restituire direttamente W ed Y. */
IF (W<10) THEN
WEEK_NO = '0';
ELSE
WEEK_NO = '';
WEEK_NO = Y || '/' || WEEK_NO || W;
SUSPEND;
END
È un anno bisestile?
Per scoprire se una data ricade in un anno bisestile, possiamo, per esempio, controllare il 59º giorno
dell'anno: se è il 29 febbraio si tratta di un anno bisestile (altrimenti sarebbe il 1 marzo di un
anno normale).
Ecco una procedura di esempio:
CREATE PROCEDURE Is_LeapYear (D DATE) RETURNS (LY INTEGER) AS
BEGIN
IF ( 2 = EXTRACT(MONTH FROM (D - EXTRACT(YEARDAY FROM D) + 59)) ) THEN
LY = 1; /* anno bisestile */
ELSE
LY = 0; /* anno normale */
END
Una procedura simile (con l'anno come argomento):
CREATE PROCEDURE Is_LeapYear (Y INTEGER) RETURNS (LY INTEGER) AS
BEGIN
IF ( 60 = EXTRACT(YEARDAY FROM CAST(Y || '-3-1' AS TIMESTAMP)) ) THEN
LY = 1; /* anno bisestile */
ELSE
LY = 0; /* anno normale */
END
Dato che IB5 non supporta la funzione EXTRACT, possiamo semplicemente limitarci a verificare se
esiste il 29 febbraio:
CREATE PROCEDURE Is_LeapYear (Y INTEGER) RETURNS (LY INTEGER) AS
DECLARE VARIABLE D DATE;
BEGIN
LY = 1;
D = CAST('29-FEB-' || Y AS DATE);
WHEN ANY DO LY = 0;
END
Un'espressione assai nota per individuare un anno bisestile è questa:
Is_LeapYear :=
( ((Year MOD 4) = 0) AND ((Year MOD 100) <> 0) )
OR ((Year DIV 400) = 0);
ma IB/FB non supportano direttamente l'operatore MOD. Questo problema può essere superato ricorrendo ad
una funzione UDF o mediante l'espressione:
x MOD y = x - (x DIV y) * y
da codificarsi correttamente (cosa non semplice quanto sembra per via delle differenti regole fra
Dialect-1 e Dialect-3).