fix: handle datetime-local timezone conversion correctly

- Convert datetime-local values to UTC ISO strings before sending to API
  (new Date(r).toISOString()) in both create and update flows
- Parse UTC ISO strings directly in API instead of manual Date.UTC() parsing
  that treated local components as UTC, causing timezone offset bugs
- Convert UTC API response times to local on load using new Date(str + 'Z')
  and date-fns format() to avoid double-timezone shift when editing
This commit is contained in:
Victor
2026-05-09 02:44:13 +00:00
parent 4b8d75617b
commit f4e3919417
4 changed files with 17 additions and 21 deletions

View File

@@ -77,15 +77,9 @@ export async function PUT(
nextOccurrence: nextOccurrence ? new Date(nextOccurrence) : undefined, nextOccurrence: nextOccurrence ? new Date(nextOccurrence) : undefined,
reminders: { reminders: {
deleteMany: {}, deleteMany: {},
create: (reminders || []).map((r: string) => { create: (reminders || []).map((r: string) => ({
// Parse local datetime string and construct Date to preserve local time reminder: new Date(r),
const [datePart, timePart] = r.split('T'); })),
const [year, month, day] = datePart.split('-').map(Number);
const [hours, minutes] = timePart.split(':').map(Number);
return {
reminder: new Date(Date.UTC(year, month - 1, day, hours, minutes)),
};
}),
}, },
}, },
include: { include: {

View File

@@ -93,15 +93,9 @@ export async function POST(request: NextRequest) {
parentTaskId: parentTaskId || undefined, parentTaskId: parentTaskId || undefined,
sortOrder: (maxSort._max.sortOrder ?? 0) + 1, sortOrder: (maxSort._max.sortOrder ?? 0) + 1,
reminders: { reminders: {
create: (reminders || []).map((r: string) => { create: (reminders || []).map((r: string) => ({
// Parse local datetime string and construct Date to preserve local time reminder: new Date(r),
const [datePart, timePart] = r.split('T'); })),
const [year, month, day] = datePart.split('-').map(Number);
const [hours, minutes] = timePart.split(':').map(Number);
return {
reminder: new Date(Date.UTC(year, month - 1, day, hours, minutes)),
};
}),
}, },
}, },
include: { include: {

View File

@@ -44,7 +44,11 @@ export default function EditTaskModal() {
.then(data => { .then(data => {
setSubtasks(data.subtasks || []); setSubtasks(data.subtasks || []);
if (data.reminders && data.reminders.length > 0) { if (data.reminders && data.reminders.length > 0) {
setReminders(data.reminders.map((r: any) => r.reminder)); // API returns UTC times — convert to local for datetime-local input
setReminders(data.reminders.map((r: any) => {
const d = new Date(r.reminder + 'Z');
return format(d, "yyyy-MM-dd'T'HH:mm");
}));
} }
}) })
.catch(console.error); .catch(console.error);
@@ -81,7 +85,9 @@ export default function EditTaskModal() {
status, status,
recurrenceRule, recurrenceRule,
recurrenceInterval, recurrenceInterval,
reminders: reminders.filter((r) => r !== ''), reminders: reminders
.filter((r) => r !== '')
.map((r) => new Date(r).toISOString()),
}), }),
}); });

View File

@@ -66,7 +66,9 @@ export default function NewTaskModal() {
status, status,
recurrenceRule, recurrenceRule,
recurrenceInterval, recurrenceInterval,
reminders: reminders.filter((r) => r !== ''), reminders: reminders
.filter((r) => r !== '')
.map((r) => new Date(r).toISOString()),
}), }),
}); });