57 messaggi dal 24 marzo 2008
Ciao a tutti :)

per motivi lavorativi ho a che fare (purtroppo) con dati relativi a soggetti con gravi patologie e, tra questi, vi è l'età alla diagnosi.

Il calcolo è semplice: basta vedere quanti anni sono trascorsi tra la data di nascita del soggetto e quella di diagnosi della malattia.

Trattandosi però di calcoli da effettuare con la massima precisione, mi sono trovato di fronte al problema degli anni bisestili, che in alcuni casi falsano il risultato.

Faccio un esempio

Nascita : 01/01/1980
Diagnosi : 29/12/1989

gli anni sarebbero 9 ma, poichè tra il 1980 ed il 1989 ci sono 3 anni bisestili, si avrebbero tre giorni in più che fanno oltrepassare il 1 gennaio, restituendo come anni 10.

La mia idea è stata quella di considerare sempre tutti gli anni come di 365 giorni:


        private short EtaDiagnosi(string dtaNascita, string dtDiagnosi)
        {
            int anniBisestili = 0;

            try
            {

                //Converte in date i parametri
                DateTime dtaNas = DateTime.Parse(dtaNascita);
                DateTime dtaDiag = DateTime.Parse(dtDiagnosi);

                //Calcola gli anni bisestili nel range temporale
                for (int i = dtaNas.Year; i <= dtaDiag.Year; i++)
                {
                    if (DateTime.IsLeapYear(i))
                        anniBisestili++;
                }

                //Calcola la differenza in giorni tra le due date escludendo i giorni in più degli anni bisestili
                Double diffDate = dtaDiag.Subtract(dtaNas).TotalDays - anniBisestili;

                //Ritorna la differenza espressa in anni
                return Convert.ToInt16(Math.Floor(diffDate / 365));
            }
            catch
            {
                //In caso di errore ritorna -1
                return -1;
            }

        }



Ho fatto alcuni test e sembra funzionare perfettamente (l'esempio iniziale torna 9 anni).

Pensate che possa essere una buona soluzione?

Vi ringrazio anticipatamente e spero che questo codice sia utile a chi abbia un problema simile.

A presto,
Davide
Modificato da dadox77 il 01 luglio 2012 13.34 -

Nulla è reale...tutto è lecito...
11.886 messaggi dal 09 febbraio 2002
Contributi
ciao, dato che devi lavorare con numeri interi evita il tipo double, se possibile. Infatti i calcoli in virgola mobile in certi casi *potrebbero* darti problemi di arrotondamento.

Prova invece a fare il calcolo usando le sole proprietà .Year, .Month e .Day degli oggetti DateTime, che sono degli interi.
Ad esempio potresti fare:

//calcolo la differenza degli anni
var eta = dtaDiag.Year - dtaNas.Year;
//e se il compleanno non è ancora trascorso, allora tolgo l'anno che non ha ancora compiuto
if (dtaNas.Month >= dtaDiag.Month && dtaNas.Day >= dtaDiag.Day)
  eta--;

return eta;


Invece, a proprosito del parsing delle date, potresti usare il metodo DateTime.TryParse che è molto più efficiente di un blocco try... catch. Lo dico così, eh, dato che hai chiesto un parere. In realtà non noterai alcuna differenza nelle prestazioni dato che devi solo parsare due date. Tuttavia, quella di usare TryParse resta una buona pratica.

Poi, (giusto come appunto) sii cosciente che se hai utenti internazionali potrebbero inserire date in formati diversi dal nostro e quindi il parsing deve rispettare questa preferenza dell'utente. Invece, se hai solo utenti italiani, puoi fregartene della globalizzazione e assicurarti solamente che la cultura in uso dall'applicazione sia sempre quella italiana.


return -1;

Anche qui, se possibile, evita i cosiddetti "numeri magici", cioè i numeri a cui la tua applicazione dà un significato speciale.
Restituisci null, se la funzione non riesce ad effettuare il calcolo. Puoi usare un tipo nullable come valore di ritorno.

//uso byte perché è difficile che una persona abbia più di 255 anni :)
private byte? EtaDiagnosi(string dtaNascita, string dtDiagnosi)


In ultimo, non dare per scontato che dtaDiag sia maggiore di dtaNas. Chi inserisce i dati può sbagliare a digitare e, se anche hai un meccanismo di validazione lato client, devi sempre ricontrollare che i valori ricevuti dalla funzione rispettino un certo criterio. Anche in questo caso, restituisci null.

ciao
Modificato da BrightSoul il 01 luglio 2012 16.50 -

Enjoy learning and just keep making
57 messaggi dal 24 marzo 2008
Ciao Bright, grazie per i (soliti) preziosi suggerimenti :)



//calcolo la differenza degli anni
var eta = dtaDiag.Year - dtaNas.Year;
//e se il compleanno non è ancora trascorso, allora tolgo l'anno che non ha ancora compiuto
if (dtaNas.Month >= dtaDiag.Month && dtaNas.Day >= dtaDiag.Day)
  eta--;

return eta;



Ottimo suggerimento, tuttavia potrebbe presentarsi un problema di questo tipo:

Es:

Data nascita : 01/02/1977
Data Diagnosi : 31/01/1997

In questa condizione si avrebbe il mese di nascita >= mese di diagnosi, ma non giorno di nascita >= giorno diagnosi, e di conseguenza il decremento non scatterebbe, rendendo valido anche l'ultimo anno (non compiuto). Che ne pensi?



