Questa è la traduzione dell'articolo Extending The JavaScript Date Object with User Defined Methods di Lawrence O'Sullivan pubblicato originariamente su Digital Web Magazine il 4 marzo 2008. La traduzione viene qui presentata con il consenso dell'editore e dell'autore.
Le applicazioni, in particolare quelle in ambito business, possono spesso richiedere molte manipolazioni delle date. Potrebbe essere tutto più semplice se l'oggetto Javascript Date
offrisse qualche metodo addizionale. In questo articolo mostrerò come aggiungere dei metodi definiti dall'utente all'oggetto in questione, metodi che sono poi ereditati da ciascuna istanza di una data.
Certo, tutti i metodi che vedremo potrebbero essere scritti e definiti come funzioni globali, ma ci sono vantaggi nell'adozione di questa tecnica. Estendendo l'oggetto Date
il namespace globale risulta meno confuso, il codice più leggibile, le performance leggermente migliori (anche se si parla in realtà di nanosecondi). La tecnica presenta anche tutti i benefici della programmazione orientata agli oggetti.
Abbiamo preparato una demo di tutte le funzioni che affronteremo nel corso dell'articolo in modo che possiate verificare dal vivo il codice presentato.
Come funziona
Questa tecnica trae vantaggio dal modo in cui gli oggetti sono implementati in Javascript. I programmatori possono pensare a linguaggi come C++ e Java quando si parla di codice orientato agli oggetti. In quei linguaggi, una classe strettamente tipizzata definisce ciascun oggetto. Tutto è determinato prima del runtime. Javascript è invece basato su prototipi. Una funzione costruttore definisce l'oggetto e i metodi possono essere estesi a runtime. Metodi e proprietà ereditabili sono assegnati alla proprietà prototype del costruttore, che è di per sé un oggetto. Metodi e proprietà sono creati tramite assegnazione, le istanze nuove ed esistenti li ereditano.
Date.prototype.method_name = function ([arguments]) {
// Code goes here
};
Nell'esempio, una funzione anonima è assegnata ad una proprietà chiamata method_name
dell'oggetto prototipo. La sintassi per richiamare il metodo è la seguente:
date_instance.method_name([arguments]);
Nel contesto del codice del metodo, la parola chiave this
fa riferimento all'istanza dell'oggetto.
Ora che sappiamo come funziona, cerchiamo di rendere l'oggetto Date predefinito di Javascript un po' più utile.
Copiare date
In JavaScript l'assegnazione di oggetti crea un riferimento ad un oggetto, NON una copia dell'oggetto. Nel codice che segue, date1
è inizializzato al 10 Febbraio 2008 e quindi assegnato a date2
. Dal momento che date2
non è una copia di date1
, cambiare il giorno al 20 produce effetti su date1
ed entrambe le date sono impostate al 20 Febbraio 2008 nel box di alert. Sono, infatti, istanza della stessa data:
var date1 = new Date(2008, 1, 10); var date2 = date1; date2.setDate(20); alert(date1 + "n" + date2);
Per copiare una data, essa deve essere convertita in un valore primitivo (String, Number o Boolean) e deve essere creata una nuova data. Il metodo getTime()
è perfetto per questo scopo. Esso restituisce la rappresentazione interna in millisecondi della data e può essere usato come un argomento per il costruttore della data stessa. Ecco il codice basilare:
var oldDate = new Date(); // today
var dateCopy = new Date(oldDate.getTime());
Piuttosto che scrivere queste righe dovunque sia necessario o creare una funzione globale, aggiungeremo un metodo copy()
ereditabile al costruttore Date:
Date.prototype.copy = function () { return new Date(this.getTime()); };
Ecco un esempio del suo uso:
var oldDate = new Date(2008, 1, 10); var newDate = oldDate.copy(); newDate.setDate(20); alert(oldDate + "n" + newDate);
Ora, cambiare newDate
non ha effetti su oldDate
. Le date mostrate nell'alert sono il 10 Febbraio 2008 e il 20 Febbraio 2008. Questo metodo è utile quando si ha bisogno di eseguire delle operazioni aritmetiche sulle date mantenendo la data originale.
Ottenere informazioni sulla data
I nostri prossimi quattro metodi sono creati per ottenere il nome o l'abbreviazione del giorno della settimana o del mese in una data. Vengono creati due array: uno per i nomi dei giorni, l'altro per i nomi dei mesi. I metodi getDay()
e getMonth()
dell'oggetto Date
restituiscono un indice usato per accedere al valore nell'array dei nomi dei giorni o dei nomi dei mesi.
Oltre ai problemi derivanti dall'uso di funzioni globali cui si è accennato in precedenza, c'è un altro rischio. Gli array (tutte le variabili) non sono costanti e sono vulnerabili quando si tratta di modificarli. Le costanti possono essere dichiarate con la parola chiave const
nelle versioni più recenti di Javascript, ma ciò non è supportato dalla maggior parte dei browser in uso. Per fortuna Javascript offre delle alternative.
Gli array possono essere trasformati in proprietà del costruttore della data rimuovendoli dal namespace globale. Ci sono due modi per ottenere ciò: come proprietà statiche o come proprietà ereditabili. Un esempio di proprietà statica è PI, che è una proprietà statica dell'oggetto Math
. Gli array sono aggiunti al costruttore Date
assegnandoli ad esso, come mostrato nel seguente esempio:
Date.DAYNAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; Date.MONTHNAMES = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
Si può poi accedere agli array nel modo seguente:
Date.DAYNAMES[index];
Una seconda opzione consiste nel rendere gli array proprietà dell'oggetto prototype
del costruttore della data:
Date.prototype.DAYNAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; Date.prototype.MONTHNAMES = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
La differenza tra questi due approcci è che queste proprietà sono ereditate e sono accessibili solo ai metodi di Date
usando la parola chiave this
(è questo l'approccio seguito in questo articolo).
C'è un terzo compromesso, possiamo fare entrambe le cose. Per esempio:
Date.DAYNAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; Date.prototype.DAYNAMES = Date.DAYNAMES;
(Avendo parlato di proprietà e metodi statici, si deve dire che i metodi presentati qui non possono essere statici, ma devono essere assegnati all'oggetto prototype
. Ci sarebbe bisogno di piccoli cambiamenti per convertire i metodi in metodi statici).
E se si volesse andare a internazionalizzare il tutto? Ecco come fare. Il codice relativo a MONTHNAMES
e DAYNAMES
può essere separato dall'altro metodo in modo tale che possano essere impostati nomi di mesi e giorni specifici di ciascuna lingua:
Date.prototype.DAYNAMES = ["Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"]; Date.prototype.MONTHNAMES = ["Janvier", "Fevrier", "Mars", "Avril", "Mai", "Juin", "Juillet", "Aout", "Septembre", "Octobre", "Novembre", "Decembre"];
Con gli array al loro posto, possono essere definiti quattro metodi con questo codice:
Date.prototype.getFullDay = function() { return this.DAYNAMES[this.getDay()]; }; Date.prototype.getDayAbbr = function() { return this.getFullDay().slice(0, 3); }; Date.prototype.getFullMonth = function() { return this.MONTHNAMES[this.getMonth()]; }; Date.prototype.getMonthAbbr = function() { return this.getFullMonth().slice(0, 3); };
Ed ecco un esempio che usa questi metodi:
var example_date = new Date(2008, 1, 20); alert("Day: " + example_date.getFullDay() + "nDay abbr: " + example_date.getDayAbbr() + "nMonth: " + example_date.getFullMonth() + "nMonth Abbr: " + example_date.getMonthAbbr());
L'alert mostra:
Esempio di output su console
Day: Wednesday Day abbr: Wed Month: February Month: Abbr Feb
Si chiude qui la prima parte. Continueremo con nuovi metodi la prossima settimana.
Formattare l'ora del giorno
L'oggetto Date offre due metodi per ottenere una rappresentazione sotto forma di stringa della parte relativa all'ora in una data: toTimeString()
e toLocaleTimeString()
. Il formato delle stringhe è basato sulle impostazioni del client e può variare da utente a utente. Se in generale è una cosa che può andare bene, può però non funzionare in certe applicazioni. I due metodi seguenti restituiscono un formato definito.
Il primo restituisce l'ora nel formato "hh:mm:ss/pm" con numeri composti da due cifre:
Date.prototype.to12HourTimeString = function () { var h = this.getHours(); var m = "0" + this.getMinutes(); var s = "0" + this.getSeconds(); var ap = "am"; if (h >= 12) { ap = "pm"; if (h >= 13) h -= 12; } else if (h == 0) h = 12; h = "0" + h; return h.slice(-2) + ":" + m.slice(-2) + ":" + s.slice(-2) + " " + ap; };
Il secondo metodo restituisce l'ora nel formato 24-h, di nuovo con numeri composti da due cifre:
Date.prototype.to24HourTimeString = function () { var h = "0" + this.getHours(); var m = "0" + this.getMinutes(); var s = "0" + this.getSeconds(); return h.slice(-2) + ":" + m.slice(-2) + ":" + s.slice(-2); };
Forse l'unica cosa interessante in questo codice riguarda è l'uso di slice()
. Per ottenere il numero a due cifre, esse sono concatenate con uno zero iniziale; si usa quindi slice()
per ottenere le due cifre più a destra; se il numero è inferiore a 10, viene incluso lo zero iniziale. I metodi sono così:
var dte = new Date(2008, 1, 20, 19, 30, 5); alert(dte.to12HourTimeString() + "n" + dte.to24HourTimeString());
Il box di alert mostra
07:30:05 pm 19:30:05
Ottenere il numero di giorni in un mese
A volte è necessario sapere quanti giorni ci sono in un mese. Mentre un array può essere usato per questo scopo (con un po' di codice in più per il mese di Febbraio), c'è un modo più semplice per ottenere il risultato finale.
JavaScript provvederà ad aggiustare (per eccesso o difetto) le date create con valori altrimenti non validi. Per esempio, se creiamo la data 33 Febbraio 2008 in questo modo
var d = new Date(2008, 1, 33); alert(d);
la data creata e mostrata a video nell'alert sarà il 4 Marzo 2008 (il 2008 è un anno bisestile).
Di conseguenza, possiamo dire che l'ultimo giorno di un mese - e il numero di giorni di un mese - è il giorno zero del mese seguente, ovvero un giorno prima del primo. Il mese seguente è calcolato come il mese corrente (date.getMonth()
) più uno. Javascript restituirà Gennaio dell'anno seguente quando si aggiunge uno a Dicembre. Il codice per calcolare l'ultimo giorno del mese è il seguente:
Date.prototype.lastday = function() { var d = new Date(this.getFullYear(), this.getMonth() + 1, 0); return d.getDate(); };
Il numero di giorni nel mese di qualunque data può essere trovato così:
var d = new Date(2008, 1, 5); alert(d.lastday());
Il box di alert mostrerà il numero di giorni nel mese: in questo esempio 29 (il secondo argomento, 1, nel costruttore della data corrisponde a Febbraio, mentre Gennaio è 0).
Trovare il numero di giorni tra due date
Nelle applicazioni business è spesso necessario determinare il numero di giorni tra due date. Creeremo ora un metodo getDaysBetween()
. Ma prima aggiungiamo un'altra proprietà al prototipo del costruttore della data, una proprietà che chiameremo msPERDAY
. Il suo valore è il numero di millisecondi in 24 ore. Ecco l'unica riga di codice di cui abbiamo bisogno:
Date.prototype.msPERDAY = 1000 * 60 * 60 * 24;
Detto altrimenti, 1000 millisecondi in un secondo, 60 secondi in un minuto, 60 minuti in un'ora, 24 ore in un giorno.
Nella maggior parte dei casi determinare il numero di giorni tra due date esclude la presa in considerazione dell'ora. Dunque, i giorni compresi tra 1 Febbraio e 10 Febbraio sono 9, a prescindere dall'ora (almeno nello scenario che useremo come modello). Il fatto è che nelle data Javascript è compresa pure l'indicazione dell'ora, così dobbiamo apportare qualche modifica:
Date.prototype.getDaysBetween = function(d) { d = d.copy(); d.setHours(this.getHours(), this.getMinutes(), this.getSeconds(), ... this.getMilliseconds()); var diff = d.getTime() - this.getTime(); return (diff)/this.msPERDAY; };
Nel codice riportato qui sopra, viene creata una nuova data con lo stesso valore come argomento, per evitare di alterare la data originale. La parte relativa all'ora della nuova data è impostata per corrispondere all'ora della data che richiama il metodo, eliminando l'influenza dell'ora del giorno.
Il metodo è usato in questo modo:
var today = new Date(); var birthday = new Date(2008, 9, 31); var days = today.getDaysBetween(birthday); if (days > 0) alert(days + " days 'til my birthday."); else if (days < 0) alert(days + " days since my birthday."); else alert("It's my birthday!!");
Se la data passata come argomento è successiva alla data usata per richiamare il metodo, viene restituito un valore positivo. Viene invece restituito un valore negativo quando l'argomento è rappresentato da una data precedente. Anche un valore assoluto potrebbe essere restituito, ma sapere quale data è precedente può essere importante, come nell'esempio.
Si chiude qui la seconda parte. Continueremo con nuovi metodi la prossima settimana.
Calcolare il giorno dell'anno
Combinando getDaysBetween()
e la tecnica usata per calcolare l'ultimo giorno del mese, possiamo creare un metodo per calcolare il giorno dell'anno (da 1 a 366) per una data specifica:
Date.prototype.getDayOfYear = function() { var start = new Date(this.getFullYear(), 0, 0); return this.getDaysBetween(start) * -1; };
Dal momento che non c'è un giorno 0 dell'anno, la data in questione viene confrontata con il giorno che precede il primo Gennaio: il 31 Dicembre. La prima riga crea un'istanza di Date con il valore corrispondente al 31 Dicembre dell'anno precedente. La data iniziale viene creata usando la stessa tecnica usata per il metodo dell'ultimo giorno del mese. Il primo 0 è Gennaio e il secondo è il numero del giorno del mese, che corrisponde al giorno prima dell'1 Gennaio.
Poiché il metodo getDaysBetween()
restituisce un numero negativo quando l'argomento è una data precedente, viene moltiplicato per -1. Il giorno dell'anno dovrebbe essere un numero positivo.
Il codice seguente restituirà come risultato 65:
var date = new Date(2008, 2, 5); // 5 Marzo 2008
alert(date.getDayOfYear());
Usare rappresentazioni della data come argomenti
Il metodo getDaysBetween()
mostrato in precedenza accetta un oggetto Date
come suo argomento, ma può essere più conveniente usare altri tipi di dati per rappresentare una data. Il costruttore Date
ha quattro differenti invocazioni:
new Date(); // nessun argomento, usa la data e l'ora correnti new Date("Feb 20, 2008 10:20:05"); // rappresentazione della data sotto forma di stringa new Date(2008, 1, 20, 10, 20, 05); // numeri che rappresentano la data new Date(milliseconds); // numero di millisecondi
Qualunque metodo noi creiamo che accetta una data come argomento può essere modificato per accettare qualunque argomento riconosciuto come valido dal costruttore Date
con l'aggiunta del seguente codice:
// Codice extra per le opzini degli argomenti if (arguments.length == 0) { d2 = new Date(): } else if (d instanceof Date) { d2 = new Date(d.getTime()); } else if (typeof d == "string") { d2 = new Date(d); } else if (arguments.length >= 3) { var dte = [0, 0, 0, 0, 0, 0]; for (var i = 0; i < arguments.length; i++) { dte [i] = arguments[i]; } d2 = new Date(dte[0], dte[1], dte[2], dte[3], dte[4], dte[5]); } else if (typeof d == "number") { d2 = new Date(d); } else { return null; } if (d2 == "Invalid Date") return null; // Fine del codice extra d2.setHours(this.getHours(), this.getMinutes(), this.getSeconds(), this.getMilliseconds()); var diff = d2.getTime() - this.getTime(); return (diff)/this.msPERDAY; };
Se l'argomento passato non viene convertito in una data, il costruttore crea un'istanza di data con il valore "Invalid date" ("Data non valida"). In quel caso viene restituito null
.
(Per evitare di essere dispersivi nel corso dell'articolo noi presumeremo sempre che l'argomento è una data).
Aggiungere giorni, settimane, mesi e anni ad una data
Ecco ora quattro metodi - addDays()
, addWeeks()
, addMonths()
, addYears()
- per cambiare una data aggiungendo o sottraendo un numero alla data. Ogni metodo prende un argomento numerico che rappresenta il numero di giorni, settimane, mesi o anni da aggiungere. Il codice per i metodi è piuttosto semplice:
Date.prototype.addDays = function(d) { this.setDate( this.getDate() + d ); }; Date.prototype.addWeeks = function(w) { this.addDays(w * 7); }; Date.prototype.addMonths= function(m) { var d = this.getDate(); this.setMonth(this.getMonth() + m); if (this.getDate() < d) this.setDate(0); }; Date.prototype.addYears = function(y) { var m = this.getMonth(); this.setFullYear(this.getFullYear() + y); if (m < this.getMonth()) { this.setDate(0); } };
Il primo metodo, addDays()
, usa lo stesso principio del metodo lastDay()
. Il metodo getDate()
dell'oggetto Date restituisce il numero del giorno del mese (da 1 a 31). Il numero di giorni addizionali è aggiunto ad esso e quindi il metodo setDate()
dell'oggetto Date imposta il giorno del mese al nuovo giorno. Quando un numero usato per una data va oltre (o è inferiore) il numero dei giorni nel mese, Javascript conta i giorni in avanti (o indietro) fino alla data corretta.
Il metodo addWeeks()
converte le settimane in giorni e quindi usa il metodo addDays()
per cambiare la data. Se per esempio la data è per un Mercoledì, la nuova data sarà un Mercoledì.
Quando si aggiungono mesi, uno si aspetterebbe che la nuova data sia lo stesso giorno del mese - se un mese viene aggiunto al 2 Gennaio il risultato dovrebbe essere il 2 Febbraio - ma, se un mese viene aggiunto al 31 Gennaio, il risultato, senza aggiustamenti, sarebbe il 3 Marzo (2 Marzo in un anno bisestile). Per il nostro scopo dovrebbe invece essere l'ultimo giorno del mese di Febbraio. C'è dunque un test per determinare se il giorno del mese risultante è meno, come valore numerico, della data originale. Se lo è, indica che la data è stata portata in un mese successivo a quello che vogliamo. La correzione usa la stessa tecnica vista per il metodo lastDay()
: la data è impostata al giorno zero.
Il metodo addYears()
soffre di un problema simile negli anni bisestili. Quando si aggiunge un anno al 29 Febbraio il risultato è 1 Marzo, così è necessario riportarlo al 28 Febbario:
var date = new Date(2008, 1, 1); // 1 Febbraio 2008
alert(date.addDays(7));
alert(date.addWeeks(3));
alert(date.addMonths(2));
alert(date.addYears(-2));
Il box di alert mostra, rispettivamente, 8 Febbario 2008, 29 Febbraio 2008, 29 Aprile 2008, 29 Aprile 2006.
Aggiungere solo giorni feriali
In alcune applicazioni business vengono contati solo i giorni feriali. Potrebbe essere un problema quando si calcola, per esempio, la data di completamento di un progetto e non si vuole tenere conto dei giorni non lavorativi, compresi quelli del weekend. Il prossimo metodo aggiunge un certo numero di giorni feriali, con l'aggiustamento per i fine settimana. In questo modo il periodo tra la data originale e la nuova data include il numero richiesto di giorni:
Date.prototype.addWeekDays = function(d) { var startDay = this.getDay(); // current weekday 0 thru 6 var wkEnds = 0; //# of weekends needed var partialWeek = d % 5; //# of weekdays for partial week if (d < 0) { //subtracting weekdays wkEnds = Math.ceil(d/5); //negative number weekends switch (startDay) { case 6: //start Sat. 1 less weekend if (partialWeek 0 && wkEnds < 0) wkEnds++; break; case 0: //starting day is Sunday if (partialWeek 0) d++; //decrease days to add else d--; //increase days to add break; default: if (partialWeek <= -startDay) wkEnds--; } } else if (d > 0) { //adding weekdays wkEnds = Math.floor(d/5); var w = wkEnds; switch (startDay) { case 6: // If staring day is Sat. and // no partial week one less day needed // if partial week one more day needed if (partialWeek 0) d--; else d++; break; case 0: //Sunday if (partialWeek 0 && wkEnds > 0) wkEnds—; break; default: if (5 – day < partialWeek) wkEnds++; } } d += wkEnds * 2; this.addDays(d); };
Il metodo addWeekDays()
calcola il numero di giorni dei weekend che dovranno essere calcolati nel range della data e aggiunge questo numero ai giorni feriali richiesti. La somma è aggiunta alla data usando il nostro metodo addDays()
, addWeekDays()
produce sempre una data tra Lunedì e Venerdì incluso. Per esempio: è Sabato e stiamo completando il piano per un progetto che inizia Lunedì. Abbiamo stabilito che il progetto durerà 50 giorni e vogliamo calcolare la data prevista per il completamento. Il codice seguente calcolerà la data:
var today = new Date(2008, 1, 23); var endDate = today.copy(); endDate.addWeekDays(50); alert(endDate);
L'alert mostrerà 2 Aprile 2008.
Purtroppo, il management decide ora che il progetto dovrebbe essere rimandato in modo che sia completato il 31 Ottobre. Vogliono allora sapere quale dovrebbe essere la data di inizio:
var endDate = new Date(2008, 9, 31); var startDate = endDate.copy(); startDate.addWeekDays(-50); alert(startDate);
L'alert mostrerà il 22 Agosto 2008.
Tutto ciò non tiene conto delle eventuali feste o delle giornate speciali in cui l'azienda è chiusa. Per tenere conto delle feste possiamo includere addWeekDays()
in un loop che aumenta i giorni richiesti di uno per ogni giornata di festa compresa nel range della data, da quella iniziale a quella finale. Ecco una funzione di esempio:
function due_date(start_date, workDays) { var dueDate = new Date(); var days = 0; var holidays = 0; do { dueDate.setYear(start_date.getFullYear()); dueDate.setMonth(start_date.getMonth()); dueDate.setDate(start_date.getDate()); days = workDays + holidays; dueDate.addWeekDays(days); // Your own lookup. // Search database or array for // holidays between start_date and dueDate holidays = [query-results] } while (days != workDays + holidays); return dueDate; }
(La funzione dueDate()
non è resa come un metodo dell'oggetto Date
perché richiede una fonte di dati esterna che è soggetta a possibili cambiamenti).
Il loop inizia impostando dueDate()
sul valore di startDate()
. Aggiunge le vacanze (inizialmente zero) ai giorni lavorativi e usa il metodo addWeekDays()
per ottenere la data di consegna del progetto. La query ricava il numero di feste tra la data iniziale e quella di consegna (esclusi i sabati e le domeniche). Se il numero delle feste più il numero originale di giorni lavorativi equivale al numero di giorni usati per calcolare la data di consegna, il lavoro è finito. Altimenti la data di consegna viene resettata e il loop viene nuovamente eseguito con il nuovo numero dei giorni festivi.
Si chiude qui la terza parte. Continueremo con nuovi metodi la prossima settimana.
Calcolare il tempo tra due date
Molte applicazioni business hanno bisogno di determinare il tempo compreso tra due date in unità di giorni, mesi, anni o giorni feriali. Abbiamo già creato un metodo per calcolare i giorni compresi tra due date: getDaysBetween()
.
Accanto a addWeekDays()
troviamo pure getWeekDays()
. Calcola il numero di giorni feriali compresi tra due date. Come addWeekDays()
, questo metodo non tiene conto dei giorni festivi, ma potrebbe essere inserito nel contesto di una funzione come questa:
Date.prototype.getWeekDays = function(d) {
var wkEnds = 0;
var days = Math.abs(this.getDaysBetween(d));
var startDay = 0, endDay = 0;
if (days) {
if (d < this) {
startDay = d.getDay();
endDay = this.getDay();
} else {
startDay = this.getDay();
endDay = d.getDay();
}
wkEnds = Math.floor(days/7);
if (startDay != 6 && startDay > endDay)
wkEnds++;
if (startDay != endDay && (startDay 6 || endDay 6) )
days-;
days -= (wkEnds * 2);
}
return days;
};
Il codice determina quanti giorni di weekend devono essere sottratti dal numero totale dei giorni tra le due date. Inoltre, a differenza degli altri metodi, restituisce un valore assoluto. Viene usato così:
var endDate = new Date(2007, 11, 5); // 5th December 2007
var startDate = new Date(2008, 1, 20); // 20th February 2008
alert(startDate.getWeekDays(endDate));
L'alert mostrerà 55, il numero di giorni compreso tra le due date.
Un semplice algoritmo per calcolare i mesi tra due date potrebbe essere questo:
var d1 = this.getFullYear() * 12 + this.getMonth(); var d2 = d.getFullYear() * 12 + d.getMonth(); return d2 - d1;
Funzionerebbe per le date che si estendono per diversi anni in cui le frazioni di mesi non sono tanto significative.
Calcolare i mesi tra due date è complicato dalla frazione di mese per ciascuna data. Ragionevolmente c'è più di un mese tra l'1 Gennaio e il 28 Febbraio. I giorni da includere come mese parziale dipendono dall'ordine cronologico delle date. Abbiamo bisogno sia dei giorni dalla data fino alla fine del mese, sia dal primo del mese alla data. Vogliamo anche che il codice restituisca un valore negativo se la data passata come argomento è quella antecedente tra le due:
Date.prototype.getMonthsBetween = function(d) {
var sDate, eDate;
var d1 = this.getFullYear() * 12 + this.getMonth();
var d2 = d.getFullYear() * 12 + d.getMonth();
var sign;
var months = 0;
if (this == d) {
months = 0;
} else if (d1 == d2) { //same year and month
months = (d.getDate() - this.getDate()) / this.lastday();
} else {
if (d1 < d2) {
sDate = this;
eDate = d;
sign = 1;
} else {
sDate = d;
eDate = this;
sign = -1;
}
var sAdj = sDate.lastday() - sDate.getDate();
var eAdj = eDate.getDate();
var adj = (sAdj + eAdj) / sDate.lastday() - 1;
months = Math.abs(d2 - d1) + adj;
months = (months * sign)
}
return months;
};
Le due date sono convertite in un numero di tutti i mesi da un punto comune, anno 1. Poi il codice si dirama in base all'ordine cronologico della date. Se le date sono le stesse, il numero di mesi è uguale a 0. Se le date hanno lo stesso anno e mese, il numero di mesi è uguale alla differenza tra le date divisa per il numero di giorni nel mese. Altrimenti, vengono create delle variabili di riferimento che indicano quale data è antecedente. Dopodiché, viene calcolato il numero di giorni rimasto nella data antecedente, viene anche calcolato il numero di giorni nel mese successivo, viene calcolato qual è il giorno del mese e la somma viene divisa per il numero di giorni nel precedente dei due mesi (pheeewww!).
Il numero di giorni nel mese antecedente è usato come divisore perché la somma sarà uguale al numero dei giorni nel mese antecedente se i giorni sono gli stessi.
Avendo creato getMonthsBetween()
, è facile scrivere un metodo per calcolare gli anni compresi tra due date. Tutto ciò che ci serve è dividere il numero dei mesi per 12:
Date.prototype.getYearsBetween = function(d) { var months = this.getMonthsBetween(d); return months/12; };
Un metodo speciale, getAge()
, può essere creato usando il metodo getYearsBetween()
, in questo modo:
Date.prototype.getAge = function() { var today = new Date(); return this.getYearsBetween(today).toFixed(2); };
Si può usare così:
var dateOfBirth = new Date(yob, mob, dob); var age = dateOfBirth.getAge();
L'ultimo metodo, sameDayEachWeek()
, è un po' diverso. Restituisce un array di date per un certo giorno della settimana, da Domenica a Domenica, per ogni settimana compresa tra due date. Potrebbe essere usato in applicazioni per lo scheduling di eventi ricorrenti:
Date.prototype.sameDayEachWeek = function (day, date) { var aDays = new Array(); var eDate, nextDate, adj; if (this > date) { eDate = this; nextDate = date.copy(); } else { eDate = date; nextDate = this.copy(); } adj = (day - nextDate.getDay() + 7) %7; nextDate.setDate(nextDate.getDate() + adj); while (nextDate < eDate) { aDays[aDays.length] = nextDate.copy(); nextDate.setDate(nextDate.getDate() + 7); } return aDays; };
Il metodo prende un valore relativo ad un giorno della settimana (da 0 a 6, da Domenica a Sabato) e una data come argomenti. La prima sezione del codice calcola quale delle date è quella antecedente. Crea una data da incrementare, nextDate
, che è la copia della data antecedente, come punto di partenza, e un riferimento alla data successiva come data finale. Un nuovo oggetto date
è usato per nextDate
in modo che possa essere cambiato senza avere effetti sulla data originale.
Poi viene calcolata la data del giorno della prima settimana che stiamo cercando. È necessario sottrarre da day
prima di aggiungere: la sottrazione converte una stringa in un numero così i numeri saranno aggiunti e non concatenati.
Dopo, un loop fa avanzare nextDate
di sette giorni ad ogni passaggio fino a quando la data non rientra più nel range. Ogni passaggio aggiunge un nuovo oggetto Date
all'array restituito.
Il metodo funziona così:
var enddate = new Date(2008, 0, 5); // 5th January 2008 var date = new Date(2008, 1, 10); // 10th February 2008 var listofdays = date.sameDayEachWeek(2, enddate); // 2 = Tuesday alert("Tuedays in range are:nn" + listofdays.join("n"));
Ecco cosa mostra l'alert:
Tuesdays in range are: Tue Jan 08 2008 00:00:00 GMT-0600 (Central Standard Time) Tue Jan 15 2008 00:00:00 GMT-0600 (Central Standard Time) Tue Jan 22 2008 00:00:00 GMT-0600 (Central Standard Time) Tue Jan 29 2008 00:00:00 GMT-0600 (Central Standard Time) Tue Feb 05 2008 00:00:00 GMT-0600 (Central Standard Time)
Mettere tutto insieme
Molti dei metodi presentati possono essere usati senza bisogno degli altri.Tenendo in mente che alcuni metodi dipendono da copy()
, lastday()
, getDaysBetween()
e getMonthsBetween()
, e che alcuni altri dipendono dalle tre proprietà create, potete implementare solo il codice di cui avete bisogno in un progetto.
Metodi possono essere aggiunti ad ognuno degli altri costruttori di base - Array, String, Math, Object - usando la stessa tecnica: assegnarli alla proprietà prototype.
Spero che possiate trovare utili questi metodi nel vostro lavoro e di avervi mostrato come sia facile estendere gli oggetti predefiniti di Javascript con codice adatto ai propri scopi. Aggiungere metodi attraverso prototype
dà chiarezza al vostro codice e anche alcuni benefici in termini di performance.
Codice sorgente
Scaricate pure tutti gli esempi in un singolo file e usate i metodi definiti nell'articolo nelle vostre applicazioni:
File zip contenente i sorgenti, oltra alle versione compresse.
Per includere il file, collegatelo semplicemente all'interno della sezione <head>
(o in fondo al body
del documento):
<script type="text/javascript" src="path/to/file/dates.js"></script>
Potete anche studiare online la demo per vedere in azione le varie funzioni.
Approfondimenti
- Exploring built-in Objects ha molti esempi su come estendere gli oggetti predefiniti;
- JavaScript Objects Part 4: Extending JavaScript Objects and Classes di Jeff Cogswell fornisce una panoramica dettagliata su come estendere gli oggetti predefiniti di Javascript;
- Javascript 1.6, implementato su Firefox 1.5 and successivi, aggiunge nuovi metodi all'ogetto array. Ma, per usarli sul web, devono essere aggiunti all'oggetto array dei browser che ancora non li implementano. Il sito di Mozilla per gli sviluppatori offre codice di supporto per
indexOf
,lastIndexOf
,every
,filter
,forEach
,map
esome
nei browser che non hanno ancora implementato JavaScript 1.6; - Infine, l'articolo Porting JavaScript 1.6 Array methods di Dustin Diaz è un'altra fonte di codice per aggiungere il supporto per i nuovi metodi.