let serverTimeOffset = 0; let intervalId = null; function formatLocalTime(date) { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0'); const tenths = Math.floor(date.getMilliseconds() / 100); return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${tenths}`; } function getTimezone() { const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; const offset = new Date().getTimezoneOffset(); const offsetHours = Math.floor(Math.abs(offset) / 60); const offsetMinutes = Math.abs(offset) % 60; const offsetSign = offset <= 0 ? '+' : '-'; const offsetString = `UTC${offsetSign}${offsetHours.toString().padStart(2, '0')}:${offsetMinutes.toString().padStart(2, '0')}`; return `${timezone} (${offsetString})`; } function formatUTCTime(date) { const year = date.getUTCFullYear(); const month = String(date.getUTCMonth() + 1).padStart(2, '0'); const day = String(date.getUTCDate()).padStart(2, '0'); const hours = String(date.getUTCHours()).padStart(2, '0'); const minutes = String(date.getUTCMinutes()).padStart(2, '0'); const seconds = String(date.getUTCSeconds()).padStart(2, '0'); const tenths = Math.floor(date.getUTCMilliseconds() / 100); return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${tenths}`; } function updateDisplay() { const now = new Date(); const correctedTime = new Date(now.getTime() + serverTimeOffset); // Local time with timezone (using corrected time) const timeString = formatLocalTime(correctedTime); const timezone = now.toLocaleTimeString('en-US', { timeZoneName: 'short' }).split(' ').pop(); document.getElementById('time-display').textContent = `${timeString} ${timezone}`; // UTC time (using corrected time) const utcTimeString = formatUTCTime(correctedTime); document.getElementById('utc-display').textContent = `${utcTimeString} UTC`; // Display offset const offsetMs = Math.abs(serverTimeOffset); // If serverTimeOffset is positive, server is ahead, so local clock is slow // If serverTimeOffset is negative, server is behind, so local clock is fast const status = serverTimeOffset > 0 ? 'slow' : 'fast'; if (offsetMs < 10000) { // Less than 10 seconds document.getElementById('offset-display').textContent = `${Math.round(offsetMs)}ms ${status}`; } else { const offsetSeconds = (offsetMs / 1000).toFixed(1); document.getElementById('offset-display').textContent = `${offsetSeconds}s ${status}`; } document.getElementById('timezone-display').style.display = 'none'; } async function fetchServerTime() { try { const roundtrips = []; // Check if we're on localhost const isLocalhost = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1' || window.location.hostname.startsWith('192.168.') || window.location.hostname.startsWith('10.'); const url = isLocalhost ? '/api/time' : window.location.href; // Make three sequential requests to measure latency for (let i = 0; i < 3; i++) { const startTime = Date.now(); const response = await fetch(url, { method: isLocalhost ? 'GET' : 'HEAD' }); const endTime = Date.now(); const roundtripTime = endTime - startTime; if (isLocalhost) { // Parse JSON response from our backend const timeData = await response.json(); const serverTime = timeData.utc_epoch_milliseconds; const localTime = startTime + (roundtripTime / 2); const offset = serverTime - localTime; console.log('Local backend sync:', { serverTime: new Date(serverTime).toISOString(), localTime: new Date(localTime).toISOString(), roundtripTime: roundtripTime + 'ms', offset: offset + 'ms' }); roundtrips.push({ roundtripTime, offset }); } else { const serverDateHeader = response.headers.get('Date'); if (serverDateHeader) { const serverTime = new Date(serverDateHeader).getTime(); const localTime = startTime + (roundtripTime / 2); // Estimate time when server processed request const offset = serverTime - localTime; roundtrips.push({ roundtripTime, offset }); } } } if (roundtrips.length > 0) { // Calculate average offset const avgOffset = roundtrips.reduce((sum, rt) => sum + rt.offset, 0) / roundtrips.length; serverTimeOffset = avgOffset; // Log for debugging console.log('Time sync complete:', { roundtrips: roundtrips.map(rt => rt.roundtripTime + 'ms'), averageOffset: avgOffset.toFixed(3) + 'ms' }); } } catch (error) { console.error('Failed to fetch server time:', error); } } async function init() { await fetchServerTime(); updateDisplay(); if (intervalId) { clearInterval(intervalId); } intervalId = setInterval(updateDisplay, 100); setInterval(fetchServerTime, 60000); } init(); document.addEventListener('visibilitychange', () => { if (!document.hidden) { fetchServerTime(); } });