Construire des dates exprimées en UTC via Javascript

Le constructeur Date comporte plusieurs formes permettant de créer une instance de Date :

new Date() donne la date courante (dans l'horaire du poste dans lequel le navigateur est lancé)

new Date(1974,02,10,19,20,30,461) permet de créer une date en lui spécifiant des paramètres exprimés dans la zone horaire du poste client.

Dans tous les cas, la forme imprimable de la date (le toString) est exprimé dans la zone horaire du poste client dans lequel le navigateur est lancé.

En fait,  La seule forme du constructeur Date permettant de créer une date en spécifiant des arguments exprimés en UTC est de passer un nombre de millisecondes au constructeur, par exemple :

new Date(1268211600000)

En javascript, l'affichage standard des dates est toujours exprimé en temps local (par exemple : Wed Mar 10 2010 10:00:00 GMT+0100) et s'il est possible de récupérer le décalage d'une date par rapport à l'UTC (via getTimezoneOffset), il n'est pas possible de modifier le timeZoneOffset directement.

Autrement dit, il n'existe pas nativement en javascript de "date locale" ou "date UTC" ; il existe juste la capacité de créer des dates en spécifiant des arguments dans la zone horaire du poste client ou en UTC. Une fois la date créée, il n'existe plus de moyen de savoir si elle a été initialement créée dans la zone horaire du poste client ou en UTC.

Il existe cependant plusieurs méthodes pour construire des dates javascript en utilisant des informations renseignées en UTC.

Méthode 1 : compter en millisecondes 🙂

On passe au constructeur Date un nombre de millisecondes (depuis le 1er Janvier 1970), ce qui permet de préciser un temps UTC (en millisecondes) qui sera convertie en une instance de Date en heure locale.

Par exemple,

new Date(1268211600000) Wed Mar 10 2010 10:00:00 GMT+0100

Méthode 2 : utiliser Date.UTC

La manipulation de millisecondes (par exemple la sauvegarde en base de données du nombre de millisecondes) n’est pas très pratique. La méthode Date.UTC permet de calculer le nombre de millisecondes pour une date en UTC en précisant les mêmes arguments que le constructeur Date, c'est-à-dire de 2 à 7 arguments, respectivement year, month, date, hour, minutes, seconds, milliseconds

Par exemple

Date.UTC(2010,02,10,9,0) 1268211600000

Pour créer (en temps local) une Date en précisant l’heure UTC, on peut enchaîner les deux appels :

new Date(Date.UTC(2010,02,10,9,0))Wed Mar 10 2010 10:00:00 GMT+0100

Méthode 3 : utiliser les accesseurs de type setUTCXXX

Une troisième solution est de créer une instance de Date et d’enchaîner les accesseurs du type setUTCXXX (setUTCDate, setUTCFullYear,setUTCHours, setUTCMinutes, setUTCMonth, setUTCSeconds)

Méthode 4 : convertir une chaîne au format ISO 8601 UTC

La méthode javascript Date.parse permet de transformer une chaîne en nombre de ms depuis 1970 (temps UTC).

Par exemple,

Date.parse("Jul 8, 2005") 1120773600000

malheureusement, cette méthode ne permet pas de parser des chaînes au format ISO 8601, qui sont pourtant le standard internationnal reconnu d'échange de dates (il est intéressant d'utiliser ce standard parce qu'il permet l'échange avec les autres langages informatiques).

La représentation complète des Dates en ISO 8601

ISO 8601 définit de nombreux formats différents pour échanger les heures, les dates, et même les périodes de temps et les événements récurrents.

Parmi ces formats, ceux qui nous intéressent sont ceux qui permettent de convertir sous forme de chaîne l'intégralité d'une date javascript. Or les dates javascript on une précision de l'ordre de la milliseconde. Le format ISO 8601 à utiliser doit donc pouvoir stocker "la date complète calendaire et l’heure avec des fractions de seconde"

ISO 8601 définit les formats correspondants suivants :

le format étendu : "aaaa-mm-qqThh:mi:ss,nzzzzzz" (ex : 1997-07-16T19:20:30,4+01:00)

et le format de base (qui est le même mais sans les tirets) : "aaaammqqThhmissnzzzzz" (ex : 19970716T1920304+0100)

et en UTC :

le format étendu : "aaaa-mm-qqThh:mi:ss,nZ" (ex : 1997-07-16T19:20:30,4Z)

