# Fuseau horaire — le silence d'une heure > Ouvert le 2026-09-04, après qu'une obligation éco déclarée à `"12:00"` se soit résolue > à 13:00 sur l'horloge de l'atelier. Le moteur avait raison ; c'est la question qui > n'avait jamais été posée. ## 1. Le constat `.75` tourne en `Europe/London`. Une obligation `dailyDeadline: "12:00"` s'y résout donc à 12:00 heure de Londres, soit **13:00 en France**. Rien, nulle part, ne le dit. L'écart est d'une heure et ressemble trait pour trait à un défaut d'heure d'été dans la résolution d'échéance — j'ai failli l'imputer au moteur avant de lire l'horloge de la box. C'est le premier coût du silence : il déplace le soupçon. Le second coût est celui qui compte. Chez un client français, une box mal fuseau-horée décale **toutes** ses fenêtres temporelles. Une obligation « avant 7 h » se déclenche à 8 h. Et quand les heures creuses, Tempo et le marché de gros arriveront, elles se décaleront aussi — sauf que là, personne ne le verra avant de compter sa facture. ## 2. Fait établi — il n'y a qu'UN fuseau, et il vient du système Vérifié, pas supposé : - `EnergyArbitrator::update()` prend `QDateTime::currentDateTime()` (`energyplugin/etm/energyarbitrator.cpp:329`), donc le fuseau du PROCESSUS `nymead`. - `resolveDeadline()` construit l'échéance avec `now.timeZone()` (`energyplugin/etm/types/ecotarget.cpp`). Elle n'introduit aucun fuseau à elle : elle hérite. - nymea n'a pas de fuseau propre. `System.GetTime` renvoie `QTimeZone::systemTimeZoneId()` (`libnymea-core/jsonrpc/systemhandler.cpp`), et `System.SetTimeZone` écrit le fuseau **système** via le `SystemController` de la plateforme. `Configuration.SetTimeZone` est déprécié au profit du premier. **Conséquence : la box et le moteur ne peuvent pas diverger entre eux.** Il n'existe pas de réglage nymea qui dirait autre chose que `timedatectl`. C'était la première question — elle est fermée, et dans le sens rassurant. ## 3. Fait mesuré — un processus vivant SUIT le changement Mesuré le 2026-09-04 dans le conteneur `build-cross-arm64`, avec un programme Qt6 qui lit `QTimeZone::systemTimeZoneId()` et `QDateTime::currentDateTime()` en boucle pendant qu'on bascule `/etc/localtime` sous lui : ``` systemTimeZoneId=Europe/London currentDateTime=06:48:50 BST zone(now)=Europe/London --- /etc/localtime bascule vers Europe/Paris --- systemTimeZoneId=Europe/Paris currentDateTime=07:48:53 CEST zone(now)=Europe/Paris ``` **Changer le fuseau ne demande donc PAS de redémarrer `nymead`.** Ce n'est pas une évidence : la mise en cache du fuseau par processus est un comportement classique de la libc, et si Qt l'avait fait, une correction de fuseau serait restée sans effet jusqu'au prochain redéploiement — une correction qui ne corrige rien étant pire que pas de correction. Corollaire à ne pas perdre : une échéance en cours de résolution peut donc **changer d'heure sous le moteur**, sans redémarrage et sans notification. Cf. §6, étape 3. ## 4. La vraie divergence n'est pas là où on la cherchait Box et moteur s'accordent par construction. Ce qui peut diverger, c'est : **(a) L'installation et le client.** `dailyDeadline` est une chaîne `"HH:mm"` **sans fuseau**. Le client saisit « 7:00 » dans l'app, sur un téléphone qui est dans SON fuseau, en pensant 7:00 chez lui. Le moteur la lit dans le fuseau de la box. Si les deux diffèrent, l'écart est silencieux **des deux côtés** — l'app affiche ce qu'elle a saisi, le moteur exécute autre chose, et les deux ont raison dans leur référentiel. Un fuseau implicite est une absence qui signifie « le défaut s'applique ». C'est exactement ce qu'on s'interdit ailleurs (`measurement.source`, `rankOrigin`, `progress.regime`). **(b) L'installation et le CALENDRIER RÉGLEMENTÉ.** Et c'est le point neuf. ## 5. La distinction qui décide : deux natures d'heure murale Elles ne prennent pas leur fuseau au même endroit, et les confondre est le piège. | | nature | fuseau qui fait foi | |---|---|---| | `dailyDeadline`, « avant 7 h » | **habitude du client** | celui de l'installation | | heures creuses, Tempo, EPEX | **calendrier réglementé** | celui où le tarif est DÉFINI | Une plage d'heures creuses française est définie en `Europe/Paris` par le fournisseur, pas « à l'heure locale de la box ». Une box française mal réglée ne décale pas seulement l'affichage : elle **achète au mauvais moment**, et le tarif, lui, ne s'est pas décalé. Aujourd'hui les deux natures prendraient silencieusement le fuseau de la box. Pour la première c'est correct **si** la box est bien réglée ; pour la seconde c'est faux dès qu'elle ne l'est pas. Une grandeur réglementée ne doit donc pas hériter du fuseau système : elle doit porter le sien. Le marché de gros amont travaille en instants ABSOLUS (les tests construisent leurs horodatages via `EnergyTestBase::utcDateTime`), donc il est déjà à l'abri. **Le risque naît le jour où l'on écrira une plage tarifaire en heures murales — et pas un jour avant.** > **La règle opposable ne vit donc PAS ici.** Elle est écrite là où le risque naît : > `specs/spec_loadmodel.md`, **LM-1005-b**, en précondition de livraison du lot tarifaire — > au même titre que le délestage était précondition du §10. Une distinction rangée dans le > document qui l'a découverte est une note ; rangée dans le lot qu'elle contraint, c'est une > condition d'entrée. Ce document n'en garde que l'établissement des faits. ## 6. La décision : ni imposer, ni refuser — DÉCLARER **Ne pas imposer.** Le moteur ne connaît pas le fuseau « vrai » de l'installation ; il ne connaît que celui de la box. Le forcer à `Europe/Paris` serait poser une affirmation qu'on ne sait pas vérifier, et serait faux par construction pour toute box vendue hors de France. **Ne pas refuser de fonctionner.** Un HEMS qui s'arrête parce qu'un téléphone est en itinérance laisse un chauffe-eau sans pilotage et une protection de disjoncteur sans arbitre. **Les couches L0 à L4 ne doivent dépendre d'aucun fuseau** : elles raisonnent en durées, pas en heures murales. Le refus est disproportionné, et il déplacerait un problème d'affichage vers la sécurité. **Déclarer**, donc — la seule réponse conforme à la doctrine maison : *publier le régime, ne jamais le laisser déduire.* Le moteur dit dans quel fuseau il résout les heures murales ; c'est au client de rapprocher cela de l'heure de son utilisateur, et de l'avertir. ### Les étapes, à comportement constant d'abord 1. **Documenter le contrat** (aucun changement de code). `dailyDeadline` est une heure murale du fuseau de la box, tel que publié par `System.GetTime.timeZone`. Écrit dans `INTERFACE.md`, à côté du champ. 2. **Publier le fuseau avec la configuration**, en lecture seule, plutôt que d'exiger du client qu'il croise deux espaces de noms pour interpréter un de NOS champs. Un client qui doit appeler `System.GetTime` pour donner un sens à `dailyDeadline` finira par ne pas le faire. 3. **Dire le changement.** Le fuseau peut bouger sous le moteur sans redémarrage (§3). Une échéance résolue à 12:00 hier peut valoir 13:00 aujourd'hui sans qu'aucun champ n'ait changé. À traiter comme tout élément runtime : un `Get*` ET un `*Changed`. 4. **Séparer les deux natures** — porté par **LM-1005-b**, précondition du lot tarifaire, et non par ce lot-ci. Rien à faire ici : la condition est posée à l'entrée de l'autre. 5. **Côté app** (lot séparé, à elle) : comparer le fuseau de la box à celui de l'appareil au moment de la SAISIE d'une échéance, et avertir. C'est l'écran de saisie qui doit parler, pas l'écran de résultat — au moment du résultat, l'heure est déjà passée. ### Ce qui reste ouvert, et qu'on ne tranche pas ici Le fuseau de `.75` lui-même. `Europe/London` sur un banc français est une erreur de configuration, mais la corriger déplace d'une heure la lecture de tout l'historique d'énergie déjà enregistré. C'est une décision d'exploitation, pas de conception.