diff --git a/app/Livewire/Project/Shared/ScheduledTask/Add.php b/app/Livewire/Project/Shared/ScheduledTask/Add.php index df49f6cf0e..a7b14e04c5 100644 --- a/app/Livewire/Project/Shared/ScheduledTask/Add.php +++ b/app/Livewire/Project/Shared/ScheduledTask/Add.php @@ -39,6 +39,9 @@ class Add extends Component public int $timeout = 300; + #[Locked] + public string $serverTimezone = 'UTC'; + protected $rules = [ 'name' => 'required|string', 'command' => 'required|string', @@ -74,6 +77,8 @@ class Add extends Component throw new \Exception('Invalid resource type'); } + $this->serverTimezone = getServerTimezone($this->resource->destination?->server); + if ($this->containerNames->count() > 0) { $this->container = $this->containerNames->first(); } diff --git a/app/Livewire/Project/Shared/ScheduledTask/Show.php b/app/Livewire/Project/Shared/ScheduledTask/Show.php index 5f5689b5de..1ba42b9d4e 100644 --- a/app/Livewire/Project/Shared/ScheduledTask/Show.php +++ b/app/Livewire/Project/Shared/ScheduledTask/Show.php @@ -45,12 +45,16 @@ class Show extends Component #[Validate(['integer', 'required', 'min:60', 'max:36000'])] public $timeout = 300; + #[Locked] + public string $serverTimezone = 'UTC'; + public function mount(Application|Service $resource, ScheduledTask $task): void { $this->resource = $resource; $this->type = $resource->type(); $this->parameters = get_route_parameters(); $this->task = $resource->scheduled_tasks()->whereKey($task->id)->firstOrFail(); + $this->serverTimezone = getServerTimezone($this->task->server()); $this->syncData(); } diff --git a/resources/views/livewire/project/shared/scheduled-task/add.blade.php b/resources/views/livewire/project/shared/scheduled-task/add.blade.php index b365bb4aef..dee7f3731c 100644 --- a/resources/views/livewire/project/shared/scheduled-task/add.blade.php +++ b/resources/views/livewire/project/shared/scheduled-task/add.blade.php @@ -2,7 +2,7 @@
diff --git a/resources/views/livewire/project/shared/scheduled-task/show.blade.php b/resources/views/livewire/project/shared/scheduled-task/show.blade.php index 481fd98253..66d6bb8afe 100644 --- a/resources/views/livewire/project/shared/scheduled-task/show.blade.php +++ b/resources/views/livewire/project/shared/scheduled-task/show.blade.php @@ -36,7 +36,7 @@ label="Name" required /> and(ScheduledJobState::query()->where('schedule_key', 'midnight')->value('last_scheduled_for')->toDateTimeString())->toBe('2026-10-03 04:00:00'); }); +it('runs a scheduled task in the server timezone, not in the instance timezone', function (string $serverTimezone, string $utcNow, bool $expectDispatch) { + config(['constants.coolify.self_hosted' => true]); + InstanceSettings::unguarded(fn () => InstanceSettings::firstOrCreate(['id' => 0]))->update(['instance_timezone' => 'Europe/Istanbul']); + Carbon::setTestNow(Carbon::parse($utcNow, 'UTC')); + Queue::fake(); + + $application = createScheduledTaskApplication(); + $application->destination->server->settings()->update(['server_timezone' => $serverTimezone]); + createScheduledApplicationTask($application, ['frequency' => '59 23 * * *']); + + (new ScheduledJobManager)->handle(); + + Queue::assertPushed(ScheduledTaskJob::class, $expectDispatch ? 1 : 0); +})->with([ + // 23:59 in Istanbul (UTC+3) is 20:59Z. + 'UTC server at 23:59 Istanbul' => ['UTC', '2026-10-26 20:59:00', false], + 'UTC server at 23:59 UTC' => ['UTC', '2026-10-26 23:59:00', true], + 'Istanbul server at 23:59 Istanbul' => ['Europe/Istanbul', '2026-10-26 20:59:00', true], +]); + function createStaleEnqueuedOccurrence(string $scheduleKey, string $jobType, int $resourceId, Carbon $scheduledFor, int $minutesAgo = ScheduledJobDeliveryService::ENQUEUED_STALE_AFTER_MINUTES + 1): ScheduledJobDelivery { return ScheduledJobDelivery::create([