et le format de base (qui est le même mais sans les tirets) : "aaaammqqThhmissnZ" (ex : 19970716T1920304Z)

avec

  • aaaa : l'année sur 4 chiffres, mm : le mois sur 2 chiffres, qq : le quantième (index du jour) sur 2 chiffres,
  • T est le séparateur date/heure
  • ss : les secondes précisées par 2 chiffres,
  • n : les fractions de secondes - sur un nombre de chiffres quelconques - indiquées après la virgule
  • zzzzzz le fuseau horaire dans le format étendu (+hh:mi ou -hh:mi)
  • zzzzz le fuseau horaire dans le format de base (+hhmi ou hhmi)

En ISO 8601, le T indique le début du codage de l'heure.

Un Z final indique le méridien Zéro ce qui indique qu'il s'agit d'une heure UTC.

En heure locale, on indique le fuseau horaire : sous un format de base (+hh, +hhmm ou -hhmm) ou un format étendu (+hh:mm ou -hh:mm), par exemple :

aaaammqqThh:mizzzzzz (ex : 1997−07−16T19:20+01:00)

Noter qu'en ISO 8601, il est possible d'indiquer des fractions de secondes (en utilisant officiellement la virgule mais les anglo-saxons continuent à favoriser l'utilisation du point) et que la précision en terme de fractions de secondes n'est pas limitée.

par exemple :

1974−03−10T19:20:30,561Z correspond au 10 Mars 1974 à 19h20, 30 secondes et 561 ms (tout cela en UTC)

En utilisant une chaîne ISO 8601 et en précisant les millisecondes, on peut donc conserver la même précision qu'en javascript.

Générer une chaîne ISO 8601 UTC en javascript

Il est facile de génerer une chaîne UTC.  La méthode toISOString supportée par Firefox joue ce rôle (noter par contre, que la méthode javscript  toUTCString n’utilise pas ce format, mais un format tel que Tue, 04 May 2010 08:07:10 GMT).

var d0=new Date(Date.UTC("1974",02,10,19,20,30,461))
console.log(d0.toISOString())  
1974-03-10T18:20:30.461Z

On peut aussi utiliser la méthode toJSON présentée ci-dessous. 

JSON supporte nativement la génération de chaînes ISO 8601 au format UTC, via la méthode toJSON et conserve la précision de la milliseconde.

var d1=new Date("1974",02,10,19,20,30,461) ; // Date locale
var utcTime=d1.getTime(); // traduite en UTC (nombre de millisecondes)
console.log("utcTime : "+utcTime)
132171630461

var d2=new Date(utcTime+561); // on ajoute 561 ms

console.log(d2);
→ Sun Mar 10  1974 19:20:30 GMT+0100

console.log(d2.toJSON());

→ 1974-03-10T18:20:30.561Z

On trouve un exemple de code permettant de générer une chaîne ISO 8601 UTC associé ici :

Parser une chaîne ISO 8601 UTC en javascript

Noter que l'implantation de référence JSON (http://www.json.org/json2.js) comporte un exemple d'analyse de chaîne ISO 8601.

var myData = JSON.parse(text, function (key, value) {
 var a;
 if (typeof value === 'string') {
 a = /^(d{4})-(d{2})-(d{2})T(d{2}):(d{2}):(d{2}(?:.d*)?)Z$/.exec(value);
 if (a) {
 return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
 +a[5], +a[6]));
 }
 }
 return value;
}

