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.
*/
/** 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.
*/
@@ -317,7 +331,7 @@ const NotificationCenter = {
list.innerHTML = this._notifications.map(n => {
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 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">
@@ -692,10 +706,10 @@ const App = {
}
// 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');
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.title = fullDate;
} else {
@@ -707,7 +721,7 @@ const App = {
const lagebildTs = document.getElementById('lagebild-timestamp');
if (lagebildTs) {
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 = {};
entries.forEach(e => {
const d = new Date(e.timestamp || 0);
const b = _tz(d);
let key, label, ts;
if (granularity === 'hour') {
key = `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}-${d.getHours()}`;
label = d.toLocaleDateString('de-DE', { day: '2-digit', month: 'short' }) + ', ' + d.getHours().toString().padStart(2, '0') + ':00';
ts = new Date(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours()).getTime();
key = `${b.year}-${b.month + 1}-${b.date}-${b.hours}`;
label = d.toLocaleDateString('de-DE', { day: '2-digit', month: 'short', timeZone: TIMEZONE }) + ', ' + b.hours.toString().padStart(2, '0') + ':00';
ts = new Date(b.year, b.month, b.date, b.hours).getTime();
} else {
key = `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}`;
label = d.toLocaleDateString('de-DE', { weekday: 'short', day: '2-digit', month: 'short' });
ts = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 12).getTime();
key = `${b.year}-${b.month + 1}-${b.date}`;
label = d.toLocaleDateString('de-DE', { weekday: 'short', day: '2-digit', month: 'short', timeZone: TIMEZONE });
ts = new Date(b.year, b.month, b.date, 12).getTime();
}
if (!bucketMap[key]) {
bucketMap[key] = { key, label, timestamp: ts, entries: [] };
@@ -1019,7 +1034,8 @@ const App = {
const ts = (granularity === 'day' && b.entries && b.entries.length > 0)
? new Date(b.entries[0].timestamp || 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;
};
@@ -1056,21 +1072,22 @@ const App = {
const markers = [];
buckets.forEach(b => {
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]) {
seen[dayKey] = true;
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const yesterday = new Date(today.getTime() - 86400000);
const bucketDay = new Date(d.getFullYear(), d.getMonth(), d.getDate());
const np = _tz(new Date());
const todayKey = `${np.year}-${np.month}-${np.date}`;
const yp = _tz(new Date(Date.now() - 86400000));
const yesterdayKey = `${yp.year}-${yp.month}-${yp.date}`;
let label;
const dateStr = d.toLocaleDateString('de-DE', { day: '2-digit', month: 'short' });
if (bucketDay.getTime() === today.getTime()) {
const dateStr = d.toLocaleDateString('de-DE', { day: '2-digit', month: 'short', timeZone: TIMEZONE });
if (dayKey === todayKey) {
label = 'Heute, ' + dateStr;
} else if (bucketDay.getTime() === yesterday.getTime()) {
} else if (dayKey === yesterdayKey) {
label = 'Gestern, ' + dateStr;
} 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);
markers.push({ text: label, pos });
@@ -1202,9 +1219,10 @@ const App = {
* Einträge nach Zeitperiode gruppieren.
*/
_groupByTimePeriod(entries, granularity) {
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const yesterday = new Date(today.getTime() - 86400000);
const np = _tz(new Date());
const todayKey = `${np.year}-${np.month}-${np.date}`;
const yp = _tz(new Date(Date.now() - 86400000));
const yesterdayKey = `${yp.year}-${yp.month}-${yp.date}`;
const groups = [];
let currentGroup = null;
@@ -1217,18 +1235,18 @@ const App = {
key = 'unknown';
label = 'Unbekannt';
} else if (granularity === 'hour') {
const h = d.getHours();
key = `${d.toDateString()}-${h}`;
label = `${h.toString().padStart(2, '0')}:00 Uhr`;
const ep = _tz(d);
key = `${ep.year}-${ep.month}-${ep.date}-${ep.hours}`;
label = `${ep.hours.toString().padStart(2, '0')}:00 Uhr`;
} else {
const entryDate = new Date(d.getFullYear(), d.getMonth(), d.getDate());
key = entryDate.toDateString();
if (entryDate.getTime() === today.getTime()) {
const ep = _tz(d);
key = `${ep.year}-${ep.month}-${ep.date}`;
if (key === todayKey) {
label = 'Heute';
} else if (entryDate.getTime() === yesterday.getTime()) {
} else if (key === yesterdayKey) {
label = 'Gestern';
} 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)
? article.published_at : article.collected_at;
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;
@@ -1322,7 +1340,7 @@ const App = {
*/
_renderSnapshotEntry(snapshot) {
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 = [];
@@ -1379,7 +1397,8 @@ const App = {
_getMinuteKey(timestamp) {
if (!timestamp) return 'none';
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 ===
@@ -1659,9 +1678,9 @@ const App = {
}
list.innerHTML = logs.map(log => {
const started = new Date(log.started_at);
const timeStr = started.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' }) + ' ' +
started.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
const started = parseUTC(log.started_at) || new Date(log.started_at);
const timeStr = started.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', timeZone: TIMEZONE }) + ' ' +
started.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', timeZone: TIMEZONE });
let detail = '';
if (log.status === 'completed') {
@@ -2967,7 +2986,7 @@ function buildDetailedSourceOverview() {
data.articles.forEach(a => {
const headline = UI.escape(a.headline_de || a.headline || 'Ohne Titel');
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'
? `<span class="lang-badge">${a.language.toUpperCase()}</span>` : '';