Le contrat porte désormais la règle : tout horodatage publié est en UTC avec son
marqueur, ou en epoch. Trois champs, deux formats, rien ne l'exigeait.
`instantDeLaBox` (lib/models/box_instant.dart) devient l'unique lecteur pour ce
que la box publie — epoch ou ISO, marqué ou nu — et requalifie la forme nue en
UTC avant de rendre l'heure du lecteur. Politique valable pour la BOX seule :
`Installation.lastSeen` est écrite en local sans marqueur et relue pareil, donc
la lire en UTC la décalerait. Deux héritages opposés sous une même forme, d'où
l'absence délibérée d'un « parser une date » unique.
Le `.toLocal()` posé hier dans telemetry_text est retiré : le modèle est seul
propriétaire de la conversion. Le rejouer serait sans effet mais laisserait
croire qu'elle peut manquer ailleurs.
CORRECTION. J'avais écrit que le défaut avait survécu « parce que box et
téléphone partageaient l'heure ». C'est faux, et la fausseté est utile : l'écart
est entre UTC et l'APPAREIL, donc visible sur tout téléphone hors UTC+0 quel que
soit le fuseau de la box. Mesuré ce matin avec les deux en Europe/Paris — la box
publie 04:33:28Z, l'APK d'hier affiche « 04:33 », il fallait lire 06:33. La
bascule en Europe/London n'a pas donné la visibilité, elle a donné le MOTIF de
regarder. La version fausse fait conclure « les fuseaux coïncident, donc c'est
réglé » et laisse le défaut en place.
`GetVariableLoadState` n'existe pas sur .75 (absente d'Introspect) et l'app ne
lit aucun `since` : rien à corriger de ce côté. Le champ existe en revanche dans
GetLoadTelemetry — `loads[].command {divergent, expectedW, observedW, since}` —
et il porte bien le Z, vérifié à la sonde. Aucun écran ne le consomme encore.
Contrôle négatif de la ligne d'horloge : même binaire qu'hier, box repassée en
Europe/Paris, la ligne a disparu sous le champ d'échéance.
docs/captures/2026-09-07_fuseau-box-paris-ligne-absente.png
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GKM8FDNHZWogAsjjfB8pSw
66 lines
3.5 KiB
Dart
66 lines
3.5 KiB
Dart
/// **Lire un INSTANT publié par la box** — et refuser de deviner son fuseau.
|
|
///
|
|
/// Le contrat porte désormais la règle : *tout horodatage publié est en UTC avec son
|
|
/// marqueur, ou en epoch*. Ce fichier existe parce qu'elle n'a pas toujours été portée, et
|
|
/// que l'écart ne se voyait pas.
|
|
///
|
|
/// ## Ce que Dart fait, et pourquoi c'est le piège
|
|
///
|
|
/// ```
|
|
/// DateTime.parse('2026-09-06T20:19:04') -> isUtc = false, .hour = 20, toLocal().hour = 20
|
|
/// DateTime.parse('2026-09-06T20:19:04Z') -> isUtc = true, .hour = 20, toLocal().hour = 22
|
|
/// ```
|
|
///
|
|
/// Une chaîne **sans marqueur** rend un `DateTime` que Dart tient pour local. `toLocal()`
|
|
/// n'a alors **aucun effet** — il ne convertit que ce qui est marqué UTC. Une valeur qui
|
|
/// était pourtant de l'UTC se retrouve donc décalée du fuseau de l'appareil, sans erreur,
|
|
/// sans trace, et dans les deux sens selon l'endroit du monde où on la lit.
|
|
///
|
|
/// Deux défauts frères, trouvés le même jour par la même bascule : côté app, `measuredSince`
|
|
/// rendu en heure UTC ; côté moteur, `command.since` publié sans marqueur.
|
|
///
|
|
/// ⚠️ Ce que la bascule a donné, c'est le **motif de regarder**, pas la visibilité. L'écart
|
|
/// se joue entre UTC et l'**appareil** : il est visible sur tout téléphone hors UTC+0, quel
|
|
/// que soit le fuseau de la box. Vérifié le 2026-09-07 avec les deux en `Europe/Paris` —
|
|
/// `04:33:28Z` publié, « 04:33 » à l'écran, 06:33 attendu. Croire que le fuseau de la box y
|
|
/// est pour quelque chose mène à « les fuseaux coïncident, donc c'est réglé », et laisse le
|
|
/// défaut en place.
|
|
///
|
|
/// ## La politique retenue pour une chaîne sans marqueur
|
|
///
|
|
/// On la lit comme de l'**UTC**, et ce n'est pas une supposition : c'est la forme
|
|
/// qu'une box antérieure au contrat publiait, **mesurée** avant sa correction — de l'UTC
|
|
/// écrit sans le dire. Comme le contrat interdit désormais la forme nue, l'ensemble des
|
|
/// chaînes non marquées est clos : ce sont les anciennes box, et elles sont en UTC.
|
|
///
|
|
/// ⚠️ Cette politique vaut pour ce que **la box publie**, et pour rien d'autre. Les dates
|
|
/// que l'app écrit elle-même (`Installation.lastSeen`) sont sérialisées en local sans
|
|
/// marqueur et relues pareil : les lire comme de l'UTC les décalerait. Deux héritages
|
|
/// opposés sous une même forme — c'est précisément pourquoi il n'y a **pas** de « parser une
|
|
/// date » unique dans ce dépôt, et pourquoi celui-ci nomme sa source dans son nom.
|
|
library;
|
|
|
|
/// L'instant publié par la box, **rendu dans le fuseau du lecteur**.
|
|
///
|
|
/// Accepte l'epoch en secondes (`num`) et la chaîne ISO, marquée ou non. Rend `null` sur
|
|
/// tout le reste — un champ absent est un état, pas un zéro.
|
|
///
|
|
/// Un INSTANT se rend toujours chez le lecteur ; c'est un **réglage** comme
|
|
/// `needs.dailyDeadline` qui se rend dans l'horloge de la box, et qui doit alors la nommer.
|
|
DateTime? instantDeLaBox(Object? v) {
|
|
if (v is num) {
|
|
return DateTime.fromMillisecondsSinceEpoch(v.toInt() * 1000, isUtc: true)
|
|
.toLocal();
|
|
}
|
|
if (v is! String || v.isEmpty) return null;
|
|
final d = DateTime.tryParse(v);
|
|
if (d == null) return null;
|
|
// `isUtc == false` ⇒ la chaîne ne portait pas de marqueur. On la requalifie en UTC au
|
|
// lieu de laisser Dart la tenir pour locale : voir la politique en tête de fichier.
|
|
final utc = d.isUtc
|
|
? d
|
|
: DateTime.utc(d.year, d.month, d.day, d.hour, d.minute, d.second,
|
|
d.millisecond, d.microsecond);
|
|
return utc.toLocal();
|
|
}
|