Les éléments <input> dont l'attribut type vaut "datetime-local" permettent de créer des champs destinés à saisir simplement une date (définie par une année, un mois, et un jour) et une heure (définie par une heure et une minute). Il n'y a pas de secondes dans ce contrôle.

L'interface utilisateur du contrôle varie selon les navigateurs. La prise en charge de cette fonctionnalité est hétérogène : Chrome/Opera et Edge l'implémentent pour les navigateurs de bureau et la plupart des navigateurs mobiles l'implémentent. Pour les navigateurs qui n'implémentent pas cette fonctionnalité, le contrôle utilisé est celui de <input type="text">.

<input id="datetime" type="datetime-local">

Voici, ci-après, un aperçu du contrôle obtenu dans un navigateur fonctionnel. En cliquant sur la flèche vers le bas, cela fait apparaître un sélecteur de date et il faut ensuite saisir l'heure manuellement. Pour Chrome / Opera :

Pour Edge, le contrôle associé à datetime-local se décompose en deux sélecteurs : un pour la date et un pour l'heure qui apparaissent quand on clique sur les éléments respectifs du champ :

Valeur Une chaîne de caractères (DOMString) qui représente une date et une heure (selon le fuseau horaire local) ou bien une chaîne de caractères vide.
Évènements change et input.
Attributs pris en charge autocomplete, list, readonly et step.
Attributs IDL list, value, valueAsNumber.
Méthodes select(), stepDown(), stepUp().

Valeur

Une chaîne de caractères (DOMString) qui représente la valeur de la date saisie dans le contrôle. Il est possible d'indiquer une date par défaut grâce à l'attribut value :

<label for="party">Veuillez saisir une date et une heure pour la fête :</label>
<input id="party" type="datetime-local" name="partydate" value="2017-06-01T08:30">

On notera ici que le format de la date affichée n'est pas celui utilisé pour écrire la valeur de l'attribut value. Le format d'affichage de la date sera choisi en fonction de la locale du système d'exploitation de l'utilisateur. En revanche, l'attribut value sera toujours formaté de la façon suivante : yyyy-MM-ddThh:mm. Lorsque la valeur est envoyée au serveur, elle aura donc ce format : partydate=2017-06-01T08:30.

Note : Attention si les données sont envoyées avec la méthode HTTP GET, les deux points (:) devront être échappés pour être intégrés dans les paramètres de l'URL. Avec l'exemple précédent, cela signifie qu'on enverra partydate=2017-06-01T08%3A30. Si on souhaite échapper une chaîne de caractères de la même façon en JavaScript, on pourra utiliser encodeURI().

En JavaScript, Il est également possible de définir la valeur de la date utilisée dans le contrôle via la propriété HTMLInputElement.value. Par exemple :

var dateControl = document.querySelector('input[type="datetime-local"]');
date.value = '2017-06-01T08:30';

Utiliser les contrôles datetime-local

Ces contrôles sont pratiques : ils permettent d'utiliser une interface simple pour sélectionner une date et une heure et en plus, ils normalisent la valeur saisie avant de l'envoyer au sereveur, quelle que soit la locale de l'utilisateur. Toutefois, il existe actuellement des problèmes liés à la prise en charge partielle de <input type="datetime-local"> dans les différents navigateurs.

Dans les exemples suivants, nous verrons certains cas d'utilisation plus complexes puis nous traiterons de l'adaptation nécessaire en fonction de la prise en charge des navigateurs.

Utilisation simple de datetime-local

Dans sa forme la plus simple, <input type="datetime-local"> peut s'utilisere avec un élément <input> et un élément <label> comme ceci :

<form>
    <label for="party">Veuillez choisir une date et une heure pour la fête :</label>
    <input id="party" type="datetime-local" name="partydate">
</form>

Paramétrer des dates et heures minimales/maximales

Les attributs min et max permettent de restreindre la fenêtre de dates qu'il est possible de choisir. Dans l'exemple qui suit, on indique une date/heure minimale au 2017-06-01T08:30 et une date maximale au 2017-06-30T16:30:

  <form>
    <label for="party">Veuillez choisir une date et une heure pour la fête :</label>
    <input id="party" type="datetime-local" name="partydate" min="2017-06-01T08:30" max="2017-06-30T16:30">
  </form>