Mais attention, ce code de référence ne respecte pas la précision des millisecondes (il ne comporte que 6 paramètres, alors que c'est le 7ième qui  précise les secondes) ; ainsi dans le code de référence, JSONParseDate("1974-03-10T18:20:30.561Z").getTime() rend 132171630000 (et pas 132171630461)

Il faut donc corriger ce code pour tenir compte des millisecondes, ce qui donne par exemple  :

/**
 * Parse une chaîne conforme aux masques tels que aaaa-mm-qqThh:mi:ss,nzzzzz ou aaaa-mm-qqThh:mi:ss,nZ
 * Rends une Date ou NaN si cette chaîne n'est pas conforme aux masques
 * @param isoDate <String> une chaîne représentant une date
 *@return <Date | NaN>
 */
function ParseDate(isoDate) {
 var a;

 a = /(d{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])T([0-1][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(?:.(d+))*(?:(?:([+|-])(0[0-9]|1[0-2]):(00|15|30|45)|Z))$/.exec(isoDate);
 if (a) {
 var year=a[1];
 var month=a[2];
 var day=a[3];
 var hour=a[4];
 var mm=a[5];
 var ss=a[6];
 var zz=a[7]; // fractions de secondes (a priori pas limitées en précisions) ou undefined
 var tmz_sign=a[8]; // "+", "-" ou undefined (si Z comme UTC)
 var tmz_hh=+a[9]; // l'heure de décalage par rapport à l'UTC
 var tmz_mm=+a[10]; // les minutes de décalage par rapport à l'UTC (volontairement limitées au quart d'heure (00|15|30|45)

 if(mm==undefined) { mm=0 };
 if(ss==undefined) { ss=0 };
 if(zz==undefined) { zz=0 };

 if(tmz_sign==undefined) {
 // On est dans le cas UTC
 // Création directe de la date en UTC
 var utcTime=new Date(Date.UTC(+year, +month - 1, +day, +hour, +mm, +zz));
 return utcTime
 } else {
 // On est dans le cas GMT
 // Création de la date en UTC (sans tenir compte du décalage horaire)
 var utcDateWithoutTZ=new Date(Date.UTC(+year, +month - 1, +day, +hour, +mm, +ss,+zz));
 // Conversion de cette date en UTC en millisecondes
 var ms=utcDateWithoutTZ.getTime();
 // Conversion du décalage horaire en ms
 var tmz_ms=((+tmz_hh*60) + tmz_mm)*60*1000;
 // Calcul du signe du décalage horaire vis à vis de l'UTC,
 // ajout au nombre de ms de la date UTC
 // Création de la date en lui spécifiant le nombre de ms
 var gmtTime;
 if(tmz_sign=="+")
 { gmtTime=new Date(ms-tmz_ms) }
 else
 { gmtTime=new Date(ms+tmz_ms) };
 return(gmtTime);
 }
 } else {
 return(NaN)
 }
}

Un code pour parser une chaîne ISO 8601 UTC est disponible en  http://delete.me.uk/2005/03/iso8601.html

Conclusion

En utilisant le format complet ISO 8601, il est possible d'avoir la même précision sous forme de chaîne qu'une date javascript et on peut facilement échanger ces dates avec d'autres systèmes, par exemple avec un serveur.

Néanmoins, il est intéressant de noter que JSON Schema (dont la version de Mars 2010 est disponible ) différencie plusieurs formes de types liés à la gestion du temps :

  • date-time - This should be a date in ISO 8601 format of YYYY-MM-DDThh:mm:ssZ in UTC time.  This is the recommended form of date/timestamp.
  • date - This should be a date in the format of YYYY-MM-DD.  It is recommended that you use the "date-time" format instead of "date" unless you need to transfer only the date part.
  • time - This should be a time in the format of hh:mm:ss.  It is recommended that you use the "date-time" format instead of "time" unless you need to transfer only the time part.
  • utc-millisec - This should be the difference, measured in milliseconds, between the specified time and midnight, January 1, 1970 UTC.  The value should be a number (integer or float).

Si on oublie les formats date et time qui ne supportent que des types partiels pas recommandés, JSON Schema différencie utc-millisec (qui correspond au nombres de millisecondes depuis le 1er Janvier 2010 et donc bien à l'API javascript getTime) et date-time qui ne comporte pas les fractions de secondes (mais qui est exprimé sous forme de chaîne ISO 8601). Autrement dit, dans la version actuelle de la norme, si on veut échanger avec la précision de la milliseconde, il faut utiliser le type utc-millisec. Si on en reste à la précision de la seconde, on peut utiliser une chaîne UTC et le type date-time.

Un commentaire

  1. Awesome post. Appreciate your hardwork

    I have an issue with date in my project. From Back end java code, i am sending date to front end,

    In front end i am comparing current client browser time to back end date,

    For this i am having lot of issues.

    so i tried to do both back end and front end dates as UTC date

    My Back end date is ‘Tue May 21 2013 07:01:41’

    and Front end date in js is ‘Tue, 21 May 2013 06:57:39 GMT’ using now.toUTCString() function.

    How can i remove GMT in front end using java script?

    Thanks
    Grep Command Examples in Linux/Unix

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.