Airflow provides three primary ways to define DAG schedules: cron expressions, timedelta intervals, and custom Python Timetable classes. Choosing between them depends on whether you schedule by fixed frequency, clock time, or business calendar logic.
Cron strings versus timedelta
A cron expression like 0 2 * * * specifies execution at a fixed clock time (02:00 UTC every day). A timedelta schedule like datetime.timedelta(hours=6) specifies a fixed duration between intervals. While @daily runs at midnight UTC, a timedelta runs strictly every N hours from the DAG start date, ignoring calendar boundaries like daylight saving transitions.
CronTriggerTimetable versus CronDataIntervalTimetable
Under the hood, Airflow distinguishes between two timetable behaviors:
CronDataIntervalTimetable (classic Airflow 2): Covers interval [00:00, 24:00). Runs at 24:00 (after the interval closes). CronTriggerTimetable (Airflow 3 default for standard cron): Fires at 02:00. The run represents a point-in-time trigger or custom interval.
In Airflow 3, standard cron strings map to CronTriggerTimetable by default, running at the specified moment without waiting for a trailing interval period.
When to write a custom timetable
Standard cron expressions cannot represent real-world business calendars. Common business requirements include:
- Running only on stock exchange trading days, skipping market holidays like Good Friday.
- Running on the last business day of every month, accounting for weekends.
- 4-4-5 retail accounting calendars with uneven fiscal months.
To implement these, subclass airflow.timetables.base.Timetable. Override next_dagrun_info to inspect a calendar database or Python library like holidays, returning the next data interval and start date. This keeps complex scheduling logic out of the DAG code and lets the scheduler calculate runs accurately.