diff --git a/main.go b/main.go index 134c5f5..f7f4d10 100644 --- a/main.go +++ b/main.go @@ -157,7 +157,7 @@ func (s *server) handleIndex(w http.ResponseWriter, r *http.Request) { updatedLabel = updatedAt.Format("02.01.2006 15:04:05") } if err := page.Execute(w, map[string]any{ - "Watts": watts, + "Watts": formatGermanNumber(watts), "UpdatedAt": updatedLabel, "HistoryChart": buildHistoryChart(history, s.chartMax), }); err != nil { @@ -165,6 +165,34 @@ func (s *server) handleIndex(w http.ResponseWriter, r *http.Request) { } } +func formatGermanNumber(n int) string { + s := strconv.Itoa(n) + if len(s) <= 3 { + return s + } + + neg := false + if s[0] == '-' { + neg = true + s = s[1:] + } + + var b strings.Builder + if neg { + b.WriteByte('-') + } + rem := len(s) % 3 + if rem == 0 { + rem = 3 + } + b.WriteString(s[:rem]) + for i := rem; i < len(s); i += 3 { + b.WriteByte('.') + b.WriteString(s[i : i+3]) + } + return b.String() +} + func buildHistoryChart(history []sample, maxWatts int) template.HTML { const ( width = 640 @@ -242,8 +270,8 @@ func buildHistoryChart(history []sample, maxWatts int) template.HTML { fmt.Fprintf(&b, ``, x1, yTop, yBottom) b.WriteString(``) b.WriteString(``) - fmt.Fprintf(&b, `%d W`, x0-10, int(yTop)+14, maxV) - fmt.Fprintf(&b, `%d W`, x0-10, int(yMid)+14, midV) + fmt.Fprintf(&b, `%s W`, x0-10, int(yTop)+14, formatGermanNumber(maxV)) + fmt.Fprintf(&b, `%s W`, x0-10, int(yMid)+14, formatGermanNumber(midV)) fmt.Fprintf(&b, `0 W`, x0-10, int(yBottom)+14) fmt.Fprintf(&b, `%s`, x0, int(height)-10, start.Format("15:04")) fmt.Fprintf(&b, `%s`, x0+(plotW/2), int(height)-10, midT.Format("15:04")) diff --git a/main_test.go b/main_test.go index a8f4193..4b05138 100644 --- a/main_test.go +++ b/main_test.go @@ -31,6 +31,23 @@ func TestHandleIndexRendersWatts(t *testing.T) { } } +func TestFormatGermanNumber(t *testing.T) { + tests := map[int]string{ + 0: "0", + 12: "12", + 999: "999", + 1000: "1.000", + 10000: "10.000", + 1234567: "1.234.567", + -1000: "-1.000", + } + for input, want := range tests { + if got := formatGermanNumber(input); got != want { + t.Fatalf("formatGermanNumber(%d) = %q, want %q", input, got, want) + } + } +} + func TestHandleWriteRequiresSecret(t *testing.T) { srv := &server{secret: "secret", store: &store{}} req := httptest.NewRequest(http.MethodPost, "/write", bytes.NewBufferString(`{"watts":456}`)) @@ -116,7 +133,7 @@ func TestBuildHistoryChartRendersPolyline(t *testing.T) { func TestBuildHistoryChartUsesConfiguredMax(t *testing.T) { history := []sample{{at: time.Date(2026, 4, 20, 12, 0, 0, 0, time.UTC), value: 250}} chart := buildHistoryChart(history, 1000) - if !strings.Contains(string(chart), "1000 W") { + if !strings.Contains(string(chart), "1.000 W") { t.Fatalf("chart did not include configured max: %s", string(chart)) } if strings.Contains(string(chart), "250 W") { diff --git a/static/index.html b/static/index.html index 1cec453..fc9267d 100644 --- a/static/index.html +++ b/static/index.html @@ -18,8 +18,7 @@ - {{.Watts}} - watts + {{.Watts}} W Last updated: {{.UpdatedAt}} {{.HistoryChart}}