Adjust time display to show correct local times for users

Update time formatting logic to consistently display scheduled times in the 'Europe/Rome' timezone, resolving inconsistencies caused by UTC storage.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: e0b5b11c-5b75-4389-8ea9-5f3cd9332f88
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/6d543d2c-20b9-4ea6-93fe-70fe9b1d9f80/e0b5b11c-5b75-4389-8ea9-5f3cd9332f88/HO4k2VG
This commit is contained in:
marco370 2025-10-24 14:22:57 +00:00
parent 74bd542309
commit 468d6477eb
5 changed files with 86 additions and 32 deletions

View File

@ -19,6 +19,10 @@ externalPort = 80
localPort = 33035 localPort = 33035
externalPort = 3001 externalPort = 3001
[[ports]]
localPort = 34027
externalPort = 6000
[[ports]] [[ports]]
localPort = 41295 localPort = 41295
externalPort = 5173 externalPort = 5173
@ -47,10 +51,6 @@ externalPort = 5000
localPort = 43267 localPort = 43267
externalPort = 3003 externalPort = 3003
[[ports]]
localPort = 44369
externalPort = 6000
[env] [env]
PORT = "5000" PORT = "5000"

View File

@ -85,14 +85,14 @@ interface GeneralPlanningResponse {
} }
// Helper per formattare orario in formato italiano 24h (HH:MM) // Helper per formattare orario in formato italiano 24h (HH:MM)
// IMPORTANTE: usa timeZone UTC per evitare shift di +2 ore // IMPORTANTE: Gli orari nel DB sono UTC, visualizzali in timezone Europe/Rome
const formatTime = (dateString: string) => { const formatTime = (dateString: string) => {
const date = new Date(dateString); const date = new Date(dateString);
return date.toLocaleTimeString("it-IT", { return date.toLocaleTimeString("it-IT", {
hour: "2-digit", hour: "2-digit",
minute: "2-digit", minute: "2-digit",
hour12: false, hour12: false,
timeZone: "UTC" // Evita conversione timezone locale (+2h in Italia) timeZone: "Europe/Rome" // Converti da UTC a Italy time
}); });
}; };

View File

