feat: Wetter zum gewählten Datum und zur Endzeit übernehmen — Version 1.15.0

Bisher stand im Formular immer der aktuelle Messwert, auch bei nachträglich
erfassten Einträgen. Ändert der Benutzer nun Datum oder Endzeit, werden
Temperatur, Feuchte und Luftdruck auf die Messung zu diesem Zeitpunkt
umgestellt.

/api/wetter nimmt dafür optional ?zeit=YYYY-MM-DDTHH:MM, holt über /range das
Fenster ±20 Minuten und liefert die nächstgelegene Messung. Wichtig: die
Wetter-API deutet Parameter ohne Zeitzone als UTC — die Route rechnet deshalb
Europe/Berlin um, sonst läge das Ergebnis im Sommer zwei Stunden daneben.

Gibt es keine Messung (Zukunft, Ausfall der Station), bleiben die bisherigen
Werte stehen und es erscheint ein Hinweis unter den Feldern.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-08 21:11:17 +02:00
parent 46e0254038
commit 5e37a9427c
5 changed files with 138 additions and 10 deletions
+84 -8
View File
@@ -1,16 +1,92 @@
import { NextResponse } from 'next/server';
import { NextRequest, NextResponse } from 'next/server';
import { getSession } from '@/lib/session';
export async function GET() {
const BASIS = 'https://stwwetter.fuerst-stuttgart.de/api/weather';
const ZONE = 'Europe/Berlin';
// Suchfenster um den gewünschten Zeitpunkt; die Station misst etwa alle 30 Sekunden.
const FENSTER_MIN = 20;
interface Messung {
datetime: string;
temperature: number;
humidity: number;
pressure: number;
}
/** Offset der Zone zum angegebenen Zeitpunkt in Minuten (im Sommer +120). */
function zonenOffset(zeitpunkt: Date): number {
const teile = new Intl.DateTimeFormat('en-US', {
timeZone: ZONE, hour12: false,
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit', second: '2-digit',
}).formatToParts(zeitpunkt);
const w = Object.fromEntries(teile.map((t) => [t.type, t.value])) as Record<string, string>;
const alsUtc = Date.UTC(+w.year, +w.month - 1, +w.day, +w.hour % 24, +w.minute, +w.second);
return (alsUtc - zeitpunkt.getTime()) / 60000;
}
/**
* Lokale Zeit ("YYYY-MM-DDTHH:MM", Europe/Berlin) in echte UTC umrechnen.
* Die Wetter-API deutet Parameter ohne Zeitzone als UTC — ohne Umrechnung läge
* das Ergebnis im Sommer zwei Stunden daneben.
*/
function lokalNachUtc(lokal: string): Date {
const naiv = Date.parse(lokal + ':00Z');
// Zwei Durchgänge, damit auch die Zeitumstellungs-Nacht richtig landet.
let utc = naiv - zonenOffset(new Date(naiv)) * 60000;
utc = naiv - zonenOffset(new Date(utc)) * 60000;
return new Date(utc);
}
/** Ohne Millisekunden und ohne 'Z' — so erwartet die Wetter-API ihre Parameter. */
function alsParameter(d: Date): string {
return d.toISOString().slice(0, 19);
}
function aufbereiten(m: Messung) {
return {
temp: Math.round(m.temperature * 10) / 10,
feuchte: Math.round(m.humidity),
druck: Math.round(m.pressure),
};
}
export async function GET(request: NextRequest) {
const session = await getSession();
if (!session) return NextResponse.json({ error: 'Nicht angemeldet' }, { status: 401 });
const res = await fetch('https://stwwetter.fuerst-stuttgart.de/api/weather/latest', { cache: 'no-store' });
const zeit = new URL(request.url).searchParams.get('zeit');
// Ohne Zeitangabe wie bisher: der aktuelle Messwert.
if (!zeit) {
const res = await fetch(`${BASIS}/latest`, { cache: 'no-store' });
if (!res.ok) return NextResponse.json({ error: 'Wetterdaten nicht verfügbar' }, { status: 502 });
return NextResponse.json(aufbereiten(await res.json()));
}
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(zeit)) {
return NextResponse.json({ error: 'Parameter zeit ungültig (YYYY-MM-DDTHH:MM)' }, { status: 400 });
}
const ziel = lokalNachUtc(zeit);
const von = new Date(ziel.getTime() - FENSTER_MIN * 60000);
const bis = new Date(ziel.getTime() + FENSTER_MIN * 60000);
const res = await fetch(
`${BASIS}/range?start=${alsParameter(von)}&end=${alsParameter(bis)}&limit=200`,
{ cache: 'no-store' }
);
if (!res.ok) return NextResponse.json({ error: 'Wetterdaten nicht verfügbar' }, { status: 502 });
const data = await res.json();
const temp = Math.round(data.temperature * 10) / 10;
const feuchte = Math.round(data.humidity);
const druck = Math.round(data.pressure);
return NextResponse.json({ temp, feuchte, druck });
const messungen: Messung[] = await res.json();
if (!Array.isArray(messungen) || messungen.length === 0) {
// Kommt bei künftigen Zeitpunkten und bei Ausfällen der Station vor.
return NextResponse.json({ error: 'Keine Wetterdaten zu diesem Zeitpunkt' }, { status: 404 });
}
const naechste = messungen.reduce((a, b) =>
Math.abs(Date.parse(a.datetime) - ziel.getTime()) <= Math.abs(Date.parse(b.datetime) - ziel.getTime()) ? a : b
);
return NextResponse.json(aufbereiten(naechste));
}