Discussione:
Ora legale e ora solare: un consiglio
(troppo vecchio per rispondere)
Roberto Montaruli
2007-10-10 18:35:31 UTC
Permalink
Gente, mi serve un consiglio o un suggerimento.
Ho trovato una imprecisione in una procedura che in determinate
circostanze sbaglia a calcolare l'ora.

Ho una tabella di log: ogni record contiene un campo col valore
dell'orario in cui e' stato scritto.

Solo che questo campo contiene la data e l'ora in formato universale
(numero di secondi trascorsi dal 1 gennaio 1970) riferito al meridiano
di greenwich, quindi per trasformare il dato nel fuso orario italiano
devo aggiungere 1 ora o 2 ore a seconda se siamo in regime di ora legale
o ora solare.

Questa procedura, che opera tale trasformazione, commette un errore
formale piuttosto grave: per sapere quanto aggiungere per trasformare
l'orario, interroga il sistema operativo, il quale correttamente
restituisce +1 o +2 a seconda del regime di orario *attuale*.

E questo funziona se si trasformano i valori del giorno corrente.

Il problema e' che se io vado a recuperare un record vecchio, e voglio
convertire il campo di orario, la procedura mi aggiunge +1 o +2 in base
al regime *attuale* e non in base a quello del momento in cui si
riferisce il record.

Si accettano suggerimenti su come procedere.

Sono consentite tutte le funzioni standard di un linguaggio procedurale
di alto livello.
E' nota la regola che il cambio di ora avviene l'ultima domenica di
marzo e l'ultima di ottobre.
Non si possono effettuare chiamate al sistema operativo.
Non si puo' usare una tabella di appoggio.
Le date risalenti a quando il cambio dell'ora legale era in settembre
invece che in ottobre non contano perche' non ci sono dati di
quell'epoca, quindi possiamo ignorarle.
Non e' necessario prendere in considerazione l'eventualita' che la
regola dell'ora legale possa cambiare in futuro.

