From d1fa49ee3551220716d4811148a440c7c465a7fc Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Sun, 19 Apr 2026 18:45:25 +0800 Subject: [PATCH] Fix city-local nearby station time labels --- frontend/hooks/useLeafletMap.ts | 3 ++- frontend/lib/dashboard-types.ts | 1 + src/data_collection/country_networks.py | 4 +++- tests/test_country_networks.py | 26 +++++++++++++++++++++++++ 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/frontend/hooks/useLeafletMap.ts b/frontend/hooks/useLeafletMap.ts index e54e6d00..dca18654 100644 --- a/frontend/hooks/useLeafletMap.ts +++ b/frontend/hooks/useLeafletMap.ts @@ -216,6 +216,8 @@ function buildNearbyIconHtml(detail: CityDetail, station: NearbyStation) { const utcOffsetSeconds = getDetailUtcOffsetSeconds(detail); const formatObsTime = () => { const raw = String(station.obs_time || "").trim(); + const label = String(station.obs_time_label || "").trim(); + if (station.obs_time_display_tz === "city_local" && label) return label.replace(/Z$/i, ""); const epochRaw = Number(station.obs_time_epoch); const epochMs = Number.isFinite(epochRaw) ? epochRaw > 1_000_000_000_000 @@ -226,7 +228,6 @@ function buildNearbyIconHtml(detail: CityDetail, station: NearbyStation) { const localOffsetSeconds = utcOffsetSeconds; return formatCityLocalDateTime(epochMs, localOffsetSeconds, detail.local_date, isEnglishUi); } - const label = String(station.obs_time_label || "").trim(); if (label) return label.replace(/Z$/i, ""); if (!raw) return ""; if (raw.endsWith("Z") || raw.includes("+00:00")) { diff --git a/frontend/lib/dashboard-types.ts b/frontend/lib/dashboard-types.ts index efae154a..668d2972 100644 --- a/frontend/lib/dashboard-types.ts +++ b/frontend/lib/dashboard-types.ts @@ -121,6 +121,7 @@ export interface NearbyStation { obs_time?: string | null; obs_time_epoch?: number | string | null; obs_time_label?: string | null; + obs_time_display_tz?: "city_local" | string | null; age_minutes?: number | null; time_delta_vs_anchor_minutes?: number | null; sync_status?: "synced" | "near_realtime" | "lagged" | "stale" | "unknown" | string | null; diff --git a/src/data_collection/country_networks.py b/src/data_collection/country_networks.py index bfde33a2..bc2c5c81 100644 --- a/src/data_collection/country_networks.py +++ b/src/data_collection/country_networks.py @@ -76,9 +76,10 @@ def _format_obs_time_label( value: Any, epoch_value: Any = None, display_utc_offset_seconds: Any = None, + source_utc_offset_seconds: Any = None, ) -> Optional[str]: text = str(value or "").strip() - parsed = _parse_obs_datetime(value, epoch_value) + parsed = _parse_obs_datetime(value, epoch_value, source_utc_offset_seconds) if parsed is not None: if display_utc_offset_seconds is not None and parsed.tzinfo is not None: try: @@ -160,6 +161,7 @@ def _enrich_station_timing( row.get("obs_time"), row.get("obs_time_epoch"), display_utc_offset_seconds, + row.get("obs_time_utc_offset_seconds"), ) if display_utc_offset_seconds is not None and enriched_row.get("obs_time_label"): enriched_row["obs_time_display_tz"] = "city_local" diff --git a/tests/test_country_networks.py b/tests/test_country_networks.py index d788f242..1192088f 100644 --- a/tests/test_country_networks.py +++ b/tests/test_country_networks.py @@ -215,6 +215,32 @@ def test_metar_cluster_obs_time_label_uses_dst_aware_city_time(): assert row["obs_time_label"] == "07:30" +def test_metar_cluster_naive_obs_time_is_interpreted_as_utc_before_city_display(): + raw = { + "metar": { + "observation_time": "2026-04-19 08:00", + "current": {"temp": 4.0}, + }, + "mgm_nearby": [ + { + "name": "Moscow/Vnukovo", + "icao": "UUWW", + "lat": 55.59, + "lon": 37.26, + "temp": 4.0, + "obs_time": "2026-04-19 08:00", + } + ], + } + + snapshot = build_country_network_snapshot("moscow", raw) + row = snapshot["official_nearby"][0] + + assert row["source_code"] == "metar_cluster" + assert row["obs_time_label"] == "11:00" + assert row["obs_time_display_tz"] == "city_local" + + def test_china_provider_prefers_nmc_rows_when_available(): raw = { "metar": {