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:
+84
-8
@@ -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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user