La funzione deve ricevere come argomento un longint col numero di
secondi dalla mezzanotte del 1/1/70 e restituire un datetime (o una
stringa con la data e l'ora)
Luca Pascali
2007-10-11 06:49:28 UTC
Permalink
Post by Roberto Montaruli
Gente, mi serve un consiglio o un suggerimento.
Ho trovato una imprecisione in una procedura che in determinate
circostanze sbaglia a calcolare l'ora.
[Really big cut]

Allora, il problema è che vuoi salvare dei record.
Utilizzando UTC nel salvataggio non hai problemi di ore legali, solari e
fusi orari di qualsiasi genere.
Però, se tu volessi risalire all'ora locale in quel periodo non hai la
possibilità di sapere le regole in vigore in quel periodo, giusto?

La cosa più semplice (costo contenuto e funziona sicuro da qui
all'eternità, anche se l'Italia decidesse di adottare il fuso orario
della Romania) è di salvare 2 timestamp per record, uno in UTC e l'altro
in localtime.

Per ordinamenti et simili lavori su quello in UTC.
Per verifiche (visualizzazioni, ricerche ecc.) lavori con quello in
localtime.

Salvando anche il flag di DST, se qualcuno ti chiede le 2:30 del giorno
di transizione da solare a legale (o l'altra? qual'è quello che torna
indietro di un'ora?) mostrerai 2 record, uno con le 2:30 ora solare e
l'altro con le 2:30 ora legale, ma in UTC avvenuto un'ora dopo.

Niente funzioni che non avrebbero senso né potrebbero funzionare senza
tabelle a lato, per il semplice fatto che le variazioni alla gestione
dell'ora solare non vengono prese in base a equazioni matematiche, ma in
base a scelte "politiche", quindi fuori da schemi,

Luca
r***@gmail.com
2007-10-11 09:54:11 UTC
Permalink
Post by Luca Pascali
Post by Roberto Montaruli
Gente, mi serve un consiglio o un suggerimento.
Ho trovato una imprecisione in una procedura che in determinate
circostanze sbaglia a calcolare l'ora.
[Really big cut]
Allora, il problema � che vuoi salvare dei record.
Utilizzando UTC nel salvataggio non hai problemi di ore legali, solari e
fusi orari di qualsiasi genere.
Per�, se tu volessi risalire all'ora locale in quel periodo non hai la
possibilit� di sapere le regole in vigore in quel periodo, giusto?
Purtroppo i record esistono gia'.
Io non devo scriverli, devo leggerli!!!
Questi scrivono il datetime in formato UTC e io devo prendere il dato
e ricavarci data e ora.
Post by Luca Pascali
Niente funzioni che non avrebbero senso n� potrebbero funzionare senza
tabelle a lato, per il semplice fatto che le variazioni alla gestione
dell'ora solare non vengono prese in base a equazioni matematiche, ma in
base a scelte "politiche", quindi fuori da schemi,
La tua e' una buona soluzione, che l'analista avrebbe dovuto prendere
in considerazione a suo tempo.
Invece l'applicativo scrive in UTC, che cosi' non ha problemi di
valori duplicati in caso di cambi di fuso orario e ore legali, e io
poi devo convertire in data perche' tanto "e' facile".

Ieri rimuginandoci sopra mi sono reso conto che facile non e'...
Luca Pascali
2007-10-11 11:08:31 UTC
Permalink
[...]
Post by r***@gmail.com
Post by Luca Pascali
Niente funzioni che non avrebbero senso né potrebbero funzionare senza
tabelle a lato, per il semplice fatto che le variazioni alla gestione
dell'ora solare non vengono prese in base a equazioni matematiche, ma in
base a scelte "politiche", quindi fuori da schemi,
La tua e' una buona soluzione, che l'analista avrebbe dovuto prendere
in considerazione a suo tempo.
Invece l'applicativo scrive in UTC, che cosi' non ha problemi di
valori duplicati in caso di cambi di fuso orario e ore legali, e io
poi devo convertire in data perche' tanto "e' facile".
Ieri rimuginandoci sopra mi sono reso conto che facile non e'...
Passo indietro, allora.
Che linguaggio usi?

Io vedo 2 soluzioni: usare la localtime (o equivalente o altre funzioni
di sistema) per convertire il timestamp da UTC a CET, oppure costruirti
una tabella (una array in memoria) con gli UTC di partenza e di fine
dell'ora legale, oppure scrivere una funzione legata a doppia mandata
con il nostro fuso orario (ma non mi sembra sia questo il problema).
In quest'ultimo modo puoi lavorare con quasi qualsiasi linguaggio (deve
per lo meno avere la conversinone da unix epoch time a human readable time)

Mi spiego meglio.
Noi siamo a CET, quindi UTC+1 più eventualmente anche l'ora legale.

Quindi dal timestamp in UTC aggiungi 3600 per avere CET senza DST e poi
verifichi se ti trovi in un intervallo di date in cui dovrebbe essere
attiva la DST, nel qual caso aggiungi ancora 3600.
Poi utilizzi le funzioni di GMTTime (quindi senza ulteriori traduzioni
con il fuso orario).

Per gli intervalli è sufficiente una associazione anno-giorno
inizio-giorno fine, ad esempio

2007,45,254
2008,46,255
ecc..

Te la compili a mano per una 20na di anni (si tratta di una array di
interi con dimensioni 20x3) e le uniche conversioni da fare sono quelle
di tradurre i giorni come ordinale dall'inizio dell'anno a timestamp
alle 2 del mattino per fare le verifiche. Se le regole, dal 2010 per
dire un anno a caso, dovessero cambiare, ricalcoli solo i giorni di
inizio e fine per gli anni dal 2010 in poi e vivi comunque felice.

Occhio che gli anni multipli di 4 ad eccezione di quelli multipli di
100, ma reintegrando quelli multipli di 400 sono bistestili, quindi con
29 giorni a febbraio.

Di più nin so.

Luca
Roberto Montaruli
2007-10-11 18:21:01 UTC
Permalink
Post by Luca Pascali
[...]
Post by r***@gmail.com
Post by Luca Pascali
Niente funzioni che non avrebbero senso né potrebbero funzionare senza
tabelle a lato, per il semplice fatto che le variazioni alla gestione
dell'ora solare non vengono prese in base a equazioni matematiche, ma in
base a scelte "politiche", quindi fuori da schemi,
La tua e' una buona soluzione, che l'analista avrebbe dovuto prendere
in considerazione a suo tempo.
Invece l'applicativo scrive in UTC, che cosi' non ha problemi di
valori duplicati in caso di cambi di fuso orario e ore legali, e io
poi devo convertire in data perche' tanto "e' facile".
Ieri rimuginandoci sopra mi sono reso conto che facile non e'...
Passo indietro, allora.
Che linguaggio usi?
SQL, su un server che non so che cosa sia, e che potrebbe cambiare un
domani.
Non posso fare affidamento sul sistema operativo.
Post by Luca Pascali
Io vedo 2 soluzioni: usare la localtime (o equivalente o altre funzioni
di sistema) per convertire il timestamp da UTC a CET, oppure costruirti
una tabella (una array in memoria) con gli UTC di partenza e di fine
dell'ora legale, oppure scrivere una funzione legata a doppia mandata
con il nostro fuso orario (ma non mi sembra sia questo il problema).
In quest'ultimo modo puoi lavorare con quasi qualsiasi linguaggio (deve
per lo meno avere la conversinone da unix epoch time a human readable time)
Mi spiego meglio.
Noi siamo a CET, quindi UTC+1 più eventualmente anche l'ora legale.
Mi e' chiaro: conosco pure io la localtime() e il trattamento che fa
unix del tempo.
Ma non posso applicarla in questa circostanza.
Post by Luca Pascali
Quindi dal timestamp in UTC aggiungi 3600 per avere CET senza DST e poi
verifichi se ti trovi in un intervallo di date in cui dovrebbe essere
attiva la DST, nel qual caso aggiungi ancora 3600.
Poi utilizzi le funzioni di GMTTime (quindi senza ulteriori traduzioni
con il fuso orario).
Per gli intervalli è sufficiente una associazione anno-giorno
inizio-giorno fine, ad esempio
2007,45,254
2008,46,255
ecc..
Te la compili a mano per una 20na di anni (si tratta di una array di
interi con dimensioni 20x3) e le uniche conversioni da fare sono quelle
di tradurre i giorni come ordinale dall'inizio dell'anno a timestamp
alle 2 del mattino per fare le verifiche. Se le regole, dal 2010 per
dire un anno a caso, dovessero cambiare, ricalcoli solo i giorni di
inizio e fine per gli anni dal 2010 in poi e vivi comunque felice.
Occhio che gli anni multipli di 4 ad eccezione di quelli multipli di
100, ma reintegrando quelli multipli di 400 sono bistestili, quindi con
29 giorni a febbraio.
Anche questa soluzione ho considerato.
Volevo evitare tabelle di appoggio.
E non mi piace lasciare in giro procedure a scadenza: il Y2K e' passato
e non mi pare il caso di riproporlo con una tabella che nessuno si
prendera' la briga di aggiornare in futuro.
Luca Pascali
2007-10-11 18:32:22 UTC
Permalink
[...]
Post by Roberto Montaruli
Post by Luca Pascali
Passo indietro, allora.
Che linguaggio usi?
SQL, su un server che non so che cosa sia, e che potrebbe cambiare un
domani.
Non posso fare affidamento sul sistema operativo.
[...]

