ICU : alias de fuseau et la TZ que vous rapportez
Demandez à ICU quelle identité de fuseau une page lit vraiment : comment les alias fusionnent, quand TZ gagne, et pourquoi un décalage fixe égale un fuseau à heure d’été.
BotBrowser Team
Vous voulez la documentation structurée pour Empreinte ?
Cet article fait partie de la bibliothèque éditoriale. Pour les étapes de configuration, la référence et les mises à jour continues, passez directement à la section docs.
BotBrowser prend en charge le rejeu d’un fixture autorisé de même origine sous un profil déclaré et la conservation d’observations bornées sur ce que la page rapporte, mais il ne peut pas faire renvoyer à ICU un nom avec lequel il n’a pas été compilé, changer la graphie qu’une version donnée d’ICU tient pour canonique, ni faire qu’un fuseau à décalage fixe se comporte comme un fuseau à heure d’été simplement parce que les deux lisent la même horloge en janvier. Cette revue exécute l’ICU livré avec le runtime Node sur une liste fixe de noms de fuseaux, rapporte ce que répond Intl.DateTimeFormat().resolvedOptions().timeZone pour chacun, puis sépare trois choses souvent confondues : le nom que lit la page, le décalage qu’elle rend et la variable d’environnement qui a fourni la valeur par défaut.
TL;DR
- Presque toutes les graphies historiques fonctionnent encore, mais ICU répond avec son propre nom canonique :
Asia/KolkatarapporteAsia/Calcutta,Europe/KyivrapporteEurope/Kiev. UTC,Etc/UTC,Etc/GMT,GMTetZulufusionnent tous enUTC, donc la chaîne rapportée ne peut pas les distinguer.- Définir
process.env.TZchange la valeur par défaut que rapporte l’appelIntlsuivant ; une optiontimeZoneexplicite l’emporte pour cet appel et laisse la valeur par défaut intacte. Etc/GMT+5etAmerica/New_Yorkrendent la même horloge en janvier et diffèrent d’une heure en juillet, donc un seul échantillon d’hiver ne sépare pas un décalage fixe d’un fuseau à heure d’été.- Les décalages ne sont pas limités aux heures entières :
Asia/KatmandurapporteGMT+5:45etPacific/ChathamrapporteGMT+13:45. - Tout cela est une propriété d’un couple ICU et tzdata précis ; cela ne dit rien de la façon dont un site note une session.
Contents
- Quel ICU cette exécution a utilisé
- Demandez à ICU le nom canonique, pas l’entrée
- Comptez combien d’entrées tombent dans un même groupe
- Où la variable TZ l’emporte vraiment
- Lisez le décalage, pas seulement le nom
- Où un décalage fixe imite un fuseau à heure d’été
- Lire une identité de fuseau sans surestimer
Quel ICU cette exécution a utilisé
La sonde imprime les versions d’ICU et de tzdata avant tout le reste, car chaque affirmation ci-dessous est bornée à ce couple. L’exécution a rapporté icu=78.3, tz=2026a, sur node=v26.9.0. Une autre compilation d’ICU peut porter une autre table canonique : une graphie traitée en alias aujourd’hui peut devenir canonique plus tard, et cela s’est produit quand des noms obsolètes ont été retirés.
Cette ligne de version est le moyen le moins coûteux de garder une revue de fuseaux honnête. Un rapport qui dit qu’une page « rapporte Europe/Kiev » sans dire quel ICU et quel tzdata l’ont produit ne peut pas être revérifié, car la réponse est une recherche dans une table livrée avec le runtime, pas un calcul d’horloge. Consigner les deux versions transforme une anecdote en observation reproductible, et explique aussi pourquoi deux machines avec la même version de navigateur peuvent diverger.
Demandez à ICU le nom canonique, pas l’entrée
La sonde passe 21 noms par new Intl.DateTimeFormat('en-US', { timeZone: zone }).resolvedOptions().timeZone. La propriété intéressante est que la réponse n’est souvent pas l’entrée :
Asia/Calcutta -> Asia/Calcutta
Asia/Kolkata -> Asia/Calcutta
Europe/Kiev -> Europe/Kiev
Europe/Kyiv -> Europe/Kiev
Asia/Rangoon -> Asia/Rangoon
Asia/Yangon -> Asia/Rangoon
Asia/Saigon -> Asia/Saigon
Asia/Ho_Chi_Minh -> Asia/Saigon
Pacific/Ponape -> Pacific/Ponape
Pacific/Pohnpei -> Pacific/Ponape
Chaque paire est un fuseau sous une graphie ancienne et une récente, et ICU résout les deux en un seul nom canonique. Le sens de la fusion compte : la graphie ancienne gagne dans cette compilation, donc une page qui rapporte Asia/Calcutta a très bien pu être configurée avec Asia/Kolkata, et une page qui rapporte Europe/Kiev a pu être réglée sur Europe/Kyiv. La chaîne rapportée atteste ce en quoi cet ICU canonicalise, pas ce que l’appelant a fourni.
La fusion ne se limite pas aux noms obsolètes. UTC, Etc/UTC, Etc/GMT, GMT et Zulu renvoient tous UTC, et US/Eastern renvoie America/New_York. Cinq entrées différentes deviennent une sortie, donc une comparaison qui ne lit que le nom résolu ne peut pas les distinguer, et un relevé qui ne garde que ce champ a perdu une information qu’il ne pourra jamais retrouver.
Dix des 21 noms sondés reviennent exactement comme écrit, plus que ne le suggèrent les paires ci-dessus : UTC, Etc/GMT+5, Etc/GMT-5, Asia/Calcutta, Europe/Kiev, America/Buenos_Aires, America/New_York, Asia/Rangoon, Pacific/Ponape et Asia/Saigon. Le groupe littéral ne signifie pas « déjà canonique » ; il signifie « déjà la graphie que cette table préfère ». Les deux variantes GMT en font partie et portent pourtant des décalages opposés. Ce sont des fuseaux distincts à décalages opposés, et la convention POSIX inverse le signe par rapport à l’habitude. Dans le test par paires ci-dessous, Etc/GMT+5 rend 07:00 quand UTC vaut 12:00, tandis que Etc/GMT-5 rend 17:00.
Un nom inconnu échoue bruyamment au lieu d’être normalisé : Mars/Olympus lève un RangeError, et de même les fautes plus proches Asia/Calcuttta, America/New_Yrok, Europe/Kievv et Europe/Berlin1. Seules les variantes de casse résolvent, donc asia/kolkata et ASIA/KOLKATA répondent tous deux Asia/Calcutta ; un nom avec des espaces en trop est rejeté comme toute autre chaîne inconnue. La conséquence est plus étroite qu’il n’y paraît : un fuseau mal orthographié est rejeté au lieu d’être mappé en silence, mais un alias correctement orthographié est remappé en silence, et la page ne peut pas distinguer les deux issues à partir de la seule chaîne rapportée. Une page qui rapporte un fuseau plausible a donc passé un contrôle, que le nom existe, et n’en a échoué aucun, car le nom rapporté peut encore différer de celui qui a été configuré.
Comptez combien d’entrées tombent dans un même groupe
La structure d’alias se raisonne mieux comptée que lue nom par nom. Une seconde passe regroupe neuf entrées candidates par la réponse résolue :
bucket Asia/Calcutta <- Asia/Kolkata, Asia/Calcutta
bucket REJECTED <- Asia/Bombay
bucket Asia/Dhaka <- Asia/Dacca, Asia/Dhaka
bucket Europe/Kiev <- Europe/Kyiv, Europe/Kiev, Europe/Uzhgorod, Europe/Zaporozhye
Quatre constats sortent de ces quatre lignes. Le fuseau de l’Inde absorbe deux graphies ; le nom long de Asia/Calcutta est India Standard Time et son décalage GMT+5:30, donc l’ancien nom ne décrit pas un autre pays. Le nom ancien de Mumbai, Asia/Bombay, n’est pas dans cette table et est rejeté d’emblée, ce qui montre que la table n’est pas une histoire complète de tous les noms jamais utilisés. Dhaka absorbe la graphie ancienne Asia/Dacca. Et le fuseau de Kyiv absorbe quatre entrées, dont deux villes de l’ouest de l’Ukraine qui sont géographiquement différentes mais résolvent vers la même entrée canonique.
Ce dernier groupe est celui qui casse le raisonnement naïf. Qui suppose que le nom canonique décrit un lieu lira Europe/Kiev comme « la session dit être à Kyiv ». Dans cette compilation il couvre aussi une session configurée en Europe/Uzhgorod, à environ 600 kilomètres. La chaîne canonique est une entrée de table, pas une affirmation sur l’endroit où se trouve la machine.
Le cas rejeté est le contre-exemple utile pour une autre raison. Si la table acceptait tous les alias historiques, un rejet serait une preuve forte d’une configuration erronée. Comme ce n’est pas le cas, un rejet prouve seulement que cette graphie précise manque dans cette compilation d’ICU. L’affirmation sûre est étroite : cette entrée n’a pas été acceptée ici, donc le fuseau rapporté par la page est venu d’un autre chemin.
Où la variable TZ l’emporte vraiment
La seconde sonde sépare la valeur par défaut d’une option explicite, ce qui est l’origine de l’essentiel de la confusion sur « dans quel fuseau est ce navigateur ».
TZ as inherited: resolved=America/Toronto toTimeString=07:00:00 GMT-0500 offset=GMT-5
TZ set at runtime: resolved=Asia/Tokyo toTimeString=21:00:00 GMT+0900 offset=GMT+9
explicit option wins over env: Europe/Berlin
default after explicit call still: Asia/Tokyo
invalid zone rejected: RangeError
long name part: Eastern Standard Time
Trois comportements ressortent de ces six lignes. Modifier process.env.TZ à l’exécution change ce que rapporte l’appel Intl suivant sans option, à la fois dans resolvedOptions() et dans toTimeString(), donc une valeur d’environnement n’est pas figée au démarrage du processus dans ce runtime. Une option timeZone explicite l’emporte sur l’environnement pour cet appel et laisse la valeur par défaut intacte pour les appels suivants, qui rapportent encore Asia/Tokyo. Et le nom long de America/New_York en janvier est Eastern Standard Time, un nom qui change lui-même avec la date.
La conséquence pour un fixture est que « la page rapporte le fuseau X » est ambigu tant que le relevé ne dit pas comment X a été fourni. Un processus lancé avec TZ défini et sans option par appel rapporte une valeur d’environnement ; une page qui passe une option explicite rapporte une décision prise dans le code. Les deux produisent la même chaîne dans resolvedOptions().timeZone quand elles concordent, et seule l’entrée consignée les distingue. C’est aussi pourquoi un rapport de support qui cite seulement le fuseau ne peut pas être reproduit : la même chaîne peut venir de l’un ou l’autre chemin, donc la première question est où la valeur est entrée dans le processus.
Il y a un piège de second ordre dans la même zone. Comme TZ est lu par le runtime et non par la page, une valeur héritée d’un profil de shell peut survivre dans un test qui croyait exercer une valeur dérivée du profil. La sonde montre le fuseau hérité en America/Toronto, venu de l’environnement de l’hôte et non d’une option de page. Un fixture qui ne consigne que le fuseau résolu attribuerait cela au profil testé. Vérifier l’environnement avant l’exécution, et noter si la variable a été définie au lancement ou modifiée ensuite, est la différence entre mesurer le profil et mesurer le shell.
Une option explicite ne réécrit pas non plus durablement la valeur par défaut. La sonde définit une valeur par défaut via TZ, formate avec un fuseau explicite, puis demande à un formateur neuf la valeur par défaut et reçoit encore celle de l’environnement. Deux lectures consécutives dans un même script peuvent donc diverger si l’une a passé une option, et une comparaison qui entrelace les deux formes d’appel sans les étiqueter donnera l’impression que le fuseau a changé entre les lectures.
Lisez le décalage, pas seulement le nom
Une identité de fuseau est d’ordinaire consignée comme une chaîne, mais le même fuseau peut rendre six formes de nom selon la question posée :
name shortOffset: GMT-5
name longOffset: GMT-05:00
name short: EST
name long: Eastern Standard Time
name shortGeneric: ET
name longGeneric: Eastern Time
Ce ne sont pas des preuves interchangeables. GMT-5 et GMT-05:00 portent un décalage et aucune saison, donc ils restent stables toute l’année. EST incruste la désignation d’hiver et lirait EDT en juillet. ET et Eastern Time sont neutres vis-à-vis de la saison et conviennent aux deux. Un rapport qui consigne « la page dit EST » puis le compare à un échantillon d’été a comparé deux étiquettes différentes et peut signaler un faux changement.
Les décalages ne se limitent pas non plus aux heures entières, une supposition courante qui échoue sur de vrais fuseaux :
zone Asia/Kathmandu -> Asia/Katmandu GMT+5:45
zone Australia/Eucla -> Australia/Eucla GMT+8:45
zone Pacific/Chatham -> Pacific/Chatham GMT+13:45
zone Asia/Kolkata -> Asia/Calcutta GMT+5:30
Katmandou canonicalise de plus vers une autre graphie, Asia/Katmandu, et rend un décalage de 45 minutes. Chatham rend GMT+13:45 en janvier car ses règles d’heure d’été sont celles de l’hémisphère sud. Un validateur qui attend des décalages sur l’heure ou un signe positif seulement en dessous d’une certaine taille lira mal les quatre lignes de la sonde sans signaler aucune erreur.
La dimension de locale ajoute une troisième variable. Le même instant et le même fuseau se rendent différemment selon la locale, en changeant à la fois l’ordre et la convention d’horloge :
locale en-US: hourCycle=h12 hour12=true -> Jan 15, 2026, 7:00 AM
locale en-GB: hourCycle=h23 hour12=false -> 15 Jan 2026, 07:00
locale de-DE: hourCycle=h23 hour12=false -> 15.01.2026, 07:00
locale fr-FR: hourCycle=h23 hour12=false -> 15 janv. 2026, 07:00
locale ja-JP: hourCycle=h23 hour12=false -> 2026/01/15 7:00
en-US est la seule ligne sur un cycle de 12 heures, et ja-JP imprime 7:00 sans zéro initial tandis que les autres lignes h23 impriment 07:00. Une comparaison qui normalise des dates en chaînes entre locales signalera donc quatre différences qui sont toutes du formatage, pas de l’état de session.
Où un décalage fixe imite un fuseau à heure d’été
La dernière section échantillonne deux dates séparées d’un an, le moyen le moins coûteux de séparer un décalage fixe d’un fuseau qui observe l’heure d’été :
dst America/New_York: 07:00 / 08:00
dst Etc/GMT+5: 07:00 / 07:00
dst Europe/Berlin: 13:00 / 14:00
America/New_York et Etc/GMT+5 rendent tous deux 07:00 pour un instant de janvier, donc un seul échantillon d’hiver ne les sépare pas. En juillet ils diffèrent d’une heure, et Europe/Berlin montre le même saut de 13:00 à 14:00. Un rapport qui capture un instant et déclare le fuseau cohérent a mesuré un accord à une date, pas l’identité du fuseau.
Le test par paires de décalages dit la même chose depuis l’autre côté :
pair Etc/GMT+5 vs America/New_York: 07:00 / 07:00
pair Etc/GMT-5 vs Asia/Karachi: 17:00 / 17:00
pair UTC vs Etc/UTC: 12:00 / 12:00
Chaque paire concorde à l’instant échantillonné tout en décrivant des fuseaux différents. Deux des trois paires concorderaient aussi en juillet ; seule la première divergerait, car un seul côté observe l’heure d’été. C’est pourquoi la comparaison hiver seulement est le piège : elle produit un résultat concordant pour chaque paire de cette table, y compris celle qui divergera six mois plus tard.
Si un fixture ne peut s’offrir qu’un échantillon, l’affirmation honnête est « ces deux ont concordé à cette date ». Un second échantillon dans la saison opposée coûte presque rien et transforme cette affirmation en « ces deux décrivent les mêmes règles de décalage », qui est celle que la plupart des revues de fuseaux veulent réellement faire.
L’hémisphère sud rend la règle d’échantillonnage plus stricte que « prenez été et hiver ». Pacific/Chatham rend GMT+13:45 en janvier, qui est son été, donc un relecteur de l’hémisphère nord qui lit cette valeur comme une lecture d’hiver a la saison à l’envers. Australia/Eucla garde un GMT+8:45 constant car il ne décale jamais. Une paire d’échantillons à six mois d’écart les distingue quand même, mais seulement si le relevé garde l’instant à côté de la valeur rendue au lieu d’étiqueter un échantillon « hiver » par supposition.
Le choix des instants compte aussi. Un échantillon pris à 12:00 UTC garde les deux dates proprement comparables, tandis qu’un échantillon proche de minuit local peut déplacer la date rendue autant que l’heure, et une comparaison qui ne stocke que l’heure lira le décalage de date comme un jour manquant. Consigner l’instant ISO exact supprime les deux ambiguïtés sans coût supplémentaire.
Le fuseau est un champ à côté de deux voisins qui suivent des règles différentes, donc un fixture qui ne consigne que celui-ci reste incomplet. Fuseau horaire, locale et langue couvre l’interaction des trois, et navigator language et localisation des applications montre que la liste de langues a son propre problème de canonicalisation, où un tableau ordonné est comparé à un seul en-tête négocié.
Lire une identité de fuseau sans surestimer
Une lecture de fuseau étaye une affirmation étroite : cette compilation d’ICU, avec cette entrée, a rapporté ce nom canonique et ce décalage rendu pour cet instant. Elle n’affirme pas quel alias a été configuré, si le fuseau observe l’heure d’été, quelle locale a produit la chaîne ni comment un site traite une session qui la rapporte.
Gardez cinq champs dans le relevé et la lecture reste vérifiable. Les versions d’ICU et de tzdata, car la table canonique et les règles d’heure d’été viennent avec elles. Le nom d’entrée tel que fourni, car la canonicalisation l’écarte. L’instant échantillonné, car un décalage fixe et un fuseau à heure d’été concordent en hiver. La forme d’appel, car une valeur d’environnement et une option explicite remplissent le même champ de sortie. Et la locale avec son hourCycle, car la chaîne rendue dépend des deux.
La page de documentation correspondante couvre la façon dont BotBrowser garde fuseau, locale et langue alignés sur le profil chargé. Cette revue ajoute le côté lecture : ce qu’ICU fait des noms que ces réglages produisent, quelles graphies fusionnent en une réponse, combien d’entrées partagent un groupe et où un seul échantillon ne distingue pas deux fuseaux différents. Une configuration cohérente et une interprétation correcte de la chaîne rapportée sont deux questions distinctes, et un fixture qui ne consigne que le nom résolu ne répond complètement à aucune des deux.
Pour toute décision sur une session réelle, utilisez un résultat d’intégration autorisé du système qui décide plutôt que d’extrapoler depuis cette sonde. Un nom canonique consigné dans un fixture et un verdict de production sont deux types de preuve différents, et c’est dans l’écart entre les deux que se produit la surestimation.
Practical conclusion
Consignez les versions d’ICU et de tzdata, le nom d’entrée, l’instant échantillonné, la forme d’appel et la locale avec son cycle horaire. Dans cette compilation les graphies anciennes gagnent la canonicalisation sur quatre groupes, Asia/Bombay est rejeté d’emblée, cinq entrées de type UTC fusionnent en UTC, TZ définit une valeur par défaut qu’une option explicite remplace sans la changer, les décalages incluent des valeurs de 45 minutes, et un décalage fixe concorde avec un fuseau à heure d’été en janvier et diverge en juillet.
Sources
- MDN : Intl.DateTimeFormat
- MDN : Intl.DateTimeFormat resolvedOptions
- Base de données des fuseaux horaires IANA
- Données d’alias de fuseaux de CLDR
- Documentation BotBrowser sur fuseau, locale et langue
Next reading
Consultez fuseau horaire, locale et langue pour l’interaction des trois champs, et navigator language et localisation des applications pour le côté langue du même profil.
Cet article fait partie du guide : Qu'est-ce qu'une empreinte de navigateur ?
Articles Connexes
Fuseau horaire, locale et langue dans les API du navigateur
Empreinteua-parser-js : Client Hints, bots et un plafond de 500
EmpreinteFingerprinting MIME et codec : signaux de prise en charge
EmpreinteLire les signaux d’empreinte du navigateur
PlateformeFormatage des dates et portabilité des fuseaux horaires
EmpreinteBotD : quels détecteurs s’exécutent vraiment
Faites passer BotBrowser de la recherche à la production
Utilisez ces guides pour comprendre le modèle, puis passez à la validation multi-plateforme, aux contextes isolés et au déploiement navigateur prêt pour l'échelle.