Choisir une heure
Trois façons, et timeLayout choisit entre elles à l'intérieur d'un champ. Isolées, ce sont createTimeInput, createTimeSelect et createTimeSlots. Elles existent parce que demander « une heure quelconque » et demander « une de ces heures-là » sont deux questions différentes.
Chaque option des trois a un exemple qui tourne dessous.
Un champ compact — createTimeInput
En Angular c'est <tz-time-input>, un ControlValueAccessor comme les autres — il entre donc directement dans un formulaire réactif :
<tz-time-input formControlName="startTime" [stepMinutes]="15" [date]="jour()" [timeZone]="zone" />Le contrôle porte un PlainTime : un cadran, sans jour ni fuseau. Le jour et le fuseau sont ce qui en fait un moment — pressez les flèches des heures le matin où une heure est sautée et regardez 02 enjambée : vers le bas depuis 03:00 on tombe sur 01:00, parce que 02:00 n'arrive pas le 29 mars à Paris. Les flèches des minutes restent dans l'heure, exprès — c'est le paragraphe suivant.
timeLayout: 'input'. Les flèches, la molette, les touches haut et bas, et la saisie. Sur douze heures là où la locale écrit les heures comme ça.
Chaque champ boucle sur lui-même : faire passer les minutes au-delà de l'heure déplacerait un rendez-vous d'une heure que personne n'a demandée.
stepMinutes
Ce que déplace une pression sur une flèche. 5 par défaut.
createTimeInput(element, { stepMinutes: 15 });value
L'heure sur laquelle il s'ouvre, en PlainTime — un cadran, sans jour ni fuseau attachés. Le jour et le fuseau sont ce qui en fait un moment, et ce sont date et timeZone plus bas.
createTimeInput(element, { value: Temporal.PlainTime.from('09:30') });minTime et maxTime
La fenêtre dans laquelle restent les flèches et la saisie. Les deux acceptent un PlainTime ou la chaîne correspondante.
createTimeInput(element, { minTime: '08:00', maxTime: '19:30' });variant
'boxed' par défaut — un champ encadré. 'bare' enlève le cadre et empile les flèches au-dessus et au-dessous des chiffres, ce que font les champs à l'intérieur d'un panneau.
locale
Décide douze ou vingt-quatre heures, parce que c'est ce que la locale écrit. Rien d'autre ne change dans ce contrôle.
createTimeInput(element, { locale: 'en-US' });date et timeZone
Avec les deux, le contrôle sait sur quel jour il se tient, et les deux matins de l'année cessent d'être ordinaires. Sans eux, c'est un cadran, rien de plus.
createTimeInput(element, { date: '2026-10-25', timeZone: 'Europe/Paris' });Descendez depuis 03:00 le 29 mars et la flèche se pose sur 01:59 : l'heure intermédiaire n'existe pas à Paris ce matin-là.
disabled
createTimeInput(element, { disabled: true });Deux menus — createTimeSelect
En Angular c'est <tz-time-select>, avec une sortie de plus que la version DOM rend en second argument :
<tz-time-select
formControlName="startTime"
[date]="jour()"
[timeZone]="zone"
[minuteStep]="15"
(offsetChange)="form.get('startOffset')?.setValue($event)" />offsetChange dit quelle lecture d'une heure répétée a été retenue — +02:00 ou +01:00. Sur une heure ordinaire elle ne veut rien dire ; le 25 octobre, c'est la différence entre deux moments distants d'une heure, donc un formulaire qui enregistre des instants doit la porter.
Choisissez 02 deux fois ci-dessous — les deux entrées sont le même cadran et deux moments différents, et la ligne en dessous est ce qu'offsetChange remet à votre formulaire :
timeLayout: 'select'. Un menu d'heures et un menu de minutes. Ce sont de vrais éléments <select> : le clavier fonctionne, rien ne peut les rogner, et un téléphone ouvre son propre sélecteur.
minuteStep et hourStep
Ce que propose chaque menu. Une minute par défaut, ce qui fait un long menu — les demi-heures sont ce que veulent la plupart des écrans. La valeur déjà retenue est toujours dans la liste, quel que soit le pas, sinon une heure fournie disparaîtrait du contrôle censé l'afficher.
createTimeSelect(element, { minuteStep: 30 });createTimeSelect(element, { hourStep: 2, minuteStep: 15 });date et timeZone
Avec un jour et un fuseau — que les champs transmettent — les menus montrent ce jour tel qu'il est vraiment. Ci-dessous, le 25 octobre 2026 à Paris : 02 apparaît deux fois, et en choisir une répond à la question d'emblée.
createTimeSelect(element, { date: '2026-10-25', timeZone: 'Europe/Paris' });Le 29 mars, l'heure manque tout simplement — il n'y a pas de 02 dans le menu :
readingStyle
Comment l'heure répétée est montrée. 'named' — la valeur par défaut — écrit l'heure une fois et nomme la lecture en vigueur sous le contrôle. 'marked' met les deux dans le menu, 02 et 02*.
offset
Quelle lecture d'une heure ambiguë est retenue, sous la forme +02:00 ou +01:00. C'est le second argument que rend onChange, et la façon d'en fournir une. Sur une heure non ambiguë, il ne veut rien dire et il est ignoré.
createTimeSelect(element, {
date: '2026-10-25', timeZone: 'Europe/Paris',
value: Temporal.PlainTime.from('02:30'), offset: '+01:00', // hiver
});minTime et maxTime
createTimeSelect(element, { minTime: '09:00', maxTime: '17:00' });Les créneaux du jour — createTimeSlots
timeLayout: 'list'. Toutes les heures réservables du jour, ce que veut un écran de réservation. La valeur est un Instant : un créneau est un moment, pas un cadran.
stepMinutes
L'écart entre les créneaux. Trente minutes par défaut.
createTimeSlots(element, { date: '2026-09-22', timeZone: 'Europe/Paris', stepMinutes: 60 });minTime et maxTime
Les heures d'ouverture. Sans elles, la liste couvre toute la journée.
createTimeSlots(element, {
date: '2026-09-22', timeZone: 'Europe/Paris',
stepMinutes: 30, minTime: '09:00', maxTime: '12:00',
});isDisabled
Ce qui est déjà pris. Appelée pour chaque créneau : gardez-la du côté de la consultation.
Un créneau porte time (le cadran), exists, ambiguous, offsets et instants — les moments auxquels ce cadran correspond, et c'est cela qu'un système de réservation stocke.
createTimeSlots(element, {
date: '2026-09-22', timeZone: 'Europe/Paris',
isDisabled: (slot) => slot.instants.some((at) => pris.has(at.toString())),
});Les deux matins, dans une liste
Le matin où les pendules reculent, l'heure est proposée deux fois, avec les décalages en dessous, parce que ce sont deux vrais créneaux que quelqu'un pourrait réserver :
Le matin où l'une est sautée, elle est barrée et ne peut pas être choisie :
skipNonExistent
Retire l'heure impossible de la liste au lieu de la barrer. Le défaut l'affiche, parce qu'un lecteur qui attendait 02:30 et ne la trouve pas ira chercher le bug dans votre système de réservation.
emptyLabel, missingLabel et ariaLabel
Ce que dit la liste quand la journée n'offre rien du tout, ce qu'elle écrit sous une heure barrée, et comment un lecteur d'écran nomme la liste elle-même.
createTimeSlots(element, {
date: '2026-12-25', timeZone: 'Europe/Paris',
minTime: '19:00', maxTime: '18:00', // une fenêtre qui ne contient rien
emptyLabel: 'Fermé le 25 décembre',
ariaLabel: 'Rendez-vous disponibles',
});Il lui faut un jour : sans date du tout, la liste ne dessine rien plutôt que d'affirmer qu'une journée n'offre rien, ce qui serait une autre affirmation.
missingLabel remplace le mot écrit dans une heure barrée — sautée par défaut — et il doit rester court, parce qu'il tient dans un bouton de la largeur d'une heure :
L'heure qui arrive deux fois
Quelle que soit la façon dont l'heure est demandée, la valeur est un Instant — un moment, pas un cadran. Quand le cadran est ambigu :
- les menus proposent les deux lectures, nommées été et hiver ;
- le champ compact pose la question une fois, avec les deux mêmes mots, et le texte du champ dit ensuite laquelle il retient :
25/10/2026 02:30 (hiver); - la liste montre les deux, avec leurs décalages en dessous.
L'heure qui n'existe pas
La taper amène au premier moment qui existe et le dit. Les flèches l'enjambent — 03:00 vers le bas, c'est 01:00 ce matin-là. Les menus et la liste ne la proposent pas du tout, sauf si skipNonExistent est désactivé, où la liste la montre barrée.