Non avevi detto che si trattava di un server sql.
Almeno sai che tipo di dbms si tratta e se potrebbe cambiare in futuro?

Se supporta i trigger e un minimo di procedure (non dico perl, ma
almento plsql), potresti (ma non so se te lo fanno fare) aggiungere una
colonna nullable alla tabella e mettere un trigger sull'inserimento che
dalla data in UTC genera al volo la data in localtime.
Poi con un programma esterno completi la tabella con le date passate.
In questo modo non modifichi il programma che aggiunge i dati, ma ti
calcoli al volo i valori (sempre che sia possibile con il dbms che hai a
disposizione).
E non aggiungi tabelle allo schema.

Questa è veramente la mia ultima idea.

Però una domanda me la devi concedere.
Ok che i dati sono su un db.
Ok che le query devono fare ricerche sulle date e tutto.
Ma le maschere utente non sono scritte in SQL, giusto?

Non è che potresti lavorare sulle maschere di consultazione in modo che
fungano da interfaccia comunicando con l'utente in CET e con il db in
UTC? (se hai scartato questa soluzione, un motivo c'è, ma non l'hai detto)

Luca
Roberto Montaruli
2007-10-11 18:54:07 UTC
Permalink
Post by Luca Pascali
[...]
Post by Roberto Montaruli
Post by Luca Pascali
Passo indietro, allora.
Che linguaggio usi?
SQL, su un server che non so che cosa sia, e che potrebbe cambiare un
domani.
Non posso fare affidamento sul sistema operativo.
[...]
Non avevi detto che si trattava di un server sql.
Ho detto che potevo avvalermi di un linguaggio procedurale senza
possibilita' di chiamate al sistema operativo.
Magari c'era una soluzione in Pascal che poi mi sarei convertito...
Post by Luca Pascali
Almeno sai che tipo di dbms si tratta e se potrebbe cambiare in futuro?
No: ho solo visto il codice SQL della procedura e posso modificare solo
quello.
Post by Luca Pascali
Se supporta i trigger e un minimo di procedure (non dico perl, ma
almento plsql), potresti (ma non so se te lo fanno fare) aggiungere una
colonna nullable alla tabella e mettere un trigger sull'inserimento che
dalla data in UTC genera al volo la data in localtime.
E tu credi che io riesca a spiegare ai capi progetto il problema al
punto di convincerli a farmi impostare dei trigger o chiamate esterne.
Quelli mi rispondono: "Ma sei sicuro? Gli analisti dicono che funziona
perfettamente."
Cosi' poi quando succedera' l'immancabile anomalia a fine mese verranno
da me a chiedere che cosa c'e' che non va nei MIEI applicativi, quando
invece sono le LORO procedure che falliscono.
Post by Luca Pascali
Poi con un programma esterno completi la tabella con le date passate.
In questo modo non modifichi il programma che aggiunge i dati, ma ti
calcoli al volo i valori (sempre che sia possibile con il dbms che hai a
disposizione).
E non aggiungi tabelle allo schema.
Il punto e' che potrebbero migrare tabelle e procedure su un server piu'
grosso, e si dimenticheranno di migrare anche i trigger (che nessuno ha
mai usato perche' non li sanno usare)
Post by Luca Pascali
Questa è veramente la mia ultima idea.
Grazie. Siamo arrivati alle stesse conclusioni a quanto pare.
Post by Luca Pascali
Però una domanda me la devi concedere.
Ok che i dati sono su un db.
Ok che le query devono fare ricerche sulle date e tutto.
Ma le maschere utente non sono scritte in SQL, giusto?
Che ne so come sono fatte le maschere.
Io so che c'e' una procedura che converte il tempo UTC in un datetime
aggiungendo il fuso dell'ora corrente (quella in cui viene effettuata la
chiamata), e quella se la lancio oggi mi aggiunge 2, se la lancio tra un
mese mi aggiungera' 1.
Poi che cosa se ne fanno del dato non e' un mio problema, per fortuna.
Solo che il dato si corrompera' (o meglio, gia' adesso se vanno ad
interrogare i dati di febbraio li vedranno sfasati di un'ora, ma siccome
sono vecchi non se ne accorgono).
Quando in novembre interrogherano i dati di ottobre, saranno tutti sfasati.
Post by Luca Pascali
Non è che potresti lavorare sulle maschere di consultazione in modo che
fungano da interfaccia comunicando con l'utente in CET e con il db in
UTC? (se hai scartato questa soluzione, un motivo c'è, ma non l'hai detto)
Il motivo e' che non e' roba mia, non so chi usa quei dati.
Ho solo trovato in produzione questa procedura, che si comporta in modo
oggettivamente sbagliato, e converte dei dati, che non sono io che
scrivo, ma che andranno ad incrociarsi con i dati che scrivo io in altre
tabelle, per produrre dei risultati di cui ignoro la destinazione.