Par conséquent :

  • Seuls les jours de juin 2017 peuvent être sélectionnés et seules les heures entre 08h30 et 16h30 pourront être sélectionnées..
  • Selon le navigateur utilisé, il est possible ou non de sélectionner des heures invalides (cf. Validation).

Note : L'attribut step devrait pouvoir être utilisé afin de faire varier l'incrément, en jours, pour sélectionner la date (par exemple afin de ne pouvoir sélectionner que les samedi). En revanche, à l'heure où nous rédigeons cet article, aucune implémentation ne semble proposer cette fonctionnalité.

Contrôler la taille du champ

<input type="datetime-local"> ne prend pas en charge des attributs tels que size. Il est nécessaire d'utiliser CSS pour les problèmes relatifs au dimensionnement.

Paramétrer le fuseau horaire

Les champs datetime-local ne permettent pas d'indiquer le fuseau horaire de la date/heure utilisée. Cette caractéristique était disponible pour les champs  de type datetime qui est désormais obsolète (retiré de la spécification). Ce type de champ a été retiré en raison d'un manque d'implémentation de la part des navigateurs et des problèmes relatifs à l'ergonomie. Il est plus simple d'avoir un contrôle séparé pour indiquer le fuseau horaire.

Ainsi, si vous créez un système où l'utilisateur est déjà connecté et que le fuseau horaire est déjà connu, celui-ci peut être fourni via un champ de type hidden. Par exemple :

<input type="hidden" id="timezone" name="timezone" value="-08:00">

Sinon, on peut proposer la sélection d'un fuseau horaire grâce à un élément <select> :

<select name="timezone_offset" id="timezone-offset" class="span5">
    <option value="-12:00">(GMT -12:00) Eniwetok, Kwajalein</option>
    <option value="-11:00">(GMT -11:00) Midway Island, Samoa</option>
    <option value="-10:00">(GMT -10:00) Hawaii</option>
    <option value="-09:50">(GMT -9:30) Taiohae</option>
    <option value="-09:00">(GMT -9:00) Alaska</option>
    <option value="-08:00">(GMT -8:00) Pacific Time (US &amp; Canada)</option>

  ...
 
</select>

