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:
1 parent
5e98e9aa05
commit
98e0949d08
3 files changed
+38
-53
No files matched your search
@@ -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,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);
|
||||||
|
|||||||
@@ -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'
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in new issue
Block a user