Poi, (giusto come appunto) sii cosciente che se hai utenti internazionali potrebbero inserire date in formati diversi dal nostro e quindi il parsing deve rispettare questa preferenza dell'utente. Invece, se hai solo utenti italiani, puoi fregartene della globalizzazione e assicurarti solamente che la cultura in uso dall'applicazione sia sempre quella italiana.



L'applicazione fortunatamente è una windows forms application destinata ad un solo pubblico italiano (è per i centri regionali che raccolgono i casi) ;)



In ultimo, non dare per scontato che dtaDiag sia maggiore di dtaNas. Chi inserisce i dati può sbagliare a digitare e, se anche hai un meccanismo di validazione lato client, devi sempre ricontrollare che i valori ricevuti dalla funzione rispettino un certo criterio. Anche in questo caso, restituisci null.



I controlli sulle date vengono fatti fatti prima di passare i valori alla funzione, quindi ho sempre date valide in input, sia come formato che come valore ;)



Anche qui, se possibile, evita i cosiddetti "numeri magici", cioè i numeri a cui la tua applicazione dà un significato speciale.
Restituisci null, se la funzione non riesce ad effettuare il calcolo. Puoi usare un tipo nullable come valore di ritorno.



Ottimo suggerimento, non ci avevo sinceramente pensato :)

Posterò a breve una versione "ottimizzata", così poi ne parliamo.

A presto,
Davide
Modificato da dadox77 il 01 luglio 2012 19.06 -

Nulla è reale...tutto è lecito...
11.886 messaggi dal 09 febbraio 2002
Contributi
ciao, prego! Scusami ma mi sono accorto di aver sbagliato a scrivere l'espressione per il calcolo dell'età. Così dovrebbe andar meglio:

var eta = dtaDiag.Year - dtaNas.Year;
//se il mese del compleanno non è arrivato, o se siamo nello stesso mese ma il giorno deve ancora arrivare...
if (dtaNas.Month > dtaDiag.Month || (dtaNas.Month==dtaDiag.Month && dtaNas.Day > dtaDiag.Day))
  eta--;


dadox77 ha scritto:

Posterò a breve una versione "ottimizzata", così poi ne parliamo.

va bene, ciao :)
Modificato da BrightSoul il 01 luglio 2012 20.11 -

Enjoy learning and just keep making
57 messaggi dal 24 marzo 2008
Ecco la nuova versione, testata e funzionante ;)


        private byte? EtaDiagnosi(string dtaNascita, string dtDiagnosi)
        {
            DateTime dtaNas;
            DateTime dtaDiag;

            byte appoEtaDiag;

            try
            {
                //Tenta un cast a datetime dei parametri stringa in input
                if (!DateTime.TryParse(dtaNascita, out dtaNas) ||
                    !DateTime.TryParse(dtDiagnosi, out dtaDiag))
                    return null;

                //Effettua un primo calcolo dell'età alla diagnosi tramite differenza tra l'anno di diagnosi e
                //quello di nascita
                appoEtaDiag = Convert.ToByte(dtaDiag.Year - dtaNas.Year);

                //Se il mese di nascita è maggiore di quello di diagnosi viene escluso l'ultimo anno dal
                //calcolo (compleanno non ancora compiuto
                if (dtaNas.Month > dtaDiag.Month || (dtaNas.Month == dtaDiag.Month && dtaNas.Day > dtaDiag.Day))
                    appoEtaDiag--;

                //Ritorna il risultato
                return appoEtaDiag;
            }
            catch (Exception)
            {
                //null in caso di errori
                return null;
            }

        }



Che ne dici?

A presto :)

Nulla è reale...tutto è lecito...
11.886 messaggi dal 09 febbraio 2002
Contributi
Benone! Ora non resta che rimuovere il blocco try...catch.

Prima di farlo, però, elimina l'ultima fonte di eccezioni: la conversione a byte.
appoEtaDiag = Convert.ToByte(dtaDiag.Year - dtaNas.Year);
Questo ti può dare una OverflowException se il risultato dovesse essere maggiore di 255 o minore di 0 (eventualità che si possono verificare se l'utente ha sbagliato a digitare la data).

Per sicurezza, vai a controllare il risultato prima di convertirlo a byte.

//Uso una variabile int, fintanto che faccio operazioni sugli interi
//Effettuerò la conversione a byte solo alla fine, nell'istruzione return
int appoEtaDiag = dtaDiag.Year - dtaNas.Year;
if (dtaNas.Month > dtaDiag.Month || (dtaNas.Month == dtaDiag.Month && dtaNas.Day > dtaDiag.Day))
                    appoEtaDiag--;

//qui controllo se il valore intero è fuori dai limiti del byte
if (appoEtaDiag < byte.MinValue || appoEtaDiag > byte.MaxValue)
  return null; //niente da fare, non era nei limiti

return Convert.ToByte(appoEtaDiag); //Ok, ritorno il valore. Sono sicuro che posso convertirlo a byte.

Ora puoi tranquillamente rimuovere il try catch.

ciao!
Modificato da BrightSoul il 01 luglio 2012 23.44 -

Enjoy learning and just keep making

Torna al forum | Feed RSS

ASPItalia.com non è responsabile per il contenuto dei messaggi presenti su questo servizio, non avendo nessun controllo sui messaggi postati nei propri forum, che rappresentano l'espressione del pensiero degli autori.