Vorrei semplicemente prevenire un possibile problema che se emerge, mi
coinvolgera' al 100%.
Roberto Montaruli
2007-10-12 16:56:54 UTC
Permalink
Secondo voi e' sufficiente come condizione

dato dd=giorno mm=mese w=giorno_settimana

dire che
se mm=marzo and dd>=25 and w=domenica
allora siamo nell'ultima domenica di marzo

e analogamente per ottobre.

A me pare sufficiente, ma siccome non ho mai trovato niente del genere
non vorrei che mi sfuggisse qualche controindicazione.


Se funziona ho trovato il modo per risolvere il mio problema.
Luca Pascali
2007-10-12 19:09:48 UTC
Permalink
Post by Roberto Montaruli
Secondo voi e' sufficiente come condizione
dato dd=giorno mm=mese w=giorno_settimana
dire che
se mm=marzo and dd>=25 and w=domenica
allora siamo nell'ultima domenica di marzo
e analogamente per ottobre.
A me pare sufficiente, ma siccome non ho mai trovato niente del genere
non vorrei che mi sfuggisse qualche controindicazione.
Se funziona ho trovato il modo per risolvere il mio problema.
Sì. Facendo un rapido controllo tra il 2000 e il 2020 risulta una sola
domenica sia nell'intervallo di marzo che in quello di ottobre.
Quindi, a meno di editti papali, dovrebbe andare bene.

