[ADD] tui qemu: pick the time zone from a list

The timezone was typed by hand. A misspelt IANA name is not rejected by
cloud-init: it is IGNORED. The VM stays on UTC, and you only notice from
the timestamps, once deployed.

A list of twenty-five zones, Québec first, then the rest of Canada and
the places one actually meets. The host's zone goes to the top, without
duplication: a machine outside this list must still see its own at a
glance.

NAMES, not offsets: "UTC-5" says nothing about daylight saving and
cloud-init will not take it. A name carries its own switching rules.

"free value…" keeps the door open to the other six hundred zones in the
database. The choice is copied into the field, which stays the only
value the spec reads -- one place holds the answer.

--- FR ---

Le fuseau se tapait à la main. Un nom IANA mal orthographié n'est pas
refusé par cloud-init : il est IGNORÉ. La VM reste en UTC, et on ne s'en
aperçoit qu'aux horodatages, une fois déployée.

Une liste de vingt-cinq fuseaux, le Québec d'abord, puis le reste du
Canada et les places qu'on rencontre en pratique. Le fuseau de l'hôte
passe en tête, sans doublon : une machine hors de cette liste doit voir
le sien en un coup d'œil.

Des NOMS, pas des décalages : « UTC-5 » ne dit rien de l'heure d'été et
cloud-init n'en veut pas. Un nom porte ses propres règles de bascule.

« libre… » garde la porte ouverte aux six cents autres fuseaux de la
base. Le choix est recopié dans le champ, qui reste la seule valeur lue
par la spec — un seul endroit porte la réponse.

Assisted-by: Claude Opus 5
This commit is contained in:
Mathieu Benoit 2026-08-13 01:56:36 -04:00
parent 8a946a0b89
commit c6c860254c
2 changed files with 77 additions and 0 deletions

View file

@ -370,6 +370,8 @@ def run_deploy_form(ctx, run_app: bool = True):
# livrent snapd — la question n'a de sens que pour celles-là, graphiques.
app_stores = list(ctx.get("app_stores") or [])
snap_distros = set(ctx.get("snap_distros") or ())
# Fuseaux proposés, celui de l'hôte en tête (voir todo.py).
timezones = list(ctx.get("timezones") or [])
defaults = ctx.get("defaults") or {}
result = {"spec": None}
@ -683,10 +685,23 @@ def run_deploy_form(ctx, run_app: bool = True):
id="f_monitor",
)
yield Static(t("Timezone"), classes="grouptitle")
# Une liste plutôt qu'une saisie : un nom IANA mal
# orthographié n'est pas refusé par cloud-init, il est
# IGNORÉ — la VM reste en UTC et on ne s'en aperçoit
# qu'aux horodatages. « libre… » garde la porte ouverte
# aux six cents autres fuseaux de la base.
yield Select(
[(z, z) for z in timezones]
+ [(t("free value…"), FREE)],
value=(timezones[0] if timezones else SELECT_NULL),
allow_blank=False,
id="f_tz_sel",
)
yield Input(
value=ctx.get("timezone") or "",
placeholder=t("Timezone for the VMs"),
id="f_tz",
classes="freeval",
)
yield Static("SSH", classes="grouptitle")
yield Input(
@ -1452,6 +1467,19 @@ def run_deploy_form(ctx, run_app: bool = True):
# ci-dessous sans rien recalculer, et les rangées restaient sur
# l'ancienne version. Elles n'en gardent pas de copie — « » y
# veut dire « celle du formulaire » — il suffit de redessiner.
if event.select.id == "f_tz_sel":
# « libre… » révèle la saisie ; un fuseau choisi la referme et
# y recopie le nom, seule valeur que lisent _form_values et la
# spec — un seul endroit porte la réponse.
free = event.value is FREE
field = self.query_one("#f_tz", Input)
field.display = free
field.disabled = not free
if free:
field.focus()
elif isinstance(event.value, str):
field.value = event.value
return
if event.select.id in ("f_branch", "f_profile_install"):
self._clear_overrides(
("branch",)

View file

@ -4413,6 +4413,52 @@ class TODO:
)
QEMU_SNAP_DISTROS = ("ubuntu",)
# Fuseaux proposés au déploiement. Des NOMS IANA, pas des décalages :
# cloud-init écrit /etc/timezone et refuse « UTC-5 », qui ne dit d'ailleurs
# rien de l'heure d'été. Un nom porte ses propres règles de bascule.
#
# Liste courte et ordonnée par usage réel plutôt qu'exhaustive : la base
# IANA en compte près de six cents, illisibles dans une liste déroulante.
# Le Québec d'abord, le reste du Canada ensuite, puis les places qu'on
# rencontre en pratique. La saisie libre reste offerte pour le reste.
QEMU_TIMEZONES = (
"America/Montreal",
"America/Toronto",
"America/Halifax",
"America/Winnipeg",
"America/Edmonton",
"America/Vancouver",
"America/St_Johns",
"UTC",
"America/New_York",
"America/Chicago",
"America/Denver",
"America/Los_Angeles",
"America/Sao_Paulo",
"Europe/London",
"Europe/Paris",
"Europe/Brussels",
"Europe/Zurich",
"Europe/Madrid",
"Europe/Berlin",
"Africa/Casablanca",
"Asia/Dubai",
"Asia/Kolkata",
"Asia/Shanghai",
"Asia/Tokyo",
"Australia/Sydney",
)
@classmethod
def _qemu_timezone_choices(cls, current=""):
"""Liste à proposer : le fuseau de l'hôte en tête, sans doublon.
Le mettre en premier plutôt que de le supposer présent : une machine
hors de cette liste doit quand même voir le sien en un coup d'œil."""
out = [current] if current else []
out += [z for z in cls.QEMU_TIMEZONES if z != current]
return out
@classmethod
def _qemu_desktop_suffixes(cls):
"""{clé de saveur: suffixe de nom}. La TUI le reçoit par son contexte
@ -5762,6 +5808,9 @@ class TODO:
"desktop_disk_gb": self.QEMU_DESKTOP_EXTRA_DISK_GB,
"mise_arches": self.QEMU_MISE_ARCHES,
"app_stores": [(k, t(lbl)) for k, lbl in self.QEMU_APP_STORES],
"timezones": self._qemu_timezone_choices(
self._qemu_host_timezone()
),
"snap_distros": self.QEMU_SNAP_DISTROS,
"desktop_suffixes": self._qemu_desktop_suffixes(),
"desktops": [