Skip to content

Commit 70402ed

Browse files
fix(schedules): normalize default UTC timezone before declarative schedule diff (#5066)
1 parent aed0848 commit 70402ed

2 files changed

Lines changed: 45 additions & 5 deletions

File tree

‎apps/webapp/app/v3/services/createBackgroundWorker.server.test.ts‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,43 @@ describe("syncDeclarativeSchedules registration", () => {
488488
}
489489
);
490490

491+
containerTest(
492+
"preserves an existing Redis job when task schedule omits explicit UTC timezone",
493+
async ({ prisma, redisOptions }) => {
494+
const { project, prodEnv } = await seedProjectWithEnvs(prisma);
495+
const schedule = await makeDeclarativeSchedule(prisma, project.id, [prodEnv.id]);
496+
const updatedAt = schedule.updatedAt;
497+
await seedScheduledTask(prisma, project.id, prodEnv.id);
498+
const engine = createTestScheduleEngine(prisma, redisOptions);
499+
500+
try {
501+
const jobId = scheduleJobId(schedule.instances[0].id);
502+
await engine.registerNextTaskScheduleInstance({ instanceId: schedule.instances[0].id });
503+
const before = await engine.getJob(jobId);
504+
505+
// Omit explicit timezone to verify default UTC normalization does not trigger false timing change
506+
await syncDeclarativeSchedules(
507+
declarativeTasks({ cron: "0 * * * *" }),
508+
noWorker,
509+
asEnv(prodEnv),
510+
prisma,
511+
engine
512+
);
513+
514+
expect(before).toBeDefined();
515+
expect(await engine.getJob(jobId)).toEqual(before);
516+
expect(
517+
await prisma.taskSchedule.findUniqueOrThrow({
518+
where: { id: schedule.id },
519+
select: { updatedAt: true },
520+
})
521+
).toEqual({ updatedAt });
522+
} finally {
523+
await engine.quit();
524+
}
525+
}
526+
);
527+
491528
containerTest(
492529
"replaces the Redis job when declarative timing changes",
493530
async ({ prisma, redisOptions }) => {

‎apps/webapp/app/v3/services/createBackgroundWorker.server.ts‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1588,7 +1588,7 @@ export async function syncDeclarativeSchedules(
15881588
environment.projectId,
15891589
{
15901590
cron: task.schedule.cron,
1591-
timezone: task.schedule.timezone,
1591+
timezone: task.schedule.timezone ?? "UTC",
15921592
taskIdentifier: task.id,
15931593
friendlyId: existingSchedule?.friendlyId,
15941594
window: task.schedule.window,
@@ -1623,15 +1623,18 @@ export async function syncDeclarativeSchedules(
16231623
windowPercentage: normalizedWindow.windowPercentage,
16241624
defaultWindowDurationSeconds: existingSchedule.defaultWindowDurationSeconds,
16251625
}).window;
1626+
const taskTimezone = task.schedule.timezone ?? "UTC";
1627+
const existingTimezone = existingSchedule.timezone ?? "UTC";
1628+
const timezoneChanged = existingTimezone !== taskTimezone;
16261629
const timingChanged =
16271630
existingSchedule.generatorExpression !== task.schedule.cron ||
1628-
existingSchedule.timezone !== task.schedule.timezone ||
1631+
timezoneChanged ||
16291632
!scheduleWindowsEqual(previousWindow, nextWindow) ||
16301633
// Clearing the minimum changes the effective range.
16311634
existingSchedule.minimumWindowDurationSeconds !== minimumWindowDurationSeconds;
16321635
const persistedValuesChanged =
16331636
existingSchedule.generatorExpression !== task.schedule.cron ||
1634-
existingSchedule.timezone !== task.schedule.timezone ||
1637+
timezoneChanged ||
16351638
existingSchedule.windowDurationSeconds !== normalizedWindow.windowDurationSeconds ||
16361639
existingSchedule.windowPercentage !== normalizedWindow.windowPercentage ||
16371640
existingSchedule.minimumWindowDurationSeconds !== minimumWindowDurationSeconds;
@@ -1643,7 +1646,7 @@ export async function syncDeclarativeSchedules(
16431646
data: {
16441647
generatorExpression: task.schedule.cron,
16451648
generatorDescription: cronstrue.toString(task.schedule.cron),
1646-
timezone: task.schedule.timezone,
1649+
timezone: taskTimezone,
16471650
minimumWindowDurationSeconds,
16481651
...normalizedWindow,
16491652
},
@@ -1675,7 +1678,7 @@ export async function syncDeclarativeSchedules(
16751678
taskIdentifier: task.id,
16761679
generatorExpression: task.schedule.cron,
16771680
generatorDescription: cronstrue.toString(task.schedule.cron),
1678-
timezone: task.schedule.timezone,
1681+
timezone: task.schedule.timezone ?? "UTC",
16791682
type: "DECLARATIVE",
16801683
minimumWindowDurationSeconds,
16811684
...normalizedWindow,

0 commit comments

Comments
 (0)