From 5633d3f9f9831ac839db5269ff25f12f76b57d5c Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 23 Aug 2026 11:55:44 +0200 Subject: [PATCH] Lehrer-Live-Zeiten fest auf Europe/Vienna (Server laeuft UTC) -> immer oesterreichische Zeit _hhmm/_vdt/_fmtHM formatieren mit timeZone Europe/Vienna; _asMs normalisiert Epoch UND MySQL-UTC-Strings. Betrifft Tag-Achse+Balken, Session-Detail, "Heute erledigt", Cockpit "zuletzt aktiv", Reflexionen, letztes Lebenszeichen. Vorher zeigte new Date().getHours() je nach Geraet evtl. UTC. Co-Authored-By: Claude Opus 4.8 --- App/teacher.html | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/App/teacher.html b/App/teacher.html index 2001ca4..ddff25e 100644 --- a/App/teacher.html +++ b/App/teacher.html @@ -1082,7 +1082,7 @@ function _cockpitStudentRow(s) { sub += ' 📵 idle seit '+idleStr+' — Zeit pausiert'; } } else if (s.status === 'heute') { - var when = s.lastSeen ? new Date(s.lastSeen).toLocaleTimeString('de-AT',{hour:'2-digit',minute:'2-digit'}) : ''; + var when = s.lastSeen ? _hhmm(s.lastSeen) : ''; sub = 'heute aktiv · zuletzt um '+when+''; } else if (s.status === 'kalt') { sub = 'heute noch nicht eingeloggt'+(s.lastSeen?' · zuletzt '+_cockpitFmtAgo(Math.floor((Date.now()-new Date(s.lastSeen).getTime())/1000)):'')+''; @@ -1227,7 +1227,7 @@ async function _loadCockpitReflections() { (data.modules || []).forEach(function(m){ simIcons[m.id] = m.icon || ''; }); var html = '
'; items.forEach(function(it) { - var when = it.recordedAt ? new Date(it.recordedAt).toLocaleString('de-AT', {day:'2-digit',month:'2-digit',hour:'2-digit',minute:'2-digit'}) : ''; + var when = it.recordedAt ? _vdt(it.recordedAt) : ''; html += '
' + '
' + '' + _avatarHtml(it.avatarSlug, it.emoji, 1.2) + ' ' + esc(it.displayName||'—') + '' @@ -1368,8 +1368,19 @@ function _liveIsActive(ls) { return ls.last_seen_ms && (Date.now() - ls.last_seen_ms) <= _LIVE_PAUSE_SEC * 1000; } -// Uhrzeit HH:MM aus ms-Epoch. -function _hhmm(ms){ if(!ms) return '—'; var d=new Date(ms); return ('0'+d.getHours()).slice(-2)+':'+('0'+d.getMinutes()).slice(-2); } +// Uhrzeit/Datum IMMER in österreichischer Zeit (Europe/Vienna) — unabhängig von der +// Zeitzone des Servers (läuft in UTC) ODER des Anzeigegeräts. +// Eingabe: ms-Epoch (Zahl) ODER MySQL-Datetime-String (wird als UTC interpretiert). +function _asMs(t){ + if (t == null || t === '') return 0; + if (typeof t === 'number') return t; + var s = String(t).trim(); + if (/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}/.test(s) && !/[zZ]|[+-]\d{2}:?\d{2}$/.test(s)) + return new Date(s.replace(' ', 'T') + 'Z').getTime(); // DB-Zeit ist UTC + return new Date(s).getTime(); +} +function _hhmm(t){ var ms=_asMs(t); if(!ms) return '—'; return new Date(ms).toLocaleTimeString('de-AT',{timeZone:'Europe/Vienna',hour:'2-digit',minute:'2-digit'}); } +function _vdt(t){ var ms=_asMs(t); if(!ms) return '—'; return new Date(ms).toLocaleString('de-AT',{timeZone:'Europe/Vienna',day:'2-digit',month:'2-digit',hour:'2-digit',minute:'2-digit'}); } // Avatar als Bild rendern — egal ob der Slug in avatar_slug ODER als // „avatar:" im emoji_avatar-Feld steckt (Daten sind uneinheitlich). @@ -2160,8 +2171,7 @@ async function renderLiveSummary() { var winSpanMs = winEnd - winStart; function _msToPct(ms) { return ((ms - winStart) / winSpanMs) * 100; } function _fmtHM(ms) { - var d = new Date(ms); - return ('0'+d.getHours()).slice(-2) + ':' + ('0'+d.getMinutes()).slice(-2); + return new Date(ms).toLocaleTimeString('de-AT', {timeZone:'Europe/Vienna', hour:'2-digit', minute:'2-digit'}); } var activeCount = students.filter(function(s){return (s.sessions||[]).length>0;}).length; @@ -2292,8 +2302,8 @@ async function openLiveSessionDetail(sessionKey) { (sess.simIcon||'📚') + ' ' + (sess.simTitle||sess.simId) + (sess.lessonLabel?' · '+sess.lessonLabel:''); var body = document.getElementById('sessdet-body'); var avatarHtml = _avatarHtml(sess.studentAvatarSlug, sess.studentEmoji, 2); - var startTime = new Date(sess.startMs).toLocaleString('de-AT',{hour:'2-digit',minute:'2-digit',day:'2-digit',month:'2-digit'}); - var endTime = new Date(sess.endMs).toLocaleString('de-AT',{hour:'2-digit',minute:'2-digit'}); + var startTime = _vdt(sess.startMs); + var endTime = _hhmm(sess.endMs); var dur = _liveFmtMin(sess.durationSec||0); var stars = sess.stars; var starsHtml = stars != null @@ -2433,7 +2443,7 @@ async function renderSpectator(studentId) { html += '
Erfolgreich abgeschlossen: '+wonVal+'
'; } } - html += '
Aktualisiert alle 3 Sekunden · letztes Lebenszeichen: '+(data.last_seen_ms?new Date(data.last_seen_ms).toLocaleTimeString('de-AT'):'?')+'
'; + html += '
Aktualisiert alle 3 Sekunden · letztes Lebenszeichen: '+(data.last_seen_ms?_hhmm(data.last_seen_ms):'?')+'
'; host.innerHTML = html; }