fix: Alle Zeitanzeigen fest auf Europe/Berlin Zeitzone

Alle toLocaleTimeString/toLocaleDateString-Aufrufe und
getHours/getMinutes-Zugriffe verwenden jetzt fix die
Zeitzone Europe/Berlin statt der Browser-Zeitzone.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Dieser Commit ist enthalten in:
claude-dev
2026-03-07 10:31:21 +01:00
Ursprung a69352575d
Commit ac3291f608

Datei anzeigen

@@ -2,6 +2,20 @@
* OSINT Lagemonitor - Hauptanwendungslogik. * OSINT Lagemonitor - Hauptanwendungslogik.
*/ */
/** Feste Zeitzone fuer alle Anzeigen — NIEMALS aendern. */
const TIMEZONE = 'Europe/Berlin';
/** Gibt Jahr/Monat(0-basiert)/Tag/Stunde/Minute in Berliner Zeit zurueck. */
function _tz(d) {
const s = d.toLocaleString('en-CA', {
timeZone: TIMEZONE, year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false
});
const m = s.match(/(\d{4})-(\d{2})-(\d{2}),?\s*(\d{2}):(\d{2}):(\d{2})/);
if (!m) return { year: d.getFullYear(), month: d.getMonth(), date: d.getDate(), hours: d.getHours(), minutes: d.getMinutes() };
return { year: +m[1], month: +m[2] - 1, date: +m[3], hours: +m[4], minutes: +m[5] };
}
/** /**
* Theme Manager: Dark/Light Theme Toggle mit localStorage-Persistenz. * Theme Manager: Dark/Light Theme Toggle mit localStorage-Persistenz.
*/ */
@@ -317,7 +331,7 @@ const NotificationCenter = {
list.innerHTML = this._notifications.map(n => { list.innerHTML = this._notifications.map(n => {
const time = new Date(n.timestamp); const time = new Date(n.timestamp);
const timeStr = time.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }); const timeStr = time.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', timeZone: TIMEZONE });
const unreadClass = n.read ? '' : ' unread'; const unreadClass = n.read ? '' : ' unread';
const icon = n.icon || 'info'; const icon = n.icon || 'info';
return `<div class="notification-item${unreadClass}" onclick="NotificationCenter._handleClick(${n.incident_id})" data-id="${n.incident_id}" role="button" tabindex="0"> return `<div class="notification-item${unreadClass}" onclick="NotificationCenter._handleClick(${n.incident_id})" data-id="${n.incident_id}" role="button" tabindex="0">
@@ -692,10 +706,10 @@ const App = {
} }
// Meta (im Header-Strip) — relative Zeitangabe mit vollem Datum als Tooltip // Meta (im Header-Strip) — relative Zeitangabe mit vollem Datum als Tooltip
const updated = incident.updated_at ? new Date(incident.updated_at) : null; const updated = incident.updated_at ? parseUTC(incident.updated_at) : null;
const metaUpdated = document.getElementById('meta-updated'); const metaUpdated = document.getElementById('meta-updated');
if (updated) { if (updated) {
const fullDate = `${updated.toLocaleDateString('de-DE')} ${updated.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })}`; const fullDate = `${updated.toLocaleDateString('de-DE', { timeZone: TIMEZONE })} ${updated.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', timeZone: TIMEZONE })}`;
metaUpdated.textContent = `Stand: ${App._timeAgo(updated)}`; metaUpdated.textContent = `Stand: ${App._timeAgo(updated)}`;
metaUpdated.title = fullDate; metaUpdated.title = fullDate;
} else { } else {
@@ -707,7 +721,7 @@ const App = {
const lagebildTs = document.getElementById('lagebild-timestamp'); const lagebildTs = document.getElementById('lagebild-timestamp');
if (lagebildTs) { if (lagebildTs) {
lagebildTs.textContent = updated lagebildTs.textContent = updated
? `Stand: ${updated.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })} ${updated.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })} Uhr` ? `Stand: ${updated.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric', timeZone: TIMEZONE })} ${updated.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', timeZone: TIMEZONE })} Uhr`
: ''; : '';
} }
@@ -945,15 +959,16 @@ const App = {
const bucketMap = {}; const bucketMap = {};
entries.forEach(e => { entries.forEach(e => {
const d = new Date(e.timestamp || 0); const d = new Date(e.timestamp || 0);
const b = _tz(d);
let key, label, ts; let key, label, ts;
if (granularity === 'hour') { if (granularity === 'hour') {
key = `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}-${d.getHours()}`; key = `${b.year}-${b.month + 1}-${b.date}-${b.hours}`;
label = d.toLocaleDateString('de-DE', { day: '2-digit', month: 'short' }) + ', ' + d.getHours().toString().padStart(2, '0') + ':00'; label = d.toLocaleDateString('de-DE', { day: '2-digit', month: 'short', timeZone: TIMEZONE }) + ', ' + b.hours.toString().padStart(2, '0') + ':00';
ts = new Date(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours()).getTime(); ts = new Date(b.year, b.month, b.date, b.hours).getTime();
} else { } else {
key = `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}`; key = `${b.year}-${b.month + 1}-${b.date}`;
label = d.toLocaleDateString('de-DE', { weekday: 'short', day: '2-digit', month: 'short' }); label = d.toLocaleDateString('de-DE', { weekday: 'short', day: '2-digit', month: 'short', timeZone: TIMEZONE });
ts = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 12).getTime(); ts = new Date(b.year, b.month, b.date, 12).getTime();
} }
if (!bucketMap[key]) { if (!bucketMap[key]) {
bucketMap[key] = { key, label, timestamp: ts, entries: [] }; bucketMap[key] = { key, label, timestamp: ts, entries: [] };
@@ -1019,7 +1034,8 @@ const App = {
const ts = (granularity === 'day' && b.entries && b.entries.length > 0) const ts = (granularity === 'day' && b.entries && b.entries.length > 0)
? new Date(b.entries[0].timestamp || b.timestamp) ? new Date(b.entries[0].timestamp || b.timestamp)
: new Date(b.timestamp); : new Date(b.timestamp);
return ts.getHours().toString().padStart(2, '0') + ':' + ts.getMinutes().toString().padStart(2, '0'); const tp = _tz(ts);
return tp.hours.toString().padStart(2, '0') + ':' + tp.minutes.toString().padStart(2, '0');
} }
return b.label; return b.label;
}; };
@@ -1056,21 +1072,22 @@ const App = {
const markers = []; const markers = [];
buckets.forEach(b => { buckets.forEach(b => {
const d = new Date(b.timestamp); const d = new Date(b.timestamp);
const dayKey = `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}`; const bp = _tz(d);
const dayKey = `${bp.year}-${bp.month}-${bp.date}`;
if (!seen[dayKey]) { if (!seen[dayKey]) {
seen[dayKey] = true; seen[dayKey] = true;
const now = new Date(); const np = _tz(new Date());
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); const todayKey = `${np.year}-${np.month}-${np.date}`;
const yesterday = new Date(today.getTime() - 86400000); const yp = _tz(new Date(Date.now() - 86400000));
const bucketDay = new Date(d.getFullYear(), d.getMonth(), d.getDate()); const yesterdayKey = `${yp.year}-${yp.month}-${yp.date}`;
let label; let label;
const dateStr = d.toLocaleDateString('de-DE', { day: '2-digit', month: 'short' }); const dateStr = d.toLocaleDateString('de-DE', { day: '2-digit', month: 'short', timeZone: TIMEZONE });
if (bucketDay.getTime() === today.getTime()) { if (dayKey === todayKey) {
label = 'Heute, ' + dateStr; label = 'Heute, ' + dateStr;
} else if (bucketDay.getTime() === yesterday.getTime()) { } else if (dayKey === yesterdayKey) {
label = 'Gestern, ' + dateStr; label = 'Gestern, ' + dateStr;
} else { } else {
label = d.toLocaleDateString('de-DE', { weekday: 'short', day: '2-digit', month: 'short' }); label = d.toLocaleDateString('de-DE', { weekday: 'short', day: '2-digit', month: 'short', timeZone: TIMEZONE });
} }
const pos = this._bucketPositionPercent(b, rangeStart, rangeEnd, buckets.length); const pos = this._bucketPositionPercent(b, rangeStart, rangeEnd, buckets.length);
markers.push({ text: label, pos }); markers.push({ text: label, pos });
@@ -1202,9 +1219,10 @@ const App = {
* Einträge nach Zeitperiode gruppieren. * Einträge nach Zeitperiode gruppieren.
*/ */
_groupByTimePeriod(entries, granularity) { _groupByTimePeriod(entries, granularity) {
const now = new Date(); const np = _tz(new Date());
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); const todayKey = `${np.year}-${np.month}-${np.date}`;
const yesterday = new Date(today.getTime() - 86400000); const yp = _tz(new Date(Date.now() - 86400000));
const yesterdayKey = `${yp.year}-${yp.month}-${yp.date}`;
const groups = []; const groups = [];
let currentGroup = null; let currentGroup = null;
@@ -1217,18 +1235,18 @@ const App = {
key = 'unknown'; key = 'unknown';
label = 'Unbekannt'; label = 'Unbekannt';
} else if (granularity === 'hour') { } else if (granularity === 'hour') {
const h = d.getHours(); const ep = _tz(d);
key = `${d.toDateString()}-${h}`; key = `${ep.year}-${ep.month}-${ep.date}-${ep.hours}`;
label = `${h.toString().padStart(2, '0')}:00 Uhr`; label = `${ep.hours.toString().padStart(2, '0')}:00 Uhr`;
} else { } else {
const entryDate = new Date(d.getFullYear(), d.getMonth(), d.getDate()); const ep = _tz(d);
key = entryDate.toDateString(); key = `${ep.year}-${ep.month}-${ep.date}`;
if (entryDate.getTime() === today.getTime()) { if (key === todayKey) {
label = 'Heute'; label = 'Heute';
} else if (entryDate.getTime() === yesterday.getTime()) { } else if (key === yesterdayKey) {
label = 'Gestern'; label = 'Gestern';
} else { } else {
label = d.toLocaleDateString('de-DE', { weekday: 'short', day: 'numeric', month: 'short' }); label = d.toLocaleDateString('de-DE', { weekday: 'short', day: 'numeric', month: 'short', timeZone: TIMEZONE });
} }
} }
@@ -1280,7 +1298,7 @@ const App = {
const dateField = (type === 'research' && article.published_at) const dateField = (type === 'research' && article.published_at)
? article.published_at : article.collected_at; ? article.published_at : article.collected_at;
const time = dateField const time = dateField
? new Date(dateField).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }) ? (parseUTC(dateField) || new Date(dateField)).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', timeZone: TIMEZONE })
: '--:--'; : '--:--';
const headline = article.headline_de || article.headline; const headline = article.headline_de || article.headline;
@@ -1322,7 +1340,7 @@ const App = {
*/ */
_renderSnapshotEntry(snapshot) { _renderSnapshotEntry(snapshot) {
const time = snapshot.created_at const time = snapshot.created_at
? new Date(snapshot.created_at).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }) ? (parseUTC(snapshot.created_at) || new Date(snapshot.created_at)).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', timeZone: TIMEZONE })
: '--:--'; : '--:--';
const stats = []; const stats = [];
@@ -1379,7 +1397,8 @@ const App = {
_getMinuteKey(timestamp) { _getMinuteKey(timestamp) {
if (!timestamp) return 'none'; if (!timestamp) return 'none';
const d = new Date(timestamp); const d = new Date(timestamp);
return `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}-${d.getHours()}-${d.getMinutes()}`; const p = _tz(d);
return `${p.year}-${p.month}-${p.date}-${p.hours}-${p.minutes}`;
}, },
// === Event Handlers === // === Event Handlers ===
@@ -1659,9 +1678,9 @@ const App = {
} }
list.innerHTML = logs.map(log => { list.innerHTML = logs.map(log => {
const started = new Date(log.started_at); const started = parseUTC(log.started_at) || new Date(log.started_at);
const timeStr = started.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' }) + ' ' + const timeStr = started.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', timeZone: TIMEZONE }) + ' ' +
started.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }); started.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', timeZone: TIMEZONE });
let detail = ''; let detail = '';
if (log.status === 'completed') { if (log.status === 'completed') {
@@ -2967,7 +2986,7 @@ function buildDetailedSourceOverview() {
data.articles.forEach(a => { data.articles.forEach(a => {
const headline = UI.escape(a.headline_de || a.headline || 'Ohne Titel'); const headline = UI.escape(a.headline_de || a.headline || 'Ohne Titel');
const time = a.collected_at const time = a.collected_at
? new Date(a.collected_at).toLocaleString('de-DE', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }) ? (parseUTC(a.collected_at) || new Date(a.collected_at)).toLocaleString('de-DE', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit', timeZone: TIMEZONE })
: ''; : '';
const langBadge = a.language && a.language !== 'de' const langBadge = a.language && a.language !== 'de'
? `<span class="lang-badge">${a.language.toUpperCase()}</span>` : ''; ? `<span class="lang-badge">${a.language.toUpperCase()}</span>` : '';