Let the timezone picker be searched by country

Finding a zone meant already knowing its name, which is the wrong way round
when the thing you know is the country -- Ghana's zone is named after Accra,
and nothing in the picker said so.

Zones now carry the countries they cover, drawn from tzdata's own tables:
zone1970.tab and zone.tab for the zone-to-country-code rows, iso3166.tab for
the names. The picker already matched on description as well as label, so
putting the countries there makes them searchable and shows them in the list.

Those tables only name about 420 of the ~600 zones; the rest are
backward-compatibility aliases like America/Buenos_Aires and US/Pacific, which
tzdata ships as hard links to their canonical zone. Matching an alias to its
canonical name by inode lifts coverage to 553, and the 45 left are the Etc/GMT
pseudo-zones, which have no country by definition.

The inherited countries are the union of every zone sharing the inode, not the
first one found: those siblings are one clock under several countries' names,
and each table names a different subset, so first-match made Africa/Asmera
findable as Ethiopia but not as Eritrea.

Joining is done in ClockModel against a fixture, so it does not depend on the
host's tzdata version; the shell side only reads the files.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MhxtL7WzL5XmuTd8GHbcTY
This commit is contained in:
Harvey
2026-09-04 20:13:11 +02:00
co-authored by Claude Opus 5
parent 3d8bb1b1b3
commit 460217d059
5 changed files with 192 additions and 18 deletions
+22 -14
View File
@@ -20,6 +20,8 @@ QtObject {
// rendered as unknown rather than being silently shown as UTC.
property var offsets: ({})
property string localOffset: ""
// [{ zone, countries }] for the picker: the zone name plus the countries it
// covers, so it can be found by country as well as by name.
property var allZones: []
property bool ready: false
@@ -75,23 +77,29 @@ QtObject {
}
}
// Emits tzdata's own country tables alongside the zone list, tagged into
// sections. The shell only reads files; the joining is done in ClockModel so
// it can be tested. `right/` and `posix/` are pruned because they duplicate
// every zone under a different inode, which would double the output and
// break the alias-by-inode match.
readonly property string catalogScript:
'printf "@ISO\n"\n' +
'cat /usr/share/zoneinfo/iso3166.tab 2>/dev/null\n' +
'printf "@ZONE\n"\n' +
'cat /usr/share/zoneinfo/zone1970.tab /usr/share/zoneinfo/zone.tab 2>/dev/null\n' +
'printf "@INODE\n"\n' +
'find /usr/share/zoneinfo -type f -not -path "*/right/*" -not -path "*/posix/*" ' +
'-printf "%i\\t%P\\n" 2>/dev/null\n' +
'printf "@LIST\n"\n' +
'timedatectl list-timezones 2>/dev/null || ' +
'find /usr/share/zoneinfo -type f -not -path "*/right/*" -not -path "*/posix/*" ' +
'-printf "%P\\n" 2>/dev/null | grep -E "^[A-Z][A-Za-z_+-]*/" | sort'
property Process zoneListProc: Process {
command: ["sh", "-c",
// timedatectl is the canonical list; the zoneinfo walk is the fallback
// for a system where systemd is not answering.
"timedatectl list-timezones 2>/dev/null || " +
"find /usr/share/zoneinfo -type f -printf '%P\\n' 2>/dev/null | grep -E '^[A-Z][A-Za-z_+-]*/' | sort"]
command: ["sh", "-c", root.catalogScript]
stdout: StdioCollector {
waitForEnd: true
onStreamFinished: {
var out = []
var lines = String(text || "").split("\n")
for (var i = 0; i < lines.length; i++) {
var line = lines[i].replace(/^\s+|\s+$/g, "")
if (line) out.push(line)
}
root.allZones = out
}
onStreamFinished: root.allZones = Model.parseZoneCatalog(text)
}
}