import { NextRequest, NextResponse } from 'next/server'; import { getSession } from '@/lib/session'; 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; 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 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 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)); }