Nous allons voir dans cet article comment formatter des dates facilement dans le navigateur en évitant de faire appel à des bibliothèques tierces.

Formater avec MomentJS

Lorsque l’on veut manipuler des données temporelles avec Javascript dans le navigateur, on se repose souvent sur la bibliothèque Moment.js.
Elle permet en effet de formater des dates à sa convenance :

Exemple de formatage de date complet avec Moment.js
1
2
moment().format('MMMM Do YYYY, HH:mm:ss');  
// October 19th 2019, 19:04:56
Exemple de formatage de date sans heure avec Moment.js
1
2
moment().format('dddd, MMMM do YYYY');  
// Saturday, October 6th 2019

Si vous chargez aussi le support des différentes locales, ça peut vous donner ceci :

Exemple de formatage de date localisé avec Moment.js
1
2
3
moment.locale('fr');  
moment().format('LLL');
// 19 octobre 2019 19:09

Plutôt pratique, n’est-ce pas ? Du coup Moment.js est très populaire (42k stars sur Github) et très utilisé (presque 2 millions d’installations journalières via npm, où plus de 36 000 paquets en sont dépendants ).

Confort vs poids

Le souci de Moment.js, c’est son poids énorme de 329Ko (non compressé, et avec toutes les locales).
Du coup ça fait un sacré module à charger (et il faut mieux qu’il soit chargé dès le début, c’est pas très joli quand les dates s’affichent une fois seulement que la page est chargée).

Y a quelques solutions pour améliorer cela, par exemple charger uniquement la locale de l’utilisateur qui peut être fournie par le backend en fonction de l’en-tête Accept-Language.
On peut ensuite éventuellement charger dynamiquement d’autres locales si jamais l’utilisateur⋅ice décide de changer de langue.

Cela dit, même en chargeant uniquement le cœur de Moment.js, on reste à plusieurs dizaines de kilo-octets pour une utilisé potentiellement très limitée.

En réaction à cette problématique, les bibliothèques dayjs et date-fns - de poids beaucoup plus réduit - existent. Date-fns permet de faire du tree-shaking avec Webpack pour importer uniquement les modules dont on a besoin et dayjs n’incorpore que le minimum, en externalisant les fonctionnalités supplémentaires via des plugins.

Pour la plupart des fonctionnalités, ça remplace très bien Moment.js. Vous pouvez voir un comparatif sur ce dépôt : You don’t (may not) need Moment.js.

Encore plus léger

Et si on voulait s’épargner encore quelques kilo-octets, on regarderait du côté…de Javascript. En effet, il existe depuis relativement assez longtemps (2012) une API Intlavec notamment un objet DateTimeFormat fait pour formater des dates en fonction de la locale.

Ça s’utilise comme cela :

Exemple de formatage de date localisé avec l'API Intl
1
2
3
let date = new Date();
new Intl.DateTimeFormat('fr-FR').format(date);
// 10/19/2019

Ça prend en charge correctement les caractères non-latins et le sens d’écriture de droite à gauche :

Exemple de formatage de date localisé en arabe avec l'API Intl
1
2
3
let date = new Date();
new Intl.DateTimeFormat('ar-EG').format(date);
// ١٩‏/١٠‏/٢٠١٩

Si on ne veut pas spécifier de locale, on peut passer l’argument 'default' ou bien undefined au constructeur de Intl.DateTimeFormat, ça utilisera la locale configurée du navigateur :

Exemple de formatage de date pour la locale par défaut avec l'API Intl
1
2
3
let date = new Date();
new Intl.DateTimeFormat('default').format(date);
// 10/19/2019

Bon, et maintenant si on veut une vraie date formattée, comment on fait ?

Avec le second argument du constructeur, pardi !

Exemple de formatage de date complexe et localisé avec l'API Intl
1
2
3
4
5
6
let date = new Date();
let options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
new Intl.DateTimeFormat('default', options).format(date);
// samedi 19 octobre 2019
new Intl.DateTimeFormat('ar-EG', options).format(date);
// السبت، ١٩ أكتوبر ٢٠١٩

La référence de ces options se trouve sur cette page du MDN.

Comme l’API Intl est assez ancienne, le support est plutôt bon (Internet Explorer 11, Safari 10).

Formatage de dates relatives

Je note qu’il existe aussi un objet Intl.RelativeTimeFormat pour afficher des temps relatifs :

Exemple de formatage de date relative avec l'API Intl
1
2
3
4
5
6
7
8
9
const rtf = new Intl.RelativeTimeFormat('default', { numeric: "auto" });
rtf.format(-1, "day");
// 'hier'

rtf.format(3, "day");
// 'dans 3 jours'

rtf.format(-6, "month");
// 'il y a 6 mois'

Cette fois par contre, pas de support pour Internet Explorer, Edge ou Safari, ce qui rend son emploi plus hasardeux.
On peut bien entendu détecter cela avec

Détection des fonctionnalités de l'API Intl
1
2
3
4
5
6
7
8
9
if (Intl.DateTimeFormat) {
// do something
// it should probably work
}

if (Intl.RelativeTimeFormat) {
// do something
// you're using Firefox or Chrome
}

Conclusion

Voilà, même avec l’addition récente de RelativeTimeFormat, l’API Intl est vraiment loin d’avoir toutes les fonctionnalités de Moment.js ou même de date-fns, mais s’il vous faut juste afficher quelques dates au bon format, pas besoin de charger des centaines de kilooctets pour rien.🙂

Bonus

En utilisant l’API Intl, on peut facilement récupérer la liste des mois et des noms des jours traduits dans la langue de l’utilisateur :

Fonction pour générer la liste des noms des mois
1
2
3
4
5
6
7
8
9
10
11
function localeMonthNames() {
const monthNames = [];
for (let i = 0; i < 12; i += 1) {
const d = new Date(2019, i, 1);
const month = d.toLocaleString('default', { month: 'long' });
monthNames.push(month);
}
return monthNames;
}
localeMonthNames();
// [ "janvier", "février", "mars", "avril", "mai", "juin", "juillet", … ]

Là, c’est plutôt simple, on n’a qu’à itérer chaque mois dans le constructeur de Date pour avoir son nom textuel. On note quand même que les mois commencent à 0.

Fonction pour générer la liste des noms des jours
1
2
3
4
5
6
7
8
9
10
11
function localeShortWeekDayNames() {
const weekDayNames = [];
for (let i = 13; i < 20; i += 1) {
const d = new Date(2019, 9, i);
const weekDay = d.toLocaleString('default', { weekday: 'short' });
weekDayNames.push(weekDay);
}
return weekDayNames;
}
localeShortWeekDayNames();
// [ "dim.", "lun.", "mar.", "mer.", "jeu.", "ven.", "sam." ]

Ici, c’est plus compliqué, il faut partir d’une date où l’on sait qu’il s’agit d’un dimanche, puis itérer sur les 7 jours de la semaine. Et oui, le jour 0 est bien un Dimanche.
Ici j’ai mis les versions courtes des noms des jours, mais ça marche aussi évidemment avec la fonction complète.

Ça peut éviter de traduire des informations inutiles.

Un truc qui manque par contre, c’est déterminer en fonction de la locale quel jour de la semaine celle-ci commence. En effet, si la fameuse norme ISO 8601 définit lundi comme le premier jour de la semaine, certains pays comme les États-Unis d’Amérique, le Canada, le Japon et l’Australie entament leur semaine le Dimanche. Au Moyen-Orient, elle commence le Samedi. Du coup si vous n’avez pas de bibliothèque tierce, il faut fournir l’option aux traducteurs pour qu’ils mettent leur propre valeur.

Image « Calendrier Mural Jours » par Andreas Lischka sur Pixabay