Scheduling, Cron, Timetables, and Timezones

Purpose and Scope

This page orients DAG authors and operators to Airflow scheduling primitives: cron expressions, presets such as @once, timetables, timezone-aware intervals, and the scheduler state that records scheduling decisions. In Airflow, a schedule is not just a wall-clock trigger. It defines when a DAG run should exist, what logical data interval that run represents, and how the scheduler should evaluate future work. The official documentation groups these topics under Authoring and Scheduling, including Cron & Time Intervals, Time Zones, Timetables, Asset-Aware Scheduling, and Event-Driven Scheduling, so treat scheduling as both an authoring API and an operational behavior.

Sources: performance/src/performance_dags/performance_dag/performance_dag_configurations/scheduling_performance.json, airflow-core/src/airflow/migrations/versions/0016_2_9_2_remove_idx_last_scheduling_decision_.py

The repository evidence also shows that scheduling is documented and operated across multiple parts of the project rather than in a single tutorial file. The documentation architecture script describes how package docs are published from the Airflow repository into the live documentation site, which explains why user-facing scheduling concepts live in published docs while implementation and operational signals remain in code, migrations, and test-style configurations. For developers, this means the safest workflow is to learn scheduling semantics from the official docs, then inspect core metadata, performance configurations, and provider packages when debugging how those semantics behave in an installation.

Sources: docs/images/documentation_architecture.py

Relevant Source Files

  • docs/images/documentation_architecture.py - Generates a diagram for the Airflow documentation publishing path, grounding why scheduling guidance is surfaced through package documentation and the live docs site.
  • airflow-core/src/airflow/migrations/versions/0016_2_9_2_remove_idx_last_scheduling_decision_.py - Defines an Alembic migration for the dag_run.last_scheduling_decision index, showing that scheduler decisions are persisted in metadata database structures that evolve over releases.
  • performance/src/performance_dags/performance_dag/performance_dag_configurations/scheduling_performance.json - Provides a performance DAG configuration with schedule-related knobs such as PERF_START_AGO and PERF_SCHEDULE_INTERVAL.
  • providers/google/src/airflow/providers/google/event_scheduling/__init__.py - Marks the Google provider event scheduling package namespace used for provider-side scheduling integrations.
  • providers/google/src/airflow/providers/google/event_scheduling/events/__init__.py - Marks the events namespace under Google provider event scheduling, separating event definitions from the top-level integration package.

Core Primitives

A DAG schedule answers the question, “When should Airflow create DAG runs for this workflow?” The most common answer is a cron expression or cron preset. A cron expression describes recurring calendar times, while presets provide named shortcuts for common behavior. The performance configuration included in this repository uses PERF_SCHEDULE_INTERVAL set to @once, which is a useful reminder that Airflow schedules can express one-time execution as well as recurring intervals. That same configuration pairs the schedule with PERF_START_AGO, so schedule tests can generate DAGs whose logical start point is relative to the current time.

Sources: performance/src/performance_dags/performance_dag/performance_dag_configurations/scheduling_performance.json

A timetable is the abstraction behind schedule calculation. Where a cron string is compact and familiar, a timetable is the mechanism that can compute run times and data intervals for schedules that do not fit simple cron syntax. The official Airflow docs present timetables alongside cron and time intervals because both ultimately feed the scheduler with the same kind of decision: whether a DAG should have a next run, and what period of data that run covers. Timezone handling is part of that calculation, since a calendar schedule only has a precise meaning after the intended timezone is known.

Event-based scheduling is related but distinct from time-based scheduling. The requested source paths include Google provider package namespaces for event scheduling and event definitions. Those files do not define a public class in the snippets, but their package placement is still important: provider integrations can organize event-scheduling support outside the core scheduler package while participating in Airflow’s broader authoring and scheduling model. This separation helps keep core scheduling concepts stable while allowing providers to add integration-specific event sources and event representations.

Sources: providers/google/src/airflow/providers/google/event_scheduling/init.py, providers/google/src/airflow/providers/google/event_scheduling/events/init.py

System-to-Code Mapping

At runtime, the scheduler creates and updates DAG runs in the metadata database. The migration 0016_2_9_2_remove_idx_last_scheduling_decision_.py removes an index named idx_last_scheduling_decision from the dag_run table and recreates it on downgrade. That small migration tells an important operational story: scheduling decisions are stored as database-backed state, and Airflow’s schema is tuned over time as scheduler behavior and database access patterns evolve. When investigating scheduling performance, do not look only at DAG code; also consider metadata database schema, scheduler queries, and release-specific migrations.

Sources: airflow-core/src/airflow/migrations/versions/0016_2_9_2_remove_idx_last_scheduling_decision_.py

The performance DAG configuration maps authoring-level schedule choices to measurable scheduler load. PERF_DAGS_COUNT and PERF_TASKS_COUNT control the number of generated workflows and tasks, while PERF_SCHEDULE_INTERVAL controls when those workflows are eligible to run. In the supplied configuration, ten workflows with one hundred tasks each are generated with no structural dependencies and a one-time schedule. This kind of configuration isolates scheduler overhead for run creation and task discovery without adding external operator latency, since PERF_SLEEP_TIME is zero and PERF_OPERATOR_TYPE is python.

