From 3315f00fb1e9c7a827ea8c1e090c267f76f7e47b Mon Sep 17 00:00:00 2001 From: b0txec Date: Sun, 23 Aug 2026 21:26:14 +0300 Subject: [PATCH] =?UTF-8?q?Fix=20the=20same=20UTC-vs-local=20mismatch=20in?= =?UTF-8?q?=20=C5=AAdens's=20observedAt=20display?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same root cause as the station-observation fix (this uses the same open-data portal, same UTC DATETIME field): the raw UTC string was passed straight through to the frontend as a display label, which JS's Date parser then reads as local time for a string with no timezone suffix — silently showing observation times 2-3h behind the newsroom's actual clock. Internal recency filtering (isRecent) was already self-consistent either way; this only affects display. --- .../fetch/lvgmc/WaterTemperatureService.scala | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/scala/fetch/lvgmc/WaterTemperatureService.scala b/src/main/scala/fetch/lvgmc/WaterTemperatureService.scala index 505c218..d83ca48 100644 --- a/src/main/scala/fetch/lvgmc/WaterTemperatureService.scala +++ b/src/main/scala/fetch/lvgmc/WaterTemperatureService.scala @@ -9,7 +9,7 @@ import org.http4s.ember.client.EmberClientBuilder import org.typelevel.log4cats.Logger import org.typelevel.log4cats.slf4j.Slf4jLogger -import java.time.{Duration, Instant, LocalDateTime} +import java.time.{Duration, Instant, LocalDateTime, ZoneId, ZoneOffset} import scala.util.Try final case class WaterTemperatureReading(area: String, observedAt: String, min: Option[Double], max: Option[Double]) @@ -43,6 +43,15 @@ final class WaterTemperatureService private (logger: Logger[IO], cache: Ref[IO, private val areaOrder: Vector[String] = Vector("Jūra", "Līcis", "Kurzeme", "Zemgale", "Vidzeme", "Latgale") + // This dataset's DATETIME field is UTC (same source/portal as the station + // observations feed, same verified offset); converted to local so the + // observedAt label shown on Ūdens matches the newsroom's own clock instead + // of reading 2-3h behind. + private val rigaZone = ZoneId.of("Europe/Riga") + + private def toLocal(utcDatetime: String): Option[String] = + Try(LocalDateTime.parse(utcDatetime).atZone(ZoneOffset.UTC).withZoneSameInstant(rigaZone).toLocalDateTime.toString).toOption + // Every LVĢMC hydrological station that reports water temperature (WTEMD // inland/river stations, SEDUT coastal stations), grouped into the six // named Ūdens zones. Boundaries follow Latvia's historical regions @@ -177,7 +186,8 @@ final class WaterTemperatureService private (logger: Logger[IO], cache: Ref[IO, val cursor = row.hcursor for { stationId <- cursor.downField("STATION_ID").as[String].toOption - datetime <- cursor.downField("DATETIME").as[String].toOption + rawDatetime <- cursor.downField("DATETIME").as[String].toOption + datetime <- toLocal(rawDatetime) value <- cursor.downField("VALUE").as[Double].toOption } yield StationReading(stationId, datetime, value) }