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