Chapter 8 · Integrations / Automation Engineer

Scheduling logic

A scheduler's job boils down to one repeated question: "is it time to run yet?" A loop wakes up periodically, asks that question, and fires the job when the answer is yes. The hard part is writing that decision so it's correct and testable.

The single most important trick: pass the time in as an argument — don't read the clock inside the function.

``
def should_run(last_run, interval, now): # GOOD — now is passed in
return now - last_run >= interval
``

If the function calls time.time() or datetime.now() internally, you can't test it — the answer changes every second and you can't reproduce a bug. Pass now in and the function is deterministic: the same inputs always give the same answer, so you can test "what happens at exactly 07:00" without waiting until 7am.

Common scheduling shapes:

  • Interval: fire if now - last_run >= interval_seconds. A last_run of 0 means "never run" — so it's immediately due.
  • Cron-style at-an-hour: fire if it's the target hour and we haven't already run today (now.date() != last_run_date). The "already ran today" guard is what stops it firing 60 times during the target hour.
  • Interval math: a frequent cron idiom is minute % interval == 0 — fire on minute 0, 5, 10, … for a 5-minute interval.

You can also compute how long until the next run: next_due - now, which goes negative when you're overdue.

Syntax

import datetime

# Interval: due if enough time has passed (last_run=0 means never)
def should_run_interval(last_run_unix, interval_s, now_unix):
    return now_unix - last_run_unix >= interval_s

# Cron-at-hour: right hour AND not already today
def should_run_at_hour(target_hour, last_run_date, now_dt):
    return now_dt.hour == target_hour and now_dt.date() != last_run_date

# every-N-minutes idiom
# now_dt.minute % interval == 0

Worked examples

Interval check — deterministic, time passed in

def should_run_interval(last_run_unix, interval_s, now_unix):
    return now_unix - last_run_unix >= interval_s

print(should_run_interval(1000, 60, 1061))   # True  (61s passed)
print(should_run_interval(1000, 60, 1060))   # True  (exactly at boundary)
print(should_run_interval(1000, 60, 1059))   # False (1s too soon)
print(should_run_interval(0, 60, 1000))      # True  (never run → due)
# Output:
# True
# True
# False
# True

Run once a day at a target hour

import datetime

def should_run_at_hour(target_hour, last_run_date, now_dt):
    if now_dt.hour != target_hour:
        return False
    if last_run_date is not None and now_dt.date() == last_run_date:
        return False
    return True

now = datetime.datetime(2026, 6, 14, 7, 30)
print(should_run_at_hour(7, datetime.date(2026, 6, 13), now))  # True  (new day)
print(should_run_at_hour(7, datetime.date(2026, 6, 14), now))  # False (ran today)
print(should_run_at_hour(7, None, datetime.datetime(2026, 6, 14, 9, 0)))  # False (wrong hour)
# Output:
# True
# False
# False

Time until next run (negative = overdue) + every-N-minutes

def next_run_in(last_run_unix, interval_s, now_unix):
    return (last_run_unix + interval_s) - now_unix

print(next_run_in(1000, 60, 1020))   # 40   (not due for 40s)
print(next_run_in(1000, 60, 1100))   # -40  (overdue by 40s)

# cron 'every 5 minutes' idiom
fires = [m for m in range(0, 16) if m % 5 == 0]
print(fires)
# Output:
# 40
# -40
# [0, 5, 10, 15]

Common mistakes & gotchas

Reading the clock inside the function

Calling time.time() or datetime.now() inside the decision makes it untestable and non-reproducible — every run gives a different answer. Pass now in as a parameter; let only the outermost loop touch the real clock. This is the whole reason the examples are deterministic.

Missing the 'already ran today' guard

An hour-based check that only tests now.hour == target_hour will fire on *every* tick during that hour — potentially 60 times. You need the second condition (now.date() != last_run_date) to ensure it runs once per day, not once per minute for an hour.

> vs >= at the interval boundary

now - last_run > interval skips the run at the exact boundary (e.g. exactly 60s after a 60s interval), so the job drifts a tick later every cycle. Use >= so it fires *at* the boundary. One character decides whether a daily job slowly creeps off-schedule.

Why it matters

Every cron job, polling loop, and recurring task is built on "is it time yet?" logic. Writing that decision deterministically — with the time passed in, not read from the clock — is what lets you unit-test scheduling without waiting hours for the real moment, and it's why production schedulers can prove they'll fire exactly when they should.