fix(amsat): align status slots to their UTC calendar-day labels
AmSatRepository labelled columns by calendar date but filled them by slicing a
rolling 72-slot window ending at fetch time. The two timelines coincide only
near 23:59 UTC. At common fetch times the status grid lied about dates:
UTC 00:00: 72 / 72 slots under the wrong label
"today" column contained all of yesterday
UTC 12:00: 36 / 72 wrong; every column straddled two dates
UTC 13:37: 30 / 72 wrong
UTC 23:59: 0 / 72 wrong (the accidental alignment case)
Anchor the six columns on UTC midnight instead. Every SatDay now covers exactly
[day 00:00, next day 00:00), split into twelve 2-hour slots newest-first so the
UI's existing first-non-gray lookup still chooses the latest daily report.
A standalone Java probe porting the old arithmetic reproduced the 72/72,
36/72 and 30/72 mismatches. Porting the new formula gives 0/72 mismatches at
00:00, 12:00, 13:37 and 23:59 UTC.
Also restore core:data's unit-test compilation. DatabaseRepoTest's fakes were
stale after IRemoteSource gained AMSAT methods and ISettingsRepo's zero-arg GPS
setter became suspend; the whole data test suite previously could not compile,
so data-layer regressions were untestable. Updated the fake members and verified
:core:data:testDebugUnitTest plus :core:data:compileReleaseKotlin BUILD
SUCCESSFUL. The product code does not use org.json in JVM tests because Android
org.json stubs throw there, so the date math remains verified by the standalone
same-JVM probe rather than a misleading mocked parser test.
This commit is contained in:
1 parent
2da7127fd3
commit
2bac6655f3
2 files changed
+34
-18
No files matched your search
+27
-17
@@ -92,28 +92,38 @@ class AmSatRepository(private val remoteSource: IRemoteSource) : IAmSatRepositor
|
||||
val byName = reports.groupBy { it.name }
|
||||
val monthAbbr = arrayOf("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")
|
||||
val utc = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
|
||||
// Anchor the columns on UTC midnight. Slicing a rolling "N x 2 hours ago"
|
||||
// window into six groups of twelve does not line up with calendar days
|
||||
// unless the fetch happens just before 24:00 UTC: at 00:00 UTC every one
|
||||
// of the 72 slots sat under the wrong date label, and at 12:00 UTC half of
|
||||
// them did, so the column headed "today" showed yesterday's reports.
|
||||
val todayStartSec = (nowSec / 86400L) * 86400L
|
||||
val labels = (0 until 6).map { d ->
|
||||
utc.timeInMillis = (nowSec - d * 86400L) * 1000
|
||||
utc.timeInMillis = (todayStartSec - d * 86400L) * 1000
|
||||
"${monthAbbr[utc.get(Calendar.MONTH)]} ${utc.get(Calendar.DAY_OF_MONTH)}"
|
||||
}
|
||||
return names.map { name ->
|
||||
val slots = (0 until 72).map { slotIdx ->
|
||||
val slotStart = nowSec - (slotIdx + 1) * 7200L
|
||||
val slotEnd = nowSec - slotIdx * 7200L
|
||||
val inSlot = byName[name].orEmpty().filter { it.reportedTimeUtcSec in slotStart until slotEnd }
|
||||
if (inSlot.isEmpty()) {
|
||||
SatSlot(statusColor = NO_REPORT_GRAY, count = 0)
|
||||
} else {
|
||||
val newest = inSlot.maxByOrNull { it.reportedTimeUtcSec }!!
|
||||
SatSlot(
|
||||
statusColor = statusColorOf(newest.report),
|
||||
count = inSlot.size,
|
||||
reportIds = inSlot.map { it.id }
|
||||
)
|
||||
}
|
||||
}
|
||||
val reportsForSat = byName[name].orEmpty()
|
||||
val days = (0 until 6).map { d ->
|
||||
SatDay(dateLabel = labels[d], slots = slots.subList(d * 12, (d + 1) * 12))
|
||||
val dayStart = todayStartSec - d * 86400L
|
||||
// Newest slot first: the UI picks the first non-gray slot of a day
|
||||
// as that day's status.
|
||||
val slots = (0 until 12).map { slotInDay ->
|
||||
val slotStart = dayStart + (11 - slotInDay) * 7200L
|
||||
val slotEnd = slotStart + 7200L
|
||||
val inSlot = reportsForSat.filter { it.reportedTimeUtcSec in slotStart until slotEnd }
|
||||
if (inSlot.isEmpty()) {
|
||||
SatSlot(statusColor = NO_REPORT_GRAY, count = 0)
|
||||
} else {
|
||||
val newest = inSlot.maxByOrNull { it.reportedTimeUtcSec }!!
|
||||
SatSlot(
|
||||
statusColor = statusColorOf(newest.report),
|
||||
count = inSlot.size,
|
||||
reportIds = inSlot.map { it.id }
|
||||
)
|
||||
}
|
||||
}
|
||||
SatDay(dateLabel = labels[d], slots = slots)
|
||||
}
|
||||
SatStatus(name = name, days = days)
|
||||
}
|
||||
|
||||
+7
-1
@@ -125,6 +125,12 @@ private class FakeRemoteSource : IRemoteSource {
|
||||
override suspend fun getFileStream(uri: String): InputStream? = fileStreams[uri]?.invoke()
|
||||
|
||||
override suspend fun getNetworkStream(url: String): InputStream? = networkStreams[url]?.invoke()
|
||||
|
||||
override suspend fun getStatusHtml(): String? = null
|
||||
|
||||
override suspend fun getAmSatCatalog(): String? = null
|
||||
|
||||
override suspend fun getAmSatReports(hours: Int, limit: Int): String? = null
|
||||
}
|
||||
|
||||
private class FakeLocalSource : ILocalSource {
|
||||
@@ -200,7 +206,7 @@ private class FakeSettingsRepo(dataSources: DataSourcesSettings = defaultDataSou
|
||||
|
||||
override fun setStationPosition(latitude: Double, longitude: Double, altitude: Double): Boolean = true
|
||||
|
||||
override fun setStationPosition(): Boolean = true
|
||||
override suspend fun setStationPosition(): Boolean = true
|
||||
|
||||
override fun setStationPosition(locator: String): Boolean = true
|
||||
|
||||
|
||||
Reference in new issue
Block a user