From a0b4cb2b1425fab86b297bca288ebde74baf9a33 Mon Sep 17 00:00:00 2001 From: Maksim Totmin Date: Fri, 9 Oct 2026 22:07:33 +0700 Subject: [PATCH] fix(outages): format numeric portal dates as integers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Portal date fields arrive as numbers; fmt's %v rendered them in scientific notation, so the "current outage" filter could not parse them and kept past outages (a 'Прошлые' record leaked through). Format integer-valued attributes explicitly. --- internal/adapters/arcgis/client.go | 18 +++++++++++++----- internal/adapters/arcgis/client_test.go | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/internal/adapters/arcgis/client.go b/internal/adapters/arcgis/client.go index d4a2b9d..4c076be 100644 --- a/internal/adapters/arcgis/client.go +++ b/internal/adapters/arcgis/client.go @@ -191,14 +191,22 @@ func codeKey(v any) string { } } +// stringOf renders an attribute value as a string. The important case is +// numeric dates: fmt's %v turns a float64 into scientific notation ("1.79e+12") +// which the date parser cannot read, so integers are formatted explicitly. func stringOf(v any) string { - if s, ok := v.(string); ok { - return s - } - if v == nil { + switch t := v.(type) { + case nil: return "" + case string: + return t + case float64: + return strconv.FormatInt(int64(t), 10) + case json.Number: + return t.String() + default: + return fmt.Sprintf("%v", v) } - return fmt.Sprintf("%v", v) } // actionFor maps a portal system name to the canonical action code used by the diff --git a/internal/adapters/arcgis/client_test.go b/internal/adapters/arcgis/client_test.go index f243db3..fca72e9 100644 --- a/internal/adapters/arcgis/client_test.go +++ b/internal/adapters/arcgis/client_test.go @@ -94,6 +94,24 @@ func TestQueryStripsDistrictSuffix(t *testing.T) { } } +// TestQueryFormatsNumericDates guards against scientific notation in dates, +// which broke the "current outage" filter. +func TestQueryFormatsNumericDates(t *testing.T) { + const env = `{"features":[{"value":"Ленина улица, 1","attributes":{"address":"Ленина улица, 1","start_date":1790820000000,"end_date":1791799200000}}]}` + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte(env)) + })) + defer ts.Close() + + rows, err := New(ts.URL, time.Second, 0).Query(context.Background(), "Ленина улица 1") + if err != nil { + t.Fatalf("Query: %v", err) + } + if rows[0].StartDate != "1790820000000" || rows[0].EndDate != "1791799200000" { + t.Errorf("numeric dates must be integer-formatted: %q..%q", rows[0].StartDate, rows[0].EndDate) + } +} + // TestLikePattern guards the ordinal/house padding rule: padding an ordinal // that starts the string breaks the portal match. func TestLikePattern(t *testing.T) {