Um in den Diagrammen immer die neusten Daten darzustellen, muss gasspeicher_history.json inkrementell mit der GIE-AGSI-API für deutsche Gasspeicherdaten aktualisiert werden. Die Kernidee: die bestehende Historie bleibt unangetastet, es werden ausschließlich fehlende Tage ergänzt und dedupliziert.
1 – Konfiguration
Zunächst werden drei Konstanten definiert:
API_KEYfür die Authentifizierung gegenüber der GIE-APICOUNTRY_CODE(DE) für den Länderfilter, undJSON_FILEfür den Zielpfad.__DIR__ . '/gasspeicher_history.json'Der Pfad wid absoult gebildet gebildet und nicht relativ, weil ein Cronjob typischerweise mit einem anderen Arebeitsverzeichnis läuft als ein manueller Aufruf im Projektverzeichnis. Ohne__DIR__würde das Skript abhängig vom Aufrufkontext eine falsche Datei lesen oder schreiben, im schlimmsten Fall eine neue leere Historie an unerwarteter Stelle anlegen.
2 – Bestandsaufnahme
if (file_exists(JSON_FILE)) {
$decoded = json_decode(file_get_contents(JSON_FILE), true);
if (is_array($decoded)) {
$existingData = $decoded;
foreach ($existingData as $entry) {
if (isset($entry['gasDayStart'])) {
$dateLookup[$entry['gasDayStart']] = true;
}
}
}
}Die Existenzprüfung ist notwendig, damit der Erstlauf (noch keine Datei vorhanden) nicht zum Fehler führt. $existingData und $dateLookup bleiben dann schlicht leere Arrays. $dateLookup ist ein assoziatives Array, das gasDayStart-Strings auf true mappt. Das ist die Standard-PHP-Technik für O(1)-Existenzprüfungen (isset() auf Array-Keys ist ein Hashtable-Lookup); die Alternative — in_array() über $existingData bei jedem neuen Eintrag — wäre O(n) pro Prüfung und würde bei wachsender Historie linear langsamer.
3 – API-Abruf
$apiUrl = 'https://agsi.gie.eu/api?country=' . strtolower(COUNTRY_CODE) . '&size=30';
$ch = curl_init($apiUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['x-key: ' . API_KEY, 'Accept: application/json']);
curl_setopt($ch, CURLOPT_TIMEOUT, 15);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);Mit dem URL-Parametersize=30 fragt das Skript die Daten der letzten 30 Tage ab anstatt nur die des aktuellen Tages. Sollte ein ein Lauf ausfallen (Serverausfall, Wartungsfenster, Cron-Fehlkonfiguration), schließt sich die entstandene Lücke beim nächsten erfolgreichen Lauf automatisch, weil die fehlenden Tage im 30-Tage-Fenster erneut mitkommen und über den $dateLookup-Abgleich als neu erkannt werden. CURLOPT_TIMEOUT auf 15s verhindert ein unbegrenztes Blockieren bei Netzwerkproblemen. Dis ist wichtig, weil das Skript typischerweise unbeaufsichtigt per Cron läuft und ein hängender Prozess sonst unbemerkt bliebe.
4 – Dreistufige Fehlerprüfung
Zuerst wird überprüft, ob die API erfolgreich abgefragt wurde. Sollte keine Antwort zurückgegeben worden sein (! $response) oder kein Dokument (httpCode !== 200) wird ein Fehler ausgeben und das Skript abgebrochen.
if ($httpCode !== 200 || !$response) {<br> fwrite(STDERR, "Fehler beim API-Abruf. HTTP-Statuscode: $httpCode" . ($curlError ? " ($curlError)" : "") . "\n");<br> exit(1);<br>}Anschließend wir überprüft, ob das Dokument ein Fehlermeldung enthält (!empty($apiData[‚error‘])). Die GIS-API hat die Eigenheit, keinen Fehlercode zurückzugeben, sondern ein valides Dokument mit Fehlermeldung.
$apiData = json_decode($response, true);
if (!empty($apiData['error'])) {
fwrite(STDERR, "API-Fehler: " . $apiData['error'] . (isset($apiData['message']) ? " - " . $apiData['message'] : "") . "\n");
exit(1);Schließlich wird noch geprüft, ob überhaupt Daten geliefert wurden, um zu vermeiden, dass das Skript später abstürzt, weil keine Daten vorhanden sind.
if (!isset($apiData['data']) || !is_array($apiData['data'])) {
fwrite(STDERR, "Ungueltige oder leere API-Antwort erhalten.\n");
exit(1);
}Alle drei Fehlerpfade schreiben auf STDERR statt STDOUT und terminieren mit exit(1). Das ist die Grundlage für die Unterscheidbarkeit im Cron-Kontext: STDERR kann separat umgeleitet werden (2>> error.log), sodass echte Fehler auffallen (z. B. via Mail-Benachrichtigung bei nicht-leerem Cron-Output), während ein Lauf ohne neue Daten, kein Fehler, sondern Normalfall, auf STDOUT bleibt und den Exit-Code 0 behält.
5 – Bereinigung und Deduplizierung
foreach ($apiData['data'] as $apiEntry) {
$date = $apiEntry['gasDayStart'] ?? null;
if (!$date) continue;
if (!isset($dateLookup[$date])) {
$cleanedEntry = [
'status' => $apiEntry['status'] ?? 'E',
'gasDayStart' => $date,
'gasInStorage' => parseGieValue($apiEntry['gasInStorage'] ?? null),
// ... weitere Felder
];
$existingData[] = $cleanedEntry;
$dateLookup[$date] = true;
$newDataCount++;
}
}Jetzt geht das Skript in einer Schleife (foreach) jeden einzelnen Datensatz durch, den die GIE-API für die letzten 30 Tage zurückgegeben hat ($apiData['data']).
foreach ($apiData['data'] as $apiEntry) {
$date = $apiEntry['gasDayStart'] ?? null;
if (!$date) continue;- Zuerst wird das Datum des Gastages (
gasDayStart, z. B."2026-08-17") ausgelesen. Sollte dieses Feld in der API-Antwort überraschenderweise fehlen (über den Null-Coalescing-Operator??abgefangen), wird$dateaufnullgesetzt.
- Die Zeile
if (!$date) continue;bricht die Verarbeitung für diesen spezifischen Eintrag sofort ab und springt zum nächsten Tag in der Schleife, falls kein gültiges Datum existiert. Das verhindert Folgefehler im Skript.
if (!isset($dateLookup[$date])) {- Das Skript prüft, ob das Datum des aktuellen API-Eintrags (
$date) bereits in Ihrer lokalen JSON-Datei existiert. $dateLookupist ein assoziatives Array, das beim Laden der bestehenden JSON-Datei mit allen bereits gespeicherten Daten befüllt wurde (z.B.$dateLookup['2026-08-08'] = true)
$cleanedEntry = [<br>'status' => $apiEntry['status'] ?? 'E',<br>'gasDayStart' => $date,<br>'gasInStorage' => parseGieValue($apiEntry['gasInStorage'] ?? null),<br>// ... weitere Felder<br>];<br>$existingData[] = $cleanedEntry;<br>$dateLookup[$date] = true;<br>$newDataCount++;- Hier wird ein neues, bereinigtes Array (
$cleanedEntry) für die lokale Speicherung aufgebaut - Für das Feld
status(das die Datenqualität wieCfür Confirmed oderEfür Estimated angibt) wird ein Fallback definiert. Falls die API keinen Status liefert, wird standardmäßig'E'(Estimated) angenommen - parseGieValue() ist die wichtigste Bereinigung. Die GIE-API liefert Zahlenwerte oft als Text-Strings oder bei Datenlücken als Bindestrich
"-". Die HilfsfunktionparseGieValue()fängt diese Sonderfälle ab und konvertiert sie in echte Gleitkommazahlen (float) oder sauberenull-Wert.
function parseGieValue($val) {
if ($val === null || $val === '-' || trim((string)$val) === '') {
return null;
}
return (float)$val;
}Zeile 12 -14:
$existingData[] = $cleanedEntry;
$dateLookup[$date] = true;
$newDataCount++;- Der frisch bereinigte Datensatz wird an das Ende des Arrays
$existingDataangehängt (welches alle historischen Daten enthält) - Das neue Datum wird dann in
$dateLookupalstruemarkiert. Sollte die API-Antwort denselben Tag doppelt enthalten, wird er beim zweiten Durchlauf der Schleife dank dieser Zeile blockiert. - Der Zähler
$newDataCountwird um eins erhöht. Am Ende des Gesamtskripts wird anhand überprüft , obnewDataCount > 0ist und lokale JSON-Datei tatsächlich neu auf die Festplatte geschrieben werden muss
6 – Sortierung und bedingtes Schreiben
if ($newDataCount > 0) {
usort($existingData, function ($a, $b) {
return strcmp($a['gasDayStart'], $b['gasDayStart']);
});
$newJsonContent = json_encode($existingData, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
if (file_put_contents(JSON_FILE, $newJsonContent) !== false) {
echo "Erfolgreich aktualisiert: $newDataCount neue(r) Eintrag/Einträge hinzugefügt.\n";
} else {
fwrite(STDERR, "Fehler: JSON-Datei konnte nicht beschrieben werden. Berechtigungen pruefen.\n");
exit(1);
}
} else {
echo "Datenbestand bereits auf dem neuesten Stand. Keine neuen Einträge gefunden.\n";
}
Dieser Code-Abschnitt steuert den abschließenden Sortierungs-, Formatierungs- und Speichervorgang der Gasspeicherdaten. Er stellt sicher, dass Festplatten-Schreibzugriffe nur bei echten Datenänderungen stattfinden, die Daten chronologisch konsistent bleiben und Fehler beim Schreiben sauber abgefangen werden.
1. Bedingter Schreibschutz (if ($newDataCount > 0))
if ($newDataCount > 0) {- Das Skript prüft zuerst, ob der Zähler für neue Einträge (
$newDataCount) größer als Null ist.
- Der Zweck: Liegen keine neuen Gastage vor (was an den meisten Tagen der Fall ist, da die API nur einmal täglich aktualisiert wird), bleibt die lokale JSON-Datei physisch unangetastet. Das spart Systemressourcen (I/O-Zyklen auf der Festplatte).
- Ist der Datenbestand bereits aktuell, wird lediglich eine informative Meldung auf dem Standard-Ausgabekanal (
STDOUT) ausgegeben, und das Skript beendet sich ohne Schreibvorgang.
2. Chronologische Sortierung (usort & strcmp)
usort($existingData, function ($a, $b) {
return strcmp($a['gasDayStart'], $b['gasDayStart']);
});- Der Zweck: Da die API-Daten nicht zwingend in der richtigen Reihenfolge geliefert werden oder neue Einträge unsortiert an das Ende des lokalen Arrays angehängt wurden, muss die gesamte Zeitreihe vor dem Speichern geordnet werden.
☝️ Unsortierte Daten erzeugen in ECharts Darstellungsfehler. - Die Funktionsweise:
usortsortiert das Array$existingDatanach benutzerdefinierten Kriterien. Die anonyme Vergleichsfunktion nutztstrcmp, um die Datums-Strings im FormatYYYY-MM-DD(FeldgasDayStart) miteinander zu vergleichen. Dies garantiert, dass die JSON-Datei von der ältesten bis zur neuesten Messung aufsteigend sortiert abgelegt wird
3: JSON-Formatierung (json_encode)
$newJsonContent = json_encode($existingData, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);Das sortierte PHP-Array wird wieder in eine JSON-Zeichenkette umgewandelt. Dabei kommen zwei wichtige Formatierungs-Flags zum Einsatz:
- JSON_PRETTY_PRINT: Formatiert das JSON mit Einrückungen und Zeilenumbrüchen. Das macht die Datei für Entwickler direkt lesbar und erleichtert das Debugging.
- JSON_UNESCAPED_SLASHES: Verhindert, dass Schrägstriche (Slashes
/) automatisch maskiert werden (aus/wird nicht\/). Dies hält Pfade und URLs in den Metadaten schlank und lesbar.
4. Sicheres Schreiben & Fehlerbehandlung (file_put_contents)
if (file_put_contents(JSON_FILE, $newJsonContent)) {
echo "Erfolgreich aktualisiert: $newDataCount neue(r) Eintrag/Einträge hinzugefügt.\n";
} else {
fwrite(STDERR, "Fehler: JSON-Datei konnte nicht beschrieben werden. Berechtigungen pruefen.\n");
exit(1);
}- file_put_contents(…): Versucht, die formatierte JSON-Zeichenkette in die konfigurierte Datei zu schreiben. Die Funktion gibt bei Erfolg die Anzahl der geschriebenen Bytes zurück, im Fehlerfall den booleschen Wert
false. - Erfolgsfall: Das Skript meldet die erfolgreiche Aktualisierung und die Anzahl der hinzugefügten Tage auf
STDOUT. Das Skript beendet sich implizit mit dem Erfolgs-Exit-Code0. - Fehlerfall: Sollte der Schreibvorgang fehlschlagen (z. B. weil das Skript keine Schreibrechte im Zielverzeichnis besitzt), wird eine präzise Fehlermeldung an den Standard-Fehlerkanal (
STDERR) gesendet. Danach bricht das Skript mittelsexit(1)kontrolliert ab und signalisiert dem Betriebssystem (oder dem Cron-Daemon) über den Exit-Code1einen Fehler.
7 – Betrieb
Um die Daten immer auf dem neuesten Stand zu halten, muss das Skript über Cron regelmäßig gestartet werden.
0 20 * * * php /home/user/gasspeicher/update_data.php > /dev/null 2>&1
20 Uhr liegt nach der ersten täglichen GIE-Aktualisierung um 19:30 Uhr. Für Fehlerüberwachung empfiehlt sich statt 2>&1 > /dev/null eine gezielte Umleitung von STDERR in eine Logdatei, z. B. 2>> update.log, da nur dieser Kanal echte Fehlerfälle signalisiert. Wer wirklich aktuelle Daten, sollte weitere Läufe um 0 und 6 Uhr einplanen.

Schreibe einen Kommentar