Fix incorrect shift assignments due to timezone conversion errors
Address timezone discrepancies by implementing a function to calculate the correct offset for Europe/Rome, ensuring accurate conversion of shift start and end times from local time to UTC for assignment processing. 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/7VmWFMu
This commit is contained in:
parent
580fbfcaab
commit
74bd542309
4
.replit
4
.replit
@ -47,6 +47,10 @@ externalPort = 5000
|
|||||||
localPort = 43267
|
localPort = 43267
|
||||||
externalPort = 3003
|
externalPort = 3003
|
||||||
|
|
||||||
|
[[ports]]
|
||||||
|
localPort = 44369
|
||||||
|
externalPort = 6000
|
||||||
|
|
||||||
[env]
|
[env]
|
||||||
PORT = "5000"
|
PORT = "5000"
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,31 @@ import { differenceInDays, differenceInHours, differenceInMinutes, startOfWeek,
|
|||||||
import { z } from "zod";
|
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.
|
||||||
|
* 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
|
||||||
|
* @returns offset in ore (1 o 2)
|
||||||
|
*/
|
||||||
|
function getItalyTimezoneOffsetHours(date: Date): number {
|
||||||
|
const year = date.getFullYear();
|
||||||
|
|
||||||
|
// Calcola ultima domenica di marzo (inizio ora legale)
|
||||||
|
const marchLastSunday = new Date(year, 2, 31); // 31 marzo
|
||||||
|
marchLastSunday.setDate(31 - marchLastSunday.getDay()); // torna indietro alla domenica
|
||||||
|
|
||||||
|
// Calcola ultima domenica di ottobre (fine ora legale)
|
||||||
|
const octoberLastSunday = new Date(year, 9, 31); // 31 ottobre
|
||||||
|
octoberLastSunday.setDate(31 - octoberLastSunday.getDay()); // torna indietro alla domenica
|
||||||
|
|
||||||
|
// L'ora legale inizia alle 02:00 UTC dell'ultima domenica di marzo
|
||||||
|
// e termina alle 03:00 UTC dell'ultima domenica di ottobre
|
||||||
|
const isDST = date >= marchLastSunday && date < octoberLastSunday;
|
||||||
|
|
||||||
|
// UTC+1 (inverno) o UTC+2 (estate)
|
||||||
|
return isDST ? 2 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
// Determina quale sistema auth usare basandosi sull'ambiente
|
// Determina quale sistema auth usare basandosi sull'ambiente
|
||||||
const USE_LOCAL_AUTH = process.env.DOMAIN === "vt.alfacom.it" || !process.env.REPLIT_DOMAINS;
|
const USE_LOCAL_AUTH = process.env.DOMAIN === "vt.alfacom.it" || !process.env.REPLIT_DOMAINS;
|
||||||
|
|
||||||
@ -1283,6 +1308,15 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
try {
|
try {
|
||||||
const { siteId, date, guardId, startTime, durationHours, consecutiveDays = 1, vehicleId, force = false } = req.body;
|
const { siteId, date, guardId, startTime, durationHours, consecutiveDays = 1, vehicleId, force = false } = req.body;
|
||||||
|
|
||||||
|
// DEBUG: Log per capire il problema timezone
|
||||||
|
console.log("🔍 DEBUG assign-guard - Input ricevuto:", {
|
||||||
|
date,
|
||||||
|
startTime,
|
||||||
|
durationHours,
|
||||||
|
serverTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||||
|
serverOffset: new Date().getTimezoneOffset()
|
||||||
|
});
|
||||||
|
|
||||||
if (!siteId || !date || !guardId || !startTime || !durationHours) {
|
if (!siteId || !date || !guardId || !startTime || !durationHours) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
message: "Missing required fields: siteId, date, guardId, startTime, durationHours"
|
message: "Missing required fields: siteId, date, guardId, startTime, durationHours"
|
||||||
@ -1312,6 +1346,16 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
}
|
}
|
||||||
const [hours, minutes] = startTime.split(":").map(Number);
|
const [hours, minutes] = startTime.split(":").map(Number);
|
||||||
|
|
||||||
|
// CRITICAL: Gli orari dal frontend sono in fuso orario Europe/Rome (UTC+1 o UTC+2)
|
||||||
|
// Calcola offset corretto per convertire a UTC
|
||||||
|
const italyOffsetHours = getItalyTimezoneOffsetHours(new Date(year, month - 1, day));
|
||||||
|
console.log("🕐 DEBUG Timezone setup:", {
|
||||||
|
inputDate: date,
|
||||||
|
inputTime: `${String(hours).padStart(2,'0')}:${String(minutes).padStart(2,'0')}`,
|
||||||
|
italyOffsetHours,
|
||||||
|
isDST: italyOffsetHours === 2
|
||||||
|
});
|
||||||
|
|
||||||
// Atomic transaction: create assignments for all consecutive days
|
// Atomic transaction: create assignments for all consecutive days
|
||||||
const result = await db.transaction(async (tx) => {
|
const result = await db.transaction(async (tx) => {
|
||||||
const createdAssignments = [];
|
const createdAssignments = [];
|
||||||
@ -1342,9 +1386,20 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate planned start and end times in LOCAL timezone
|
// Calculate planned start and end times converting from Europe/Rome to UTC
|
||||||
const plannedStart = new Date(actualYear, actualMonth, actualDay, hours, minutes, 0, 0);
|
// IMPORTANTE: l'utente inserisce orari in fuso orario Italia (Europe/Rome)
|
||||||
const plannedEnd = new Date(actualYear, actualMonth, actualDay, hours + durationHours, minutes, 0, 0);
|
// Il server è in UTC, quindi dobbiamo convertire Italy time → UTC
|
||||||
|
// Formula: UTC time = Italy time - offset
|
||||||
|
// Esempio: 09:00 Italy (UTC+2) = 07:00 UTC
|
||||||
|
const plannedStart = new Date(Date.UTC(actualYear, actualMonth, actualDay, hours - italyOffsetHours, minutes, 0, 0));
|
||||||
|
const plannedEnd = new Date(Date.UTC(actualYear, actualMonth, actualDay, hours + durationHours - italyOffsetHours, minutes, 0, 0));
|
||||||
|
|
||||||
|
console.log("🕐 DEBUG Timestamp conversion:", {
|
||||||
|
inputTime: `${String(hours).padStart(2,'0')}:${String(minutes).padStart(2,'0')}`,
|
||||||
|
italyOffset: `UTC+${italyOffsetHours}`,
|
||||||
|
plannedStartUTC: plannedStart.toISOString(),
|
||||||
|
plannedEndUTC: plannedEnd.toISOString()
|
||||||
|
});
|
||||||
|
|
||||||
// Find or create shift for this site/date (full day boundaries in LOCAL timezone)
|
// Find or create shift for this site/date (full day boundaries in LOCAL timezone)
|
||||||
const dayStart = new Date(actualYear, actualMonth, actualDay, 0, 0, 0, 0);
|
const dayStart = new Date(actualYear, actualMonth, actualDay, 0, 0, 0, 0);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user