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:
@@ -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>` : '';
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren