Fix a real UTC-vs-local timezone mismatch in open-data station timestamps

Found while wiring up real FTP credentials: the open-data portal's
DATETIME field is UTC, but was being stored into weather.dateTime
as-is with no conversion — while the private FTP feed's "Laiks"
column is already Latvia local time and also stored as-is. Both
paths write the same column, so the table has been silently holding
two timelines 2-3h apart since open-data went live, invisible only
because FTP was never actually running with real data until today.
Verified live: at real local time 20:31 EEST, the fix now correctly
produces dateTime=20:00 instead of 17:00, matching what FTP writes
for the same real hour.
This commit is contained in:
b0txec
2026-08-23 21:26:08 +03:00
parent 2b5dff6255
commit cab94d097c
@@ -11,7 +11,7 @@ import org.typelevel.log4cats.Logger
import org.typelevel.log4cats.slf4j.Slf4jLogger import org.typelevel.log4cats.slf4j.Slf4jLogger
import parse.csv.{WeatherData, WeatherStationData} import parse.csv.{WeatherData, WeatherStationData}
import java.time.LocalDateTime import java.time.{LocalDateTime, ZoneId, ZoneOffset}
import scala.util.Try import scala.util.Try
object OpenDataStationService { object OpenDataStationService {
@@ -27,6 +27,14 @@ object OpenDataStationService {
final class OpenDataStationService private (logger: Logger[IO]) { final class OpenDataStationService private (logger: Logger[IO]) {
private val pageSize = 5000 private val pageSize = 5000
// The open-data portal's DATETIME field is UTC (verified live against real
// clock time 2026-08-23); the private FTP feed's "Laiks" column is already
// Latvia local time with no conversion needed. Both write into the same
// weather.dateTime column, so this path converts to match — otherwise the
// two sources silently occupy two different, 2-3h-offset timelines in the
// same table instead of one.
private val rigaZone = ZoneId.of("Europe/Riga")
private val observationsUrl = sys.env.getOrElse( private val observationsUrl = sys.env.getOrElse(
"LVGMC_OPENDATA_OBSERVATIONS_URL", "LVGMC_OPENDATA_OBSERVATIONS_URL",
"https://data.gov.lv/dati/api/3/action/datastore_search?resource_id=17460efb-ae99-4d1d-8144-1068f184b05f" "https://data.gov.lv/dati/api/3/action/datastore_search?resource_id=17460efb-ae99-4d1d-8144-1068f184b05f"
@@ -113,7 +121,7 @@ final class OpenDataStationService private (logger: Logger[IO]) {
city <- stationCities.get(stationId) city <- stationCities.get(stationId)
abbreviation <- cursor.downField("ABBREVIATION").as[String].toOption abbreviation <- cursor.downField("ABBREVIATION").as[String].toOption
datetimeStr <- cursor.downField("DATETIME").as[String].toOption datetimeStr <- cursor.downField("DATETIME").as[String].toOption
timestamp <- Try(LocalDateTime.parse(datetimeStr)).toOption timestamp <- Try(LocalDateTime.parse(datetimeStr).atZone(ZoneOffset.UTC).withZoneSameInstant(rigaZone).toLocalDateTime).toOption
value <- cursor.downField("VALUE").as[Double].toOption value <- cursor.downField("VALUE").as[Double].toOption
} yield ObservationCell(city, timestamp, abbreviation, value) } yield ObservationCell(city, timestamp, abbreviation, value)
} }