fix(worldclock): name offset-set zones by their real offset everywhere

The Etc/GMT-n naming fix only covered the local row. A home that
follows a device set by UTC offset still put "GMT-9" under a UTC+9
face. The rule now lives in the one place every row and the face take
their city from.
This commit is contained in:
makiolaj committed 2026-10-22 07:38:49 +02:00
1 parent f46b7e1e37
commit 40b4c9e2e4
3 files changed
+30 -24

No files matched your search

@@ -1,6 +1,7 @@
package de.jeanlucmakiola.clockula.ui.worldclock
import de.jeanlucmakiola.clockula.domain.WorldClock
import de.jeanlucmakiola.clockula.domain.Zones
import de.jeanlucmakiola.clockula.domain.isKnownZone
import de.jeanlucmakiola.clockula.domain.worldclock.DayLabel
import de.jeanlucmakiola.clockula.domain.worldclock.DayNight
@@ -88,7 +89,7 @@ internal object WorldClockRows {
WorldClockRowState(
id = clock.id,
zoneId = clock.zoneId,
name = clock.label ?: cityOf(clock.zoneId, entries),
name = clock.label ?: cityOf(clock.zoneId, entries, now),
time = comparison?.localDateTime?.toLocalTime()?.truncatedTo(ChronoUnit.MINUTES),
offset = comparison?.let { ZoneOffsetFormat.labelFor(it.offsetMinutes) },
day = comparison?.let { ZoneOffsetFormat.dayLabelFor(it.dayDifference) },
@@ -101,10 +102,6 @@ internal object WorldClockRows {
* The automatic local clock's row for [deviceZone], read against home like
* any other row. Its [WorldClockRowState.id] is [LOCAL_ROW_ID], which no
* stored row can have.
*
* A device set by UTC offset rather than by city (Android 13+ offers it)
* reports an id like `Etc/GMT-9`, whose last segment is no city and has
* its sign reversed, so such a row is named by its offset ("GMT+9").
*/
fun local(
deviceZone: ZoneId,
@@ -112,20 +109,13 @@ internal object WorldClockRows {
now: Instant,
entries: Map<String, ZoneEntry>,
zoneNames: Map<String, String?>,
): WorldClockRowState {
val name = if (ZoneCatalog.hasPlaceName(deviceZone.id)) {
null
} else {
ZoneOffsetFormat.gmtLabel(ZoneComparisons.utcOffsetMinutes(deviceZone, now))
}
return from(
clocks = listOf(WorldClock(id = LOCAL_ROW_ID, zoneId = deviceZone.id, label = name, sortOrder = 0)),
homeZone = homeZone,
now = now,
entries = entries,
zoneNames = zoneNames,
).single()
}
): WorldClockRowState = from(
clocks = listOf(WorldClock(id = LOCAL_ROW_ID, zoneId = deviceZone.id, label = null, sortOrder = 0)),
homeZone = homeZone,
now = now,
entries = entries,
zoneNames = zoneNames,
).single()
/** Room ids start at 1, so a row with this id is never a stored one. */
const val LOCAL_ROW_ID: Long = -1L
@@ -143,13 +133,22 @@ internal object WorldClockRows {
val local = now.toJavaInstant().atZone(homeZone).toLocalTime().truncatedTo(ChronoUnit.SECONDS)
return HomeFaceState(
zoneId = homeZone.id,
city = cityOf(homeZone.id, entries),
city = cityOf(homeZone.id, entries, now),
zoneName = zoneNames[homeZone.id],
time = local,
dayFraction = DayNight.fractionAt(local),
)
}
private fun cityOf(zoneId: String, entries: Map<String, ZoneEntry>): String =
entries[zoneId]?.city ?: ZoneCatalog.cityFallbackFor(zoneId)
/**
* A device set by UTC offset rather than by city (Android 13+ offers it)
* reports an id like `Etc/GMT-9`, whose last segment is no place and has
* its sign reversed, so such a zone is named by its real offset ("GMT+9").
*/
private fun cityOf(zoneId: String, entries: Map<String, ZoneEntry>, now: Instant): String =
if (!ZoneCatalog.hasPlaceName(zoneId) && Zones.isValid(zoneId)) {
ZoneOffsetFormat.gmtLabel(ZoneComparisons.utcOffsetMinutes(ZoneId.of(zoneId), now))
} else {
entries[zoneId]?.city ?: ZoneCatalog.cityFallbackFor(zoneId)
}
}
@@ -265,4 +265,11 @@ class WorldClockRowsTest {
assertThat(local.name).isEqualTo("GMT+9")
assertThat(local.offset).isEqualTo(OffsetLabel.Ahead(8, 0))
}
@Test
fun `a home that follows a device set by UTC offset is named by its real offset`() {
val home = WorldClockRows.home(ZoneId.of("Etc/GMT-9"), T0, entries, emptyMap())
assertThat(home.city).isEqualTo("GMT+9")
}
}
+2 -2
View File
@@ -2406,8 +2406,8 @@ that city already shows the local time. Home that follows the device can never
differ from it, so with no stored home the rule is always null, and Settings ›
Clock says so under the switch rather than hiding it. A device set by UTC
offset rather than by city reports an id like `Etc/GMT-9`, whose last segment
has its sign reversed, so that row is named by its real offset ("GMT+9")
instead (`ZoneCatalog.hasPlaceName`).
has its sign reversed, so that zone is named by its real offset ("GMT+9")
instead, on the local row and on the face alike (`ZoneCatalog.hasPlaceName`).
The row is not stored. It is `WorldClockUiState.Loaded.local`, built per tick
by the source from the same `zones.current()` read the face uses, so a