bugfix wunderground reporting

This commit is contained in:
2026-03-09 09:19:45 +11:00
parent 5b8cad905f
commit c796f1324e
12 changed files with 253 additions and 33 deletions

View File

@@ -2,6 +2,7 @@ package config
import (
"errors"
"fmt"
"os"
"strings"
"time"
@@ -31,6 +32,7 @@ type Config struct {
Latitude float64 `yaml:"latitude"`
Longitude float64 `yaml:"longitude"`
ElevationM float64 `yaml:"elevation_m"`
Timezone string `yaml:"timezone"`
} `yaml:"site"`
Pollers struct {
@@ -105,6 +107,11 @@ func Load(path string) (*Config, error) {
if c.Site.Name == "" {
c.Site.Name = "default"
}
if c.Site.Timezone != "" {
if _, err := time.LoadLocation(c.Site.Timezone); err != nil {
return nil, fmt.Errorf("invalid site timezone %q: %w", c.Site.Timezone, err)
}
}
if c.Pollers.OpenMeteo.Model == "" {
c.Pollers.OpenMeteo.Model = "ecmwf"
}

View File

@@ -27,7 +27,9 @@ type Latest struct {
// rolling sums built from "rain increment" values (mm)
rainIncs []rainIncPoint // last 1h
dailyIncs []rainIncPoint // since midnight (or since start; well trim daily by midnight)
dailyIncs []rainIncPoint // since midnight in rainDayLoc (or since start; trimmed each update)
rainDayLoc *time.Location
}
type rainIncPoint struct {
@@ -35,6 +37,15 @@ type rainIncPoint struct {
mm float64 // incremental rainfall at this timestamp (mm)
}
func NewLatest(rainDayLoc *time.Location) *Latest {
if rainDayLoc == nil {
rainDayLoc = time.Local
}
return &Latest{
rainDayLoc: rainDayLoc,
}
}
func (l *Latest) Update(ts time.Time, p *WS90Payload) {
l.mu.Lock()
defer l.mu.Unlock()
@@ -49,9 +60,9 @@ func (l *Latest) Update(ts time.Time, p *WS90Payload) {
cutoff := ts.Add(-1 * time.Hour)
l.rainIncs = trimBefore(l.rainIncs, cutoff)
// Track daily increments: trim before local midnight
// Track daily increments: trim before midnight in configured rain day timezone.
l.dailyIncs = append(l.dailyIncs, rainIncPoint{ts: ts, mm: inc})
midnight := localMidnight(ts)
midnight := l.rainDayMidnight(ts)
l.dailyIncs = trimBefore(l.dailyIncs, midnight)
}
@@ -68,10 +79,13 @@ func trimBefore(a []rainIncPoint, cutoff time.Time) []rainIncPoint {
return a
}
// localMidnight returns midnight in the local timezone of the *process*.
// If you want a specific timezone (e.g. Australia/Sydney) we can wire that in later.
func localMidnight(t time.Time) time.Time {
lt := t.Local()
// rainDayMidnight returns midnight in the configured rain day timezone.
func (l *Latest) rainDayMidnight(t time.Time) time.Time {
loc := l.rainDayLoc
if loc == nil {
loc = time.Local
}
lt := t.In(loc)
return time.Date(lt.Year(), lt.Month(), lt.Day(), 0, 0, 0, 0, lt.Location())
}

View File

@@ -0,0 +1,50 @@
package mqttingest
import (
"testing"
"time"
)
func TestLatestDailyRainUsesConfiguredTimezone(t *testing.T) {
loc, err := time.LoadLocation("Australia/Sydney")
if err != nil {
t.Fatalf("load location: %v", err)
}
l := NewLatest(loc)
// Crosses UTC midnight but remains the same local day in Sydney (UTC+11 during DST).
l.Update(time.Date(2026, time.January, 14, 22, 0, 0, 0, time.UTC), &WS90Payload{RainMM: 0})
l.Update(time.Date(2026, time.January, 14, 23, 30, 0, 0, time.UTC), &WS90Payload{RainMM: 2})
l.Update(time.Date(2026, time.January, 15, 0, 5, 0, 0, time.UTC), &WS90Payload{RainMM: 2})
snap, ok := l.Snapshot()
if !ok {
t.Fatal("expected snapshot")
}
if snap.DailyRainMM != 2 {
t.Fatalf("expected daily rain 2.0mm, got %.2fmm", snap.DailyRainMM)
}
}
func TestLatestDailyRainResetsAtConfiguredLocalMidnight(t *testing.T) {
loc, err := time.LoadLocation("Australia/Sydney")
if err != nil {
t.Fatalf("load location: %v", err)
}
l := NewLatest(loc)
// Crosses local midnight in Sydney (00:00 local == 13:00 UTC during DST).
l.Update(time.Date(2026, time.January, 15, 12, 30, 0, 0, time.UTC), &WS90Payload{RainMM: 0})
l.Update(time.Date(2026, time.January, 15, 12, 50, 0, 0, time.UTC), &WS90Payload{RainMM: 1})
l.Update(time.Date(2026, time.January, 15, 13, 10, 0, 0, time.UTC), &WS90Payload{RainMM: 1})
snap, ok := l.Snapshot()
if !ok {
t.Fatal("expected snapshot")
}
if snap.DailyRainMM != 0 {
t.Fatalf("expected daily rain reset after local midnight; got %.2fmm", snap.DailyRainMM)
}
}