@ -159,21 +159,31 @@ export default function MyShiftsFixed() {
</div> </div>
) : ( ) : (
dayShifts.map((shift) => { dayShifts.map((shift) => {
// Parsing sicuro orari // Parsing sicuro orari (DB in UTC → visualizza in Europe/Rome)
let startTime = "N/A"; let startTime = "N/A";
let endTime = "N/A"; let endTime = "N/A";
if (shift.plannedStartTime) { if (shift.plannedStartTime) {
const parsedStart = parseISO(shift.plannedStartTime); const parsedStart = new Date(shift.plannedStartTime);
if (isValid(parsedStart)) { if (isValid(parsedStart)) {
startTime = format(parsedStart, "HH:mm"); startTime = parsedStart.toLocaleTimeString("it-IT", {
hour: "2-digit",
minute: "2-digit",
hour12: false,
timeZone: "Europe/Rome"
});
} }
} }
if (shift.plannedEndTime) { if (shift.plannedEndTime) {
const parsedEnd = parseISO(shift.plannedEndTime); const parsedEnd = new Date(shift.plannedEndTime);
if (isValid(parsedEnd)) { if (isValid(parsedEnd)) {
endTime = format(parsedEnd, "HH:mm"); endTime = parsedEnd.toLocaleTimeString("it-IT", {
hour: "2-digit",
minute: "2-digit",
hour12: false,
timeZone: "Europe/Rome"
});
} }
} }

View File

@ -201,21 +201,31 @@ export default function SitePlanningView() {
</div> </div>
) : ( ) : (
dayGuards.map((guard, index) => { dayGuards.map((guard, index) => {
// Parsing sicuro orari // Parsing sicuro orari (DB in UTC → visualizza in Europe/Rome)
let startTime = "N/A"; let startTime = "N/A";
let endTime = "N/A"; let endTime = "N/A";
if (guard.plannedStartTime) { if (guard.plannedStartTime) {
const parsedStart = parseISO(guard.plannedStartTime); const parsedStart = new Date(guard.plannedStartTime);
if (isValid(parsedStart)) { if (isValid(parsedStart)) {
startTime = format(parsedStart, "HH:mm"); startTime = parsedStart.toLocaleTimeString("it-IT", {
hour: "2-digit",
minute: "2-digit",
hour12: false,
timeZone: "Europe/Rome"
});
} }
} }
if (guard.plannedEndTime) { if (guard.plannedEndTime) {
const parsedEnd = parseISO(guard.plannedEndTime); const parsedEnd = new Date(guard.plannedEndTime);
if (isValid(parsedEnd)) { if (isValid(parsedEnd)) {
endTime = format(parsedEnd, "HH:mm"); endTime = parsedEnd.toLocaleTimeString("it-IT", {
hour: "2-digit",
minute: "2-digit",
hour12: false,
timeZone: "Europe/Rome"
});
} }
} }

View File

@ -11,28 +11,62 @@ import { z } from "zod";
import { fromZodError } from "zod-validation-error"; import { fromZodError } from "zod-validation-error";
/** /**
* Calcola l'offset di Europe/Rome rispetto a UTC per una data specifica. * Calcola l'offset di Europe/Rome rispetto a UTC per una specifica data/ora.
* Gestisce correttamente orari serali e transizioni DST.
* Italy: UTC+1 (ora solare inverno) / UTC+2 (ora legale estate) * Italy: UTC+1 (ora solare inverno) / UTC+2 (ora legale estate)
* L'ora legale va dall'ultima domenica di marzo all'ultima domenica di ottobre * @param year, month (0-11), day, hour, minute
* @returns offset in ore (1 o 2) * @returns offset in ore (1 o 2)
*/ */
function getItalyTimezoneOffsetHours(date: Date): number { function getItalyTimezoneOffsetHours(year: number, month: number, day: number, hour: number, minute: number = 0): number {
const year = date.getFullYear(); // Crea timestamp UTC esatto per l'input Italy time
// Useremo formatToParts per ottenere tutti i componenti date/time
const utcTimestamp = Date.UTC(year, month, day, hour, minute, 0);
const utcDate = new Date(utcTimestamp);
// Calcola ultima domenica di marzo (inizio ora legale) // Ottieni tutti i componenti in Europe/Rome timezone
const marchLastSunday = new Date(year, 2, 31); // 31 marzo const formatter = new Intl.DateTimeFormat('en-US', {
marchLastSunday.setDate(31 - marchLastSunday.getDay()); // torna indietro alla domenica timeZone: 'Europe/Rome',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
hour12: false
});
// Calcola ultima domenica di ottobre (fine ora legale) const parts = formatter.formatToParts(utcDate);
const octoberLastSunday = new Date(year, 9, 31); // 31 ottobre const italyYear = parseInt(parts.find(p => p.type === 'year')?.value || '0');
octoberLastSunday.setDate(31 - octoberLastSunday.getDay()); // torna indietro alla domenica const italyMonth = parseInt(parts.find(p => p.type === 'month')?.value || '0') - 1;
const italyDay = parseInt(parts.find(p => p.type === 'day')?.value || '0');
let italyHour = parseInt(parts.find(p => p.type === 'hour')?.value || '0');
const italyMinute = parseInt(parts.find(p => p.type === 'minute')?.value || '0');
// L'ora legale inizia alle 02:00 UTC dell'ultima domenica di marzo // formatToParts restituisce hour=24 per mezzanotte (00:00 del giorno successivo)
// e termina alle 03:00 UTC dell'ultima domenica di ottobre // Il giorno è già stato incrementato automaticamente da formatToParts
const isDST = date >= marchLastSunday && date < octoberLastSunday; // Normalizziamo solo l'ora: 24:00 → 00:00
const normalizedHour = italyHour === 24 ? 0 : italyHour;
// UTC+1 (inverno) o UTC+2 (estate) // Crea timestamp Italy come se fosse UTC (per calcolare differenza)
return isDST ? 2 : 1; const italyAsUtcTimestamp = Date.UTC(italyYear, italyMonth, italyDay, normalizedHour, italyMinute, 0);
// Calcola differenza in millisecondi e converti in ore
const offsetMs = italyAsUtcTimestamp - utcTimestamp;
const offsetHours = Math.round(offsetMs / (1000 * 60 * 60));
console.log("🕐 Offset calculation:", {
input: `${year}-${String(month+1).padStart(2,'0')}-${String(day).padStart(2,'0')} ${String(hour).padStart(2,'0')}:${String(minute).padStart(2,'0')}`,
utcTimestamp: utcDate.toISOString(),
italyComponents: { year: italyYear, month: italyMonth+1, day: italyDay, hour: italyHour, minute: italyMinute },
offsetHours
});
// Italy è sempre UTC+1 o UTC+2
if (offsetHours !== 1 && offsetHours !== 2) {
console.error("⚠️ Unexpected offset:", offsetHours, "- defaulting to UTC+1");
return 1;
}
return offsetHours;
} }
// Determina quale sistema auth usare basandosi sull'ambiente // Determina quale sistema auth usare basandosi sull'ambiente
@ -1348,7 +1382,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
// CRITICAL: Gli orari dal frontend sono in fuso orario Europe/Rome (UTC+1 o UTC+2) // CRITICAL: Gli orari dal frontend sono in fuso orario Europe/Rome (UTC+1 o UTC+2)
// Calcola offset corretto per convertire a UTC // Calcola offset corretto per convertire a UTC
const italyOffsetHours = getItalyTimezoneOffsetHours(new Date(year, month - 1, day)); const italyOffsetHours = getItalyTimezoneOffsetHours(year, month - 1, day, hours, minutes);
console.log("🕐 DEBUG Timezone setup:", { console.log("🕐 DEBUG Timezone setup:", {
inputDate: date, inputDate: date,
inputTime: `${String(hours).padStart(2,'0')}:${String(minutes).padStart(2,'0')}`, inputTime: `${String(hours).padStart(2,'0')}:${String(minutes).padStart(2,'0')}`,