Dans ces deux situations, le fuseau horaire et la date sont transmis au serveur séparément (c'est côté serveur que le choix de la représentation pour le stockage est effectué).

Note : Le fragment de code précédent est tiré de Tous les fuseaux horaires du monde dans un élément <select>.

Validation

Par défaut, <input type="datetime-local"> n'applique pas de validation aux valeurs saisies. C'est l'interface utilisateur du contrôle qui ne permet pas de saisir autre chose qu'une date et une heure (ce qui est utile) mais il est toujours possible de ne saisir aucune valeur ou de saisir une valeur invalide (le 32 avril par exemple).

Les attributs min et max permettent de restreindre les dates disponibles et required rend la date obligatoire. Dans ces cas, les navigateurs afficheront un message d'erreur si on essaie d'envoyer une date en dehors de l'intervalle ou une date vide.

Prenons un exemple avec des dates mini/maxi et le champ obligatoire :

<form>
    <div>
        <label for="party">Veuillez choisir une date et une heure pour la fête (obligatoire, entre le 1er juin, 8h30 et le 30 juin, 16h30) :</label>
        <input id="party" type="datetime-local" name="partydate" min="2017-06-01T08:30" max="2017-06-30T16:30" required>
        <span class="validity"></span>
    </div>
    <div>
        <input type="submit" value="Réserver !">
    </div>
</form>

Si vous essayez d'envoyer le formulaire avec une date incomplète ou en dehors de l'intervalle indiqué, le navigateur affichera une erreur. Voici le résultat :

Voici une capture d'écran illustrant un message d'erreur dans un navigateur qui prend en charge cette fonctionnalité :

Vous trouverez ensuite la feuille de style CSS utilisée pour l'exemple. On utilise les pseudo-classes :valid et :invalid afin de mettre en forme le contrôle selon que sa valeur est valide ou non. Les icônes indicatives sont placées dans un élément <span> séparé car, sous Chrome, le contenu généré automatiquement est placé à l'intériur du contrôle et ne peut pas être affiché/mis en forme efficacement.

div {
    margin-bottom: 10px;
    display: flex;
    align-items: center;
}

label {
  display: inline-block;
  width: 300px;
}

input:invalid+span:after {
    content: '✖';
    padding-left: 5px;
}

input:valid+span:after {
    content: '✓';
    padding-left: 5px;
}

Important : La validation des données du formulaire HTML par le navigateur ne doit pas remplacer la validation des données reçues sur le serveur ! En effet, il est tout à fait possible pour quelqu'un de modifier le document HTML afin d'outrepasser ces contraintes (voire d'envoyer directement des données au serveur sans passer par le formulaire HTML). Si les mécanismes, côté serveur, échouent à valider les données, cela pourrait avoir des conséquences néfastes sur le stockage ou la sécurité de l'application.

Gérer la prise en charge des navigateurs

Comme indiqué ci-avant, le principal problème qu'on rencontre avec ces contrôles est la prise en charge hétérogène des différents navigateurs : seuls Opera et Chrome implémentent cette fonctionnalité parmi les navigateurs de bureau et la plupart des navigateurs mobiles la prennent en charge. Voici, par exemple, l'interface utilisateur du sélecteur datetime-local de Firefox pour Android :

Les navigateurs qui n'implémentent pas cette fonctionnalité afficheront un contrôle de saisie textuelle. Toutefois, cela entraîne des problème de cohérence d'interface graphique d'une part et de représentation des données d'autre part.

C'est ce second problème qui est le plus important. Comme nous l'avons mentionné avant, la valeur d'un contrôle datetime-local est toujours normalisée sous la forme yyyy-mm-ddThh:mm. En revanche, avec un champ texte, le navigateur n'utilise pas de formatage particulier et il existe différentes façon d'écrire des dates et heures selon les langues et les régions. On peut par exemple avoir les formats suivants :

  • ddmmyyyy
  • dd/mm/yyyy
  • mm/dd/yyyy
  • dd-mm-yyyy
  • mm-dd-yyyy
  • mm-dd-yyyy hh:mm (heure exprimée sur 12 heures)
  • mm-dd-yyyy hh:mm (heure exprimée sur 24 heures)
  • etc.

Une façon de contourner ce problème est de placer un attribut pattern dans l'élméent <input type="datetime-local">. Bien que cet élément n'utilise pas cet attribut, s'il est converti en <input type="text"> par le navigateur, le motif sera alors utilisé. Vous pouvez par exemple essayer le code suivant dans un navigateur qui ne prend pas en charge <input type="datetime-local"> :

<form>
  <div>
     <label for="party">Veuillez choisir une date et une heure pour la fête (obligatoire, entre le 1er juin, 8h30 et le 30 juin, 16h30) :</label>
    <input id="party" type="datetime-local" name="partydate"
           min="2017-06-01T08:30" max="2017-06-30T16:30"
           pattern="[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}" required>
    <span class="validity"></span>
  </div>
  <div>
    <input type="submit" value="Réserver !">
  </div>
  <input type="hidden" id="timezone" name="timezone" value="-08:00">
</form>

Si vous essayer de soumettre ce formulaire, vous pourrez voir que le navigateur affiche un message d'erreur et met en avant le champ invalide si la valeur saisie ne respecte pas la forme nnnn-nn-nnTnn:nn avec n qui est un chiffre entre 0 et 9. Bien entendu, cela n'empêche pas de saisir des dates/heures invalides ou mal formatées.

De plus, comment l'utilisateur doit-il comprendre la règle de format qui lui est imposée pour saisir une date et une heure ?

Bref, il y a toujours un problème.

Actuellement, la meilleure façon de gérer les dates/heures d'une façon homogène pour les différents navigateurs est d'utiliser différents contrôles (via des éléments <select>) pour sélectionner l'année, le jour, le mois, la date et l'heure. Il existe également des bibliothèques JavaScript telles que le sélecteur de date jQuery et le sélecteur d'heure jQuery.

Exemples

Dans cet exemple, on crée deux ensembles d'éléments pour sélectionner une date et une heure : un sélecteur natif <input type="datetime-local"> d'une part et un ensemble de cinq éléments <select> d'autre part pour les navigateurs qui ne prennent pas en charge le contrôle natif.

Voici le fragment de code HTML utilisé :

<form>
  <div class="nativeDateTimePicker">
    <label for="party">Veuillez sélectionner une date et une heure pour la fête :</label>
    <input type="datetime-local" id="party" name="bday">
    <span class="validity"></span>
  </div>
  <p class="fallbackLabel">Veuillez sélectionner une date et une heure pour la fête :</p>
  <div class="fallbackDateTimePicker">
    <div>
      <span>
        <label for="day">Jour :</label>
        <select id="day" name="day">
        </select>
      </span>
      <span>
        <label for="month">Mois :</label>
        <select id="month" name="month">
          <option selected>Janvier</option>
          <option>Février</option>
          <option>Mars</option>
          <option>Avril</option>
          <option>Mai</option>
          <option>Juin</option>
          <option>Juillet</option>
          <option>Août</option>
          <option>Septembre</option>
          <option>Octobre</option>
          <option>Novembre</option>
          <option>Décembre</option>
        </select>
      </span>
      <span>
        <label for="year">Année :</label>
        <select id="year" name="year">
        </select>
      </span>
    </div>
    <div>
      <span>
        <label for="hour">Heure :</label>
        <select id="hour" name="hour">
        </select>
      </span>
      <span>
        <label for="minute">Minute :</label>
        <select id="minute" name="minute">
        </select>
      </span>
    </div>
  </div>
</form>

Les mois disponibles sont codés de façon statique (ce sont toujours les mêmes). En revanche, les valeurs pour les jours et les années sont générées dynamiquement selon le mois et l'année courante (voir les commentaires du script ci-après). Les heures et les minutes sont également générées dynamiquement.

Une partie intéressante du code est celle où on détecte la prise en charge de la fonctionnalité. Pour cela, dans le script, on crée un nouvel élément <input> auquel on attribut le type datetime-local puis on vérifie son type juste après. Pour les navigateurs qui ne prennent pas en charge ce type de contrôle, le type aura changé et sera text. Si c'est le cas, on masque le contrôle natif et on utilise l'interface utilisateur alternative (composée avec les éléments (<select>)).

// On définit les différentes variables 
var nativePicker = document.querySelector('.nativeDateTimePicker');
var fallbackPicker = document.querySelector('.fallbackDateTimePicker');
var fallbackLabel = document.querySelector('.fallbackLabel');

var yearSelect = document.querySelector('#year');
var monthSelect = document.querySelector('#month');
var daySelect = document.querySelector('#day');
var hourSelect = document.querySelector('#hour');
var minuteSelect = document.querySelector('#minute');

// Initialement, on masque le sélecteur non-natif
fallbackPicker.style.display = 'none';
fallbackLabel.style.display = 'none';

// On teste si l'élément <input type="date">
// se transforme en <input type="text"> 
var test = document.createElement('input');
test.type = 'date';
// Si c'est le cas, cela signifie que l'élément
// n'est pas pris en charge et 
if(test.type === 'text') {
  // On masque le sélecteur natif et on affiche
  // le sélecteur avec les <select> 
  nativePicker.style.display = 'none';
  fallbackPicker.style.display = 'block';
  fallbackLabel.style.display = 'block';

  // On affiche les jours, années, heures
  // et minutes de façon dynamique 
  populateDays(monthSelect.value);
  populateYears();
  populateHours();
  populateMinutes();
}

function populateDays(month) {
  // On supprime les éléments <option> pour l'élément
  // <select> des jours afin de pouvoir ajouter les prochains
  while(daySelect.firstChild){
    daySelect.removeChild(daySelect.firstChild);
  }

  // On crée une variable afin de contenir le nombre
  // de jours à afficher
  var dayNum;

  // 31 ou 30 jours ?
  if(month === 'Janvier' | month === 'Mars' | month === 'Mai' | month === 'Juillet' | month === 'Août' | month === 'Octobre' | month === 'Décembre') {
    dayNum = 31;
  } else if(month === 'Avril' | month === 'Juin' | month === 'Septembre' | month === 'Novembre') {
    dayNum = 30;
  } else {
  // Si le mois est février, on calcule si l'année est bissextile
    var year = yearSelect.value;
    (year - 2016) % 4 === 0 ? dayNum = 29 : dayNum = 28;
  }

  // on ajoute le bon nombre de jours dans autant
  // d'éléments <option> pour l'élément <select>
  // pour la journée
  for(i = 1; i <= dayNum; i++) {
    var option = document.createElement('option');
    option.textContent = i;
    daySelect.appendChild(option);
  }

  // Si le jour précédent a déjà été défini on utilise
  // la valeur de ce jour pour daySelect afin d'éviter de
  // réinitialiser le jour lorsqu'on change l'année
  if(previousDay) {
    daySelect.value = previousDay;

    // Si le jour précédent correspond au dernier jour d'un mois
    // et que le mois sélectionné possède moins de jours (par 
    // exemple en février)
    if(daySelect.value === "") {
      daySelect.value = previousDay - 1;
    }

    if(daySelect.value === "") {
      daySelect.value = previousDay - 2;
    }

    if(daySelect.value === "") {
      daySelect.value = previousDay - 3;
    }
  }
}

function populateYears() {
  // On obtient l'année courante
  var date = new Date();
  var year = date.getFullYear();

  // On affiche l'année courante et les 100 années
  // précédentes pour l'élément <select> destiné à
  // stocker l'année
  for(var i = 0; i <= 100; i++) {
    var option = document.createElement('option');
    option.textContent = year-i;
    yearSelect.appendChild(option);
  }
}

function populateHours() {
  // on crée 24 valeurs pour l'élément <select> 
  // associé aux heures
  for(var i = 0; i <= 23; i++) {
    var option = document.createElement('option');
    option.textContent = (i < 10) ? ("0" + i) : i;
    hourSelect.appendChild(option);
  }
}

function populateMinutes() {
  // On crée 60 valeurs pour l'élément <select>
  // associé aux minutes
  for(var i = 0; i <= 59; i++) {
    var option = document.createElement('option');
    option.textContent = (i < 10) ? ("0" + i) : i;
    minuteSelect.appendChild(option);
  }
}

// Lorsque la valeur du mois ou de l'année est modifiée
// on relance populateDays() 
yearSelect.onchange = function() {
  populateDays(monthSelect.value);
}

monthSelect.onchange = function() {
  populateDays(monthSelect.value);
}

// On conserve le jour sélectionné 
var previousDay;

// On met à jour la journée utilisé précédemment
// (voir la fin de populateDays() pour voir où
// est utilisée cette valeur) 
daySelect.onchange = function() {
  previousDay = daySelect.value;
}

Spécifications

Spécification État Commentaires
HTML Living Standard
La définition de '<input type="datetime-local">' dans cette spécification.
Standard évolutif  
HTML5
La définition de '<input type="datetime-local">' dans cette spécification.
Recommendation  

Compatibilité des navigateurs

Fonctionnalité Chrome Edge Firefox (Gecko) Internet Explorer Opera Safari
Support simple 20 12 Pas de support[1] Pas de support 10.62 Pas de support[2]
Fonctionnalité Android Chrome pour Android Edge Firefox Mobile (Gecko) IE Mobile Opera Mobile Safari Mobile
Support simple (Oui) (Oui) (Oui) (Oui) ? (Oui) (Oui)

[1] Cette fonctionnalité n'est pas encore implémentée. Voir bug 888320 et cette page du wiki Mozilla.

[2] Ce type d'élément est bien reconnu mais il n'y a pas d'interface utilisateur spécifique associée.

Voir aussi

Étiquettes et contributeurs liés au document

 Contributeurs à cette page : SphinxKnight
 Dernière mise à jour par : SphinxKnight,