docs/web/cron-jobs.md#timezone tells users to set cron.timezone to match Django's TIME_ZONE, but nothing verifies it. Default cron.timezone is GMT, so a project on a non-UTC TIME_ZONE gets every pg_cron-scheduled task firing hours off — silently, and only in production where pg_cron is the scheduler.
Beat interprets cron expressions in TIME_ZONE (scheduler.py get_next_datetime → timezone.localtime); pg_cron interprets them in cron.timezone. Same schedule, different wall-clock time, no signal.
Ship
An informational check (absurd.I001), not a warning or error — the mismatch is legitimate if intended.
@register(Tags.database, "absurd") with the databases guard, mirroring check_absurd_queue_state.
- Only when the pg_cron app is installed and the resolved scheduler is pg_cron.
- Read
cron.timezone from pg_settings — it's a server-level GUC, readable from any database on that server, so no central-database connection needed (unlike the cron.database_name work).
- Compare resolved UTC offsets, not names:
GMT/UTC/Etc/UTC are equivalent, America/Chicago is not.
msg states the mismatch (the two zones and that schedules fire at different wall-clock times); hint gives the cron.timezone = '<TIME_ZONE>' line for postgresql.conf.
Note
Tags.database checks don't run under a bare manage.py check — only manage.py check --database <alias> and migrate. Worth saying in the docs so nobody expects it in a plain check run.
docs/web/cron-jobs.md#timezonetells users to setcron.timezoneto match Django'sTIME_ZONE, but nothing verifies it. Defaultcron.timezoneisGMT, so a project on a non-UTCTIME_ZONEgets every pg_cron-scheduled task firing hours off — silently, and only in production where pg_cron is the scheduler.Beat interprets cron expressions in
TIME_ZONE(scheduler.pyget_next_datetime→timezone.localtime); pg_cron interprets them incron.timezone. Same schedule, different wall-clock time, no signal.Ship
An informational check (
absurd.I001), not a warning or error — the mismatch is legitimate if intended.@register(Tags.database, "absurd")with thedatabasesguard, mirroringcheck_absurd_queue_state.cron.timezonefrompg_settings— it's a server-level GUC, readable from any database on that server, so no central-database connection needed (unlike thecron.database_namework).GMT/UTC/Etc/UTCare equivalent,America/Chicagois not.msgstates the mismatch (the two zones and that schedules fire at different wall-clock times);hintgives thecron.timezone = '<TIME_ZONE>'line forpostgresql.conf.Note
Tags.databasechecks don't run under a baremanage.py check— onlymanage.py check --database <alias>andmigrate. Worth saying in the docs so nobody expects it in a plain check run.