LP
Giovanni Zezza
2007-10-15 16:13:02 UTC
Permalink
Roberto Montaruli, nel messaggio
Post by Roberto Montaruli
Secondo voi e' sufficiente come condizione
dato dd=giorno mm=mese w=giorno_settimana
dire che
se mm=marzo and dd>=25 and w=domenica
allora siamo nell'ultima domenica di marzo
e analogamente per ottobre.
Direi di sì:

25 lun 25 mar 25 mer 25 gio 25 ven 25 sab 25 dom
26 mar 26 mer 26 gio 26 ven 26 sab 26 dom 26 lun
27 mer 27 gio 27 ven 27 sab 27 dom 27 lun 27 mar
28 gio 28 ven 28 sab 28 dom 28 lun 28 mar 28 mer
29 ven 29 sab 29 dom 29 lun 29 mar 29 mer 29 gio
30 sab 30 dom 30 lun 30 mar 30 mer 30 gio 30 ven
31 dom 31 lun 31 mar 31 mer 31 gio 31 ven 31 sab
Post by Roberto Montaruli
A me pare sufficiente, ma siccome non ho mai trovato niente del genere
non vorrei che mi sfuggisse qualche controindicazione.
A quel che ne so, non è mai successo nulla che abbia interrotto la sequenza
settimanale (neanche il passaggio al calendario gregoriano), o comunque
modificato l'ultima settimana di marzo o ottobre, sicché non mi riesce di
vedere nulla che modifichi il calcolo. Neanche a me però è mai capitato di
vedere calcolare l'ultima domenica del mese in questo modo, forse proprio
perché di solito la domanda è "qual è l'ultima domenica del mese?" e non
"questa è l'ultima domenica del mese?".

Ciao.
Roberto Montaruli
2007-10-15 18:50:50 UTC
Permalink
Post by Giovanni Zezza
A quel che ne so, non è mai successo nulla che abbia interrotto la sequenza
settimanale (neanche il passaggio al calendario gregoriano), o comunque
modificato l'ultima settimana di marzo o ottobre, sicché non mi riesce di
vedere nulla che modifichi il calcolo. Neanche a me però è mai capitato di
vedere calcolare l'ultima domenica del mese in questo modo, forse proprio
perché di solito la domanda è "qual è l'ultima domenica del mese?" e non
"questa è l'ultima domenica del mese?".
Effettivamente hai centrato il punto.
Coinsiderando l'intervallo di giorni dal 25 al 31, se becco la domenica,
quella e' senz'altro l'ultima domenica del mese.

Ma se becco un mercoledi, non sono mica sicuro se l'ultima domenica sia
gia' passata o se debba ancora arrivare, in funzione di sapere se
applicare l'ora estiva oppure no.

Tra l'altro, tanto per complicare le cose, mi sono accorto che la
funzione Oracle che restituisce il giorno della settimana, e'
maledettamente server-depended: se il server e' impostato in inglese la
settimana parte dalla domenica, mentre se in italiano, la settimana
parte da lunedi, o qualcosa del genere.
Non mi ritorna lo stesso valore per lo stesso giorno.

