Scheduler

Recurring work

# Scheduler

```pascal
uses Askr.Scheduler;

SetSchedule(TScheduler.Create(Queue));

Schedule.EveryMinutes(5, 'poll-inbox');
Schedule.Hourly('warm-cache');
Schedule.DailyAt(3, 30, 'nightly-report');
Schedule.WeeklyAt(dowMonday, 8, 0, 'weekly-digest');
Schedule.MonthlyAt(1, 6, 0, 'invoice-run', Payload);
Schedule.EverySeconds(30, 'heartbeat');

Schedule.Start;
```

## It dispatches; it never executes

**The scheduler pushes to the queue and never runs anything itself.**

Do not "simplify" that to running the job directly. It would give two
execution paths with their own lifetime rules — one with a worker arena and
one without — and they would drift apart. Same reason the durable queue
reuses `TQueue` rather than having its own loop.

It follows that the scheduler needs a queue, and that a scheduled job is an
ordinary job: `Queue.Handle('nightly-report', @NightlyReport)`.

## UTC

**The next run time is computed in UTC.** Local time would give two runs or
none at the daylight-saving change.

A `DailyAt(3, 30, ...)` is 03:30 UTC. If you need local wall-clock time, you
need a timezone database, and Askr does not ship one.

## Overlap

```pascal
Schedule.SkipWhenPending;
```

Skips a dispatch when the same job is still queued. Without it, a job that
takes longer than its interval piles up.

## Running it

```pascal
Schedule.Start;     { a thread that ticks }
Schedule.Stop;
```

```pascal
N := Schedule.Tick;     { dispatch what is due, once; returns how many }
```

`Tick` is what `askr schedule:run` calls — one pass, for a cron that drives
the app from outside instead of letting it tick itself.

```sh
askr schedule:list
askr schedule:run
```

## Inspecting

```pascal nocheck
Schedule.Count;
Schedule.Describe(Lines);
Schedule.Ticks;  Schedule.Dispatched;
```