DAG Scheduling and Time Semantics
Logical date, data interval, and run time
Airflow schedules by data interval. A run's logical_date (formerly execution_date) is the start of the interval it processes. With schedule='@daily' and start_date=2024-03-01, the run with logical date 2024-03-01 covers data from 2024-03-01T00:00 to 2024-03-02T00:00 and is triggered at the end of that interval (2024-03-02T00:00). Templates like {{ ds }} resolve to the logical date, which is exactly the partition the run should process.
Schedules can be cron strings, timedelta, None (manual only), or timetables for complex calendars. catchup=True backfills every interval since start_date; max_active_runs limits concurrency; catchup=False starts from the latest interval.
Traps candidates hit
- Expecting today's data in today's run. A daily run for 2024-03-01 does not start until 2024-03-02.
start_datein the future means no runs until that time passes;start_datewithcatchup=Truecan flood the scheduler.schedule_intervalis the legacy name; modern Airflow usesschedule. Both may appear in questions.- Time zones matter. Naive datetimes are interpreted in the deployment's default timezone; use
pendulum/timezone.utcexplicitly. end_datestops new runs but does not cancel running ones.
Rule of thumb
"The run processes the interval that ends when it starts." If a question asks which date a run handles, answer with the interval start, not the wall-clock trigger time.
Worked example
start_date=pendulum.datetime(2024,3,1,tz='UTC'), schedule='@daily', catchup=False. The first run has logical date 2024-03-01, and {{ ds }} renders 2024-03-01 even though execution begins on 2024-03-02.