Compact mapping: PERF_START_AGO sets the generated start offset; PERF_SCHEDULE_INTERVAL sets the schedule expression; dag_run.last_scheduling_decision records scheduler-related metadata; provider event-scheduling packages reserve integration namespaces for non-cron scheduling extensions.

Sources: performance/src/performance_dags/performance_dag/performance_dag_configurations/scheduling_performance.json, airflow-core/src/airflow/migrations/versions/0016_2_9_2_remove_idx_last_scheduling_decision_.py, providers/google/src/airflow/providers/google/event_scheduling/init.py, providers/google/src/airflow/providers/google/event_scheduling/events/init.py

Execution Flow

A practical scheduling flow starts when a DAG author chooses a schedule. For a regular calendar workflow, the author uses a cron expression or preset. For one-time workflows, a preset such as @once is appropriate. For schedules that need custom interval calculation, a timetable is the right conceptual tool. Once the DAG is parsed, the scheduler evaluates whether a new run should be created, calculates the logical interval, records metadata about the run, and later revisits scheduling decisions as DAG state changes. The database-backed dag_run state is therefore part of the scheduling loop, not just historical bookkeeping.

Sources: airflow-core/src/airflow/migrations/versions/0016_2_9_2_remove_idx_last_scheduling_decision_.py

Timezone choices should be made deliberately before production deployment. A cron schedule like “run at midnight” is ambiguous unless the intended timezone is clear, and daylight-saving transitions can create surprising gaps or duplicates for local-time schedules. The official docs separate Time Zones from Cron & Time Intervals because schedule expressions and timezone interpretation work together. For teams, the recommended practice is to standardize DAG timezone expectations, document them beside business SLAs, and test schedules around boundary dates such as month end, daylight-saving changes, and regional holidays.

Event-driven scheduling changes the first trigger in the flow. Instead of asking only whether the clock has reached the next interval, event-aware DAGs may depend on an asset event or provider-specific event source. The official docs describe asset-aware and event-driven scheduling as separate authoring topics, and the Google provider event-scheduling namespaces show how provider packages can participate in that model. Operators should still reason about resulting DAG runs the same way: events create scheduling opportunities, but Airflow still needs durable run metadata, observable state, and clear ownership of downstream tasks.

Sources: providers/google/src/airflow/providers/google/event_scheduling/init.py, providers/google/src/airflow/providers/google/event_scheduling/events/init.py, airflow-core/src/airflow/migrations/versions/0016_2_9_2_remove_idx_last_scheduling_decision_.py

Configuration and Reference Notes

Use schedule configuration as a contract between DAG authors and the scheduler. A compact local or performance-oriented configuration can express the important pieces clearly: how many DAGs exist, how many tasks each DAG contains, when the DAGs start, and what schedule expression they use. The supplied performance JSON demonstrates this style with PERF_DAG_PREFIX, PERF_DAGS_COUNT, PERF_TASKS_COUNT, PERF_START_AGO, and PERF_SCHEDULE_INTERVAL. Although it is performance-oriented rather than a user DAG file, it is a useful source-backed example of schedule inputs being treated as explicit, reproducible configuration.

{
  "PERF_START_AGO": "1d",
  "PERF_SCHEDULE_INTERVAL": "@once",
  "PERF_DAGS_COUNT": "10",
  "PERF_TASKS_COUNT": "100"
}

Sources: performance/src/performance_dags/performance_dag/performance_dag_configurations/scheduling_performance.json

For reference, interpret the key scheduling terms this way. A cron expression or preset is the author-facing schedule declaration. A timetable is the schedule-calculation abstraction that can represent cron-like or custom interval logic. A timezone determines how calendar expressions become exact instants. A DAG run is the persisted execution instance produced by scheduling. The last_scheduling_decision metadata field referenced by the migration is not an authoring API, but it is a useful marker for understanding that scheduler decisions have database representation and may be affected by schema changes across Airflow versions.

Sources: airflow-core/src/airflow/migrations/versions/0016_2_9_2_remove_idx_last_scheduling_decision_.py

Operational Guidance and Next Steps

When diagnosing schedule behavior, first confirm the authoring declaration: cron string, preset, timetable, asset expression, or event source. Next, confirm timezone assumptions and start-date expectations. Then inspect whether the scheduler is creating DAG runs as expected and whether metadata database state is healthy for the Airflow version in use. For performance investigations, reproduce the shape of the problem with explicit counts and schedule settings, similar to the supplied performance DAG configuration. For integration-driven workflows, review the relevant provider’s event scheduling package or documentation before assuming the behavior belongs to core time-based scheduling.

Sources: performance/src/performance_dags/performance_dag/performance_dag_configurations/scheduling_performance.json, providers/google/src/airflow/providers/google/event_scheduling/init.py, providers/google/src/airflow/providers/google/event_scheduling/events/init.py

Read next: use the Scheduler page for operational tuning, Asset and Event Scheduling for non-time triggers, DAGs for the core workflow model, and Metadata Database and Migrations for schema evolution that can affect scheduler behavior. If you are writing DAGs, start with simple cron or presets, add timezone documentation, and move to timetables only when the schedule cannot be expressed cleanly as a cron-style interval.