Me ne sono accorto perche' il server di produzione e' settato in un modo
e quello di sviluppo in un altro (evviva le cose fatte bene!)

Io ODIO le localizzazioni dei server! E ho ragione ad odiarle.
Roberto Montaruli
2007-10-17 09:15:42 UTC
Permalink
Ho risolto!

Lascio il codice SQL, nel caso possa interessare a qualcuno.

Prima ho definito una funzione WEEKDAY(DATE) che mi ritorna il numero del
giorno della settimana (0=domenica, 1=lunedi, ... 6=sabato), che funziona
indipendentemente dai settaggi del server, che altrimenti il numero del
giorno della settimana puo' variare.

FUNCTION "WEEKDAY" ( DT IN DATE) RETURN INTEGER
--
-- Ritorna il numero del giorno della settimana
-- 0=Domenica, 1=Lunedi, ... 6=Sabato
-- Senza preoccuparsi del settaggio del server
--
IS
BEGIN
-- Prendo la data nel calendario Giuliano,
-- aggiungo 1 per riportarmi nel range desiderato
-- dividendo in modulo 7 ottengo il risultato voluto
RETURN (MOD(TO_NUMBER(TO_CHAR(DT, 'J')), 7));

END; -- Function WEEKDAY
/


Questa funzione viene usata dalla seguente:

FUNCTION "UTC2DATE" (UTC IN NUMBER) RETURN DATE
--
-- Trasforma una data in formato UTC (numero di secondi trascorsi a
partire dal 1/1/1970)
-- nel formato DATE di Oracle
--
IS
v_DT Date;
v_LS03 Integer;
v_LS10 Integer;
v_YYYY Integer;
BEGIN
-- Data di riferimento iniziale
v_DT := TO_DATE('01-01-1970', 'DD-MM-YYYY');

-- Aggiungo il tempo UTC
v_DT := v_DT + (UTC / 86400);

-- Aggiungo 1 ora per portarmi nel fuso orario italiano
v_DT := v_DT + (1/24);

-- Ricavo l'anno della data
v_YYYY := TO_NUMBER(TO_CHAR(v_DT, 'YYYY'));

-- Ricavo l'ultima domenica di marzo dell'anno v_YYYY
-- sottraendo a 31 il suo giorno della settimana
v_LS03 := 31 - WEEKDAY(TO_DATE('31/03/' || v_YYYY, 'DD/MM/YYYY'));

-- Ricavo l'ultima domenica di ottobre dell'anno YYYY
-- sottraendo a 31 il suo giorno della settimana
v_LS10 := 31 - WEEKDAY(TO_DATE('31/10/' || v_YYYY, 'DD/MM/YYYY'));

-- Se la data e' compresa nell'intervallo di ora estiva, aggiungo
un'altra ora
IF v_DT BETWEEN TO_DATE(v_LS03 || '/03/' || v_YYYY || ' 02:00',
'DD/MM/YYYY HH24:MI')
AND TO_DATE(v_LS10 || '/10/' || v_YYYY || ' 02:00',
'DD/MM/YYYY HH24:MI') THEN
v_DT := v_DT + (1/24);
END IF;

RETURN(v_DT);

END; -- Function UTC2DATE
/

Il succo della funzione sta nel calcolo dell'ultima domenica del mese, che
si ottiene abbastanza banalmente sottraendo al giorno dell'ultimo del
mese, il suo giorno della settimana, con la convenzione 0=dom, 1=lun,
2=mar.

Per esempio se il 31 Marzo fosse stato mercoledi (3) allora l'ultima
domenica di marzo sara' stata il 31-3=28.
In questo modo, anno per anno, la funzione calcola l'ultima domenica del
mese e valuta l'intervallo di tempo in cui si deve applicare l'ora estiva.
Senza tabelle, senza altri casini.
Ed e' indipendente da qualunque settaggio del sistema operativo.
--
questo articolo e` stato inviato via web dal servizio gratuito
http://www.newsland.it/news segnala gli abusi ad ***@newsland.it
Continua a leggere su narkive:
Loading...