Fix timezone consistency: apply server timezone everywhere in the app

- Extract getServerTimezone() helper in timeUtils.js; apply it in
  formatChartTime and formatLocalTimeOnly (previously ignored TZ)
- Status.js: remove local formatLocalTime (was bare toLocaleString),
  import the shared util that reads server timezone
- SettingsWizard: immediately update localStorage when timezone is saved
  so new value takes effect without waiting for cache to expire

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 4.6 committed 2026-08-06 16:48:12 -04:00
1 parent 5e98e9aa05
commit 98e0949d08
3 files changed
+38 -53

No files matched your search

+4
View File
@@ -61,6 +61,10 @@ function SettingsWizard() {
setSavedMessage(null); setSavedMessage(null);
try { try {
await axios.post('/api/settings', sectionData); await axios.post('/api/settings', sectionData);
if (sectionData.timezone) {
localStorage.setItem('serverTimezone', sectionData.timezone);
localStorage.setItem('serverTimezoneTime', Date.now().toString());
}
setSavedMessage({ section: sectionName, success: true }); setSavedMessage({ section: sectionName, success: true });
// Clear message after 3 seconds // Clear message after 3 seconds
setTimeout(() => setSavedMessage(null), 3000); setTimeout(() => setSavedMessage(null), 3000);
+1 -8
View File
@@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import axios from 'axios'; import axios from 'axios';
import { formatLocalTime } from './utils/timeUtils';
import './Status.css'; import './Status.css';
function Status() { function Status() {
@@ -31,14 +32,6 @@ function Status() {
} }
}; };
const formatLocalTime = (timestamp) => {
try {
return new Date(timestamp).toLocaleString();
} catch {
return timestamp;
}
};
const formatDuration = (ms) => { const formatDuration = (ms) => {
const seconds = Math.floor(ms / 1000); const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60); const minutes = Math.floor(seconds / 60);
+33 -45
View File
@@ -9,6 +9,21 @@ export const getCurrentTimezone = () => {
return Intl.DateTimeFormat().resolvedOptions().timeZone; return Intl.DateTimeFormat().resolvedOptions().timeZone;
}; };
const getServerTimezone = () => {
try {
const tz = localStorage.getItem('serverTimezone');
const ts = localStorage.getItem('serverTimezoneTime');
if (tz && ts && (Date.now() - parseInt(ts)) < 3600000 && tz !== 'null' && tz !== 'undefined' && tz.trim() !== '') {
return tz.replace(/^["']|["']$/g, '').trim();
}
if (tz) {
localStorage.removeItem('serverTimezone');
localStorage.removeItem('serverTimezoneTime');
}
} catch (_) {}
return null;
};
/** /**
* Format a timestamp to local time string with timezone info * Format a timestamp to local time string with timezone info
* Uses server timezone from localStorage if available, otherwise browser timezone * Uses server timezone from localStorage if available, otherwise browser timezone
@@ -23,40 +38,19 @@ export const formatLocalTime = (timestamp, options = {}) => {
} }
const date = new Date(ts); const date = new Date(ts);
const tz = getServerTimezone();
// Try to get server timezone from settings API response (cached in localStorage)
let timeZone;
try {
const cachedSettings = localStorage.getItem('serverTimezone');
const cacheTime = localStorage.getItem('serverTimezoneTime');
const now = Date.now();
// Invalidate cache every 1 hour
if (cachedSettings && cacheTime && (now - parseInt(cacheTime)) < 3600000 && cachedSettings !== 'null' && cachedSettings !== 'undefined' && cachedSettings.trim() !== '') {
// Remove any quotes that might be in the string
timeZone = cachedSettings.replace(/^["']|["']$/g, '').trim();
} else if (cachedSettings) {
// Clear stale cache
localStorage.removeItem('serverTimezone');
localStorage.removeItem('serverTimezoneTime');
}
} catch (e) {
// Timezone load error handled silently
}
const defaultOptions = { const defaultOptions = {
year: 'numeric', year: 'numeric',
month: 'short', month: 'short',
day: '2-digit', day: '2-digit',
hour: '2-digit', hour: '2-digit',
minute: '2-digit', minute: '2-digit',
second: '2-digit', second: '2-digit',
timeZoneName: 'short' timeZoneName: 'short',
...(tz ? { timeZone: tz } : {})
}; };
if (timeZone) {
defaultOptions.timeZone = timeZone;
}
return date.toLocaleString('en-US', { ...defaultOptions, ...options }); return date.toLocaleString('en-US', { ...defaultOptions, ...options });
}; };
@@ -65,15 +59,17 @@ export const formatLocalTime = (timestamp, options = {}) => {
*/ */
export const formatLocalTimeOnly = (timestamp, options = {}) => { export const formatLocalTimeOnly = (timestamp, options = {}) => {
if (!timestamp) return 'Never'; if (!timestamp) return 'Never';
const date = new Date(timestamp); const date = new Date(timestamp);
const tz = getServerTimezone();
const defaultOptions = { const defaultOptions = {
hour: '2-digit', hour: '2-digit',
minute: '2-digit', minute: '2-digit',
second: '2-digit', second: '2-digit',
timeZoneName: 'short' timeZoneName: 'short',
...(tz ? { timeZone: tz } : {})
}; };
return date.toLocaleTimeString('en-US', { ...defaultOptions, ...options }); return date.toLocaleTimeString('en-US', { ...defaultOptions, ...options });
}; };
@@ -83,26 +79,18 @@ export const formatLocalTimeOnly = (timestamp, options = {}) => {
*/ */
export const formatChartTime = (timestamp) => { export const formatChartTime = (timestamp) => {
if (!timestamp) return ''; if (!timestamp) return '';
const date = new Date(timestamp); const date = new Date(timestamp);
const now = new Date(); const now = new Date();
const diffDays = Math.floor((now - date) / (1000 * 60 * 60 * 24)); const diffDays = Math.floor((now - date) / (1000 * 60 * 60 * 24));
const tz = getServerTimezone();
// If within last 24 hours, show time only const tzOpt = tz ? { timeZone: tz } : {};
if (diffDays === 0) { if (diffDays === 0) {
return date.toLocaleTimeString('en-US', { return date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', ...tzOpt });
hour: '2-digit',
minute: '2-digit'
});
} }
// Otherwise show date and time return date.toLocaleString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', ...tzOpt });
return date.toLocaleString('en-US', {
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
}; };
/** /**