Traduire les dates
Article initialement publié sur inside.laruche.com et écrit par Eric Berthe.
29 mai 2017. 29 May 2017. May 29, 2017. 2017年5月29日.
Quatre représentations de la même date, le vingt-neuvième jour du cinquième mois de la deux mille dix-septième année du calendrier grégorien, dans quatre langues (le français, l’anglais britannique, l’anglais américain, le japonais). Autant de syntaxes fondamentalement diverses, avec non seulement un vocabulaire différent, mais également un ordre des éléments variable, une ponctuation et une typographie spécifiques à chaque pays.
Bienvenue dans le monde merveilleux de la traduction de dates.
Retranscrire une date dans un format qui soit naturel pour un locuteur natif est un vrai défi. Il n’y aucune logique à chercher, aucun algorithme universel à employer. Il faut, pour chaque langue, et même pour chaque variante de langue, connaître la règle particulière en vigueur et l’appliquer.
Heureusement, de courageux héros, réunis au sein du Unicode Common Locale Data Repository, ont déjà accompli cette tâche titanesque pour nous, associant à chaque code IETF, la syntaxe et le vocabulaire qui lui correspondent.
Et d’autres, au sein du projet ICU, ont transformé cette donnée brute en du code C/C++ directement prêt à l’emploi, lui-même intégré à votre navigateur. Qui peut donc être appelé avec un peu de Javascript, via Intl.DateTimeFormat.
Toutefois, je ne vais pas m’étendre sur cette implémentation, parce que ce ne serait que de la redite de la documentation de MDN, qui est, comme toujours, très bien faite.
IntlDateFormatter
À la place, je vais plutôt m’intéresser à une autre intégration, trop souvent ignorée mais pourtant très utile à connaître, car correspondant au langage encore aujourd’hui le plus utilisé dans la conception de sites web : IntlDateFormatter.
Ouvrant directement le bal avec un exemple :
<?phpfunction day(\DateTime $date, $locale) {
return (new \IntlDateFormatter($locale, \IntlDateFormatter::LONG, \IntlDateFormatter::NONE, $date->getTimezone()))->format($date);
}function hour(\DateTime $date, $locale) {
return (new \IntlDateFormatter($locale, \IntlDateFormatter::NONE, \IntlDateFormatter::MEDIUM, $date->getTimezone()))->format($date);
}function mixed(\DateTime $date, $locale) {
return (new \IntlDateFormatter($locale, \IntlDateFormatter::SHORT, \IntlDateFormatter::SHORT, $date->getTimezone()))->format($date);
}$date = new \DateTime();
$locales = [‘fr_FR’, ‘en_GB’, ‘en_US’, ‘ja_JA’];foreach ($locales as $locale) {
print($locale.PHP_EOL);
print(day($date, $locale).PHP_EOL);
print(hour($date, $locale).PHP_EOL);
print(mixed($date, $locale).PHP_EOL);
}
Soit en retour :
fr_FR
29 mai 2017
14:39:13
29/05/17 14:39
en_GB
29 May 2017
14:39:13
29/05/2017 14:39
en_US
May 29, 2017
2:39:13 PM
5/29/17 2:39 PM
ja_JA
2017年5月29日
14:39:13
17/05/29 14:39
Ou, pour ne garder que l’essentiel :
return (new \IntlDateFormatter($locale, $datetype, $timetype, $date->getTimezone()))->format($date);
Où $datetype et $timetype sont deux constantes à choisir dans la liste suivante : http://php.net/manual/fr/class.intldateformatter.php#intl.intldateformatter-constants
Cette syntaxe (directement reprise de l’implémentation en C) n’est pas forcément très intuitive, et je conseille d’expérimenter sur quelques exemples, typiquement en copiant-collant le code précédent dans un coin et en jouant avec les constantes employées, les dates et les locales.
En pratique, une fois le coup pris, ça fonctionne plutôt pas mal, et ça permet de se libérer d’un poids. Ainsi, une fois le bon format déterminé pour une langue donnée, on a l’assurance d’en avoir le meilleur équivalent possible dans chaque autre, sans prise de tête.
Gérer les exceptions
Terminons par le sujet qui fâche. Les exceptions à la norme.
En effet, parfois, quand une date ou une horaire est intégrée à une phrase complète, il peut être nécessaire, pour une langue donnée, de prendre quelques libertés avec la norme pour que le texte sonne plus naturel.
Exemple : Le Royaume-Uni emploie officiellement le système horaire sur 24 heures. La machine nous renverra donc l’heure dans ce format, et c’est d’ailleurs ce qui se passe dans l’exemple ci-dessus.
Sauf que, comme pour le système métrique, il y a un fossé entre adoption officielle et adoption populaire. Ainsi dans la langue parlée, et même la langue écrite informelle, AM et PM dominent encore.
Une exception, codée comme telle, peut donc s’avérer nécessaire, via un formattage explicite (syntaxe) :
function hour(\DateTime $date, $locale) {
if (‘en_GB’ === $locale) {
return (new \IntlDateFormatter($locale, null, null, $date->getTimezone(), null, ‘h:mm a’))->format($date);
}
return (new \IntlDateFormatter($locale, \IntlDateFormatter::NONE, \IntlDateFormatter::SHORT, $date->getTimezone()))->format($date);
}$date = new \DateTime(‘16:00’);
$locales = [‘fr_FR’, ‘en_GB’, ‘en_US’, ‘ja_JA’];foreach ($locales as $locale) {
print($locale.PHP_EOL);
print(hour($date, $locale).PHP_EOL);
}
Soit en retour :
fr_FR
16:00
en_GB
4:00 PM
en_US
4:00 PM
ja_JA
16:00
J’insiste toutefois, un peu lourdement je sais, sur le fait que ces exceptions doivent rester exceptionnelles, et n’être appliquées qu’en toute connaissance de cause, en ayant explicitement et strictement défini la langue et le contexte dans lesquelles elles s’appliquent.
Surtout, ne jamais utiliser la surcharge du format, qui prend le pas sur tous les autres paramètres, dans le cas général. C’est un coup à se retrouver avec 4:00 PM en français, ce qui n’aurait aucun sens.
Ou, plus subtilement, des dates en XX/YY/ZZ où le mois et le jour sont inversés par rapport à la norme en vigueur du pays auquel on s’adresse, avec les problèmes évidents que cela implique.