Compare commits
2 Commits
208f762c18
...
092dff25a5
| Author | SHA1 | Date | |
|---|---|---|---|
| 092dff25a5 | |||
| 56c21bcf03 |
@@ -1,5 +1,10 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-05-07 23:37 CEST — fix(go): accept single-digit day/month in attendance date headers
|
||||||
|
|
||||||
|
- `go/internal/services/membership/sources.go`: `parseDates` now uses Go time formats `2.1.2006` and `1/2/2006` (single-digit reference forms, which accept both padded and unpadded inputs) instead of `02.01.2006` and `01/02/2006`. The Czech attendance sheet headers contain dates like `1.6.2026`, `23.3.2026`, `6.4.2026` — Go silently dropped those columns under the strict zero-padded format, while Python's `strptime("%d.%m.%Y")` accepted them. Effect was a missing `2026-06` month entirely on `/api/juniors` plus undercounted attendance for any month with single-digit columns; both surfaced as diffs in `make parity`.
|
||||||
|
- `sources_test.go::TestParseDates_SingleDigitDayMonth` added as a regression guard covering both Czech and US format flavours with and without leading zeros.
|
||||||
|
|
||||||
## 2026-05-07 23:17 CEST — fix(go): pass raw value to FormatDate so numeric serial-day dates format
|
## 2026-05-07 23:17 CEST — fix(go): pass raw value to FormatDate so numeric serial-day dates format
|
||||||
|
|
||||||
- `go/internal/services/membership/sources.go`: transaction-row parser now passes `row[idxDate]` directly to `matching.FormatDate` (via a new `getRaw` helper) instead of stringifying first via `getVal`. The Sheets API returns numeric serial-day values as `float64` for date-formatted cells; pre-stringifying them defeated `FormatDate`'s `case float64:` dispatch, causing all numeric dates to leak through as `"46147"` style strings instead of `"2026-05-05"`.
|
- `go/internal/services/membership/sources.go`: transaction-row parser now passes `row[idxDate]` directly to `matching.FormatDate` (via a new `getRaw` helper) instead of stringifying first via `getVal`. The Sheets API returns numeric serial-day values as `float64` for date-formatted cells; pre-stringifying them defeated `FormatDate`'s `case float64:` dispatch, causing all numeric dates to leak through as `"46147"` style strings instead of `"2026-05-05"`.
|
||||||
|
|||||||
@@ -142,7 +142,13 @@ func parseDates(header []string) []struct {
|
|||||||
}
|
}
|
||||||
var dt time.Time
|
var dt time.Time
|
||||||
var err error
|
var err error
|
||||||
for _, fmt_ := range []string{"02.01.2006", "01/02/2006"} {
|
// Use the unpadded reference forms ("2.1" and "1/2"): Go's time.Parse
|
||||||
|
// accepts both single-digit and zero-padded inputs against them, so
|
||||||
|
// "1.6.2026", "01.06.2026", "23.3.2026" all parse. Czech sheet authors
|
||||||
|
// drop the leading zero on dates ≤ 9 — Python's strptime is lenient
|
||||||
|
// the same way; the previous "02.01.2006" form silently dropped those
|
||||||
|
// columns and undercounted attendance.
|
||||||
|
for _, fmt_ := range []string{"2.1.2006", "1/2/2006"} {
|
||||||
dt, err = time.Parse(fmt_, raw)
|
dt, err = time.Parse(fmt_, raw)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -174,6 +174,28 @@ func TestLoadExceptions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestParseDates_SingleDigitDayMonth covers the regression where Go's strict
|
||||||
|
// "02.01.2006" format dropped header cells written without leading zeros
|
||||||
|
// (e.g. "1.6.2026", "23.3.2026"), causing attendance undercounts and missing
|
||||||
|
// months on the /api/juniors response. Czech sheet authors drop the zero
|
||||||
|
// pad freely; Python's strptime tolerates it, so the parsers must match.
|
||||||
|
func TestParseDates_SingleDigitDayMonth(t *testing.T) {
|
||||||
|
// Czech form ("DD.MM.YYYY", with leading zeros optional) is the primary
|
||||||
|
// path. The "M/D/YYYY" fallback mirrors Python's %m/%d/%Y secondary
|
||||||
|
// strptime branch — month-first, day-second.
|
||||||
|
header := []string{"Jméno", "Tier", "", "01.06.2026", "1.6.2026", "23.3.2026", "6.4.2026", "01/02/2026", "1/2/2026"}
|
||||||
|
got := parseDates(header)
|
||||||
|
want := []string{"2026-06", "2026-06", "2026-03", "2026-04", "2026-01", "2026-01"}
|
||||||
|
if len(got) != len(want) {
|
||||||
|
t.Fatalf("parseDates: got %d entries, want %d (%v)", len(got), len(want), got)
|
||||||
|
}
|
||||||
|
for i, e := range got {
|
||||||
|
if e.month != want[i] {
|
||||||
|
t.Errorf("parseDates[%d].month = %q, want %q (raw=%q)", i, e.month, want[i], header[e.col])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TTL smoke test: second call within TTL must not call fetch again.
|
// TTL smoke test: second call within TTL must not call fetch again.
|
||||||
func TestLoadAdults_CacheHit(t *testing.T) {
|
func TestLoadAdults_CacheHit(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
|
|||||||
Reference in New Issue
Block a user