package udp import ( "testing" "time" ) const ms = 1000 func sec(h, m, s int) uint32 { return uint32((h*3600 + m*60 + s) * ms) } // WSJT-X stamps a decode with a time of DAY and no date, so the date has to come // from our own clock — and around midnight the two disagree. A decode stamped // 23:59:58 that reaches us at 00:00:01 would be dated the NEW day, putting it // almost 24 hours in the future: it would sort to the top of the decodes panel // and stay there for the rest of the session, and its period would never line up // with the ones around it. // // The clock is passed IN. The previous version of this test read the real one // and so failed every afternoon — a 23:59:58 stamp seen at 17:00 is genuinely // hours in the future, and no rule can make it otherwise. A suite that is red // for half the day is a suite nobody reads. func TestDecodeTimeCrossesMidnight(t *testing.T) { // Just after midnight, a stamp from the last seconds of yesterday. now := time.Date(2026, 3, 14, 0, 0, 1, 0, time.UTC) got := decodeTimeAt(sec(23, 59, 58), now) if want := time.Date(2026, 3, 13, 23, 59, 58, 0, time.UTC); !got.Equal(want) { t.Errorf("23:59:58 seen at 00:00:01 → %s, want %s (yesterday)", got, want) } if got.After(now) { t.Errorf("%s is in the future — it would sort to the top of the list for the session", got) } } // The mirror case: just before midnight, a stamp from the first seconds of // tomorrow. Our clock is still on the old day. func TestDecodeTimeCrossesMidnightBackwards(t *testing.T) { now := time.Date(2026, 3, 13, 23, 59, 58, 0, time.UTC) got := decodeTimeAt(sec(0, 0, 1), now) if want := time.Date(2026, 3, 14, 0, 0, 1, 0, time.UTC); !got.Equal(want) { t.Errorf("00:00:01 seen at 23:59:58 → %s, want %s (tomorrow)", got, want) } } // The ordinary case, which is every decode that is not within a few seconds of // midnight: the stamp belongs to today and is left alone. func TestDecodeTimeOrdinary(t *testing.T) { now := time.Date(2026, 3, 13, 14, 30, 0, 0, time.UTC) got := decodeTimeAt(sec(14, 29, 45), now) if want := time.Date(2026, 3, 13, 14, 29, 45, 0, time.UTC); !got.Equal(want) { t.Errorf("a stamp close to now → %s, want %s", got, want) } } // The whole point of the timestamp is grouping, so two decodes from the same // fifteen-second slot must floor to the same period however far apart in the // slot they were heard. func TestDecodesInOneSlotShareAPeriod(t *testing.T) { now := time.Date(2026, 3, 13, 12, 30, 7, 0, time.UTC) at := func(h, m, s int) time.Time { return decodeTimeAt(sec(h, m, s), now) } floor := func(x time.Time) int64 { return x.Unix() / 15 * 15 } a, b := at(12, 30, 0), at(12, 30, 14) if floor(a) != floor(b) { t.Errorf("%s and %s fell in different periods", a, b) } if floor(at(12, 30, 14)) == floor(at(12, 30, 15)) { t.Error("12:30:14 and 12:30:15 are different slots and must not share a period") } }