Custom Timetables
This package provides a set of custom Airflow timetables for advanced scheduling scenarios. Below you'll find usage instructions and examples for each timetable class.
Originally written by Jorge Marquez (jmmarqu/airflow-custom-timetables). Maintained by Gary Dukes (pendulum .now fix, optional holiday calendars, packaging).
Install
pip install "airflow-custom-timetables[holidays]"
Or in requirements.txt:
airflow-custom-timetables[holidays]
From GitHub instead of PyPI:
airflow-custom-timetables[holidays] @ git+https://github.com/airflow-dev/airflow-custom-timetables.git
Then pip install -r requirements.txt (or however you build the Airflow image). Import path is unchanged:
from custom_timetables import BusinessDayOfMonth
Installing the package registers the Airflow plugin CustomIntervalTimetables via the airflow.plugins entry point, so you do not need to copy this file into $AIRFLOW_HOME/plugins. Drop it in plugins/ or pip-install it — not both.
apache-airflow is not a pip dependency of this package; install it into an Airflow environment that already has Airflow and pendulum. The [holidays] extra pulls in PyPI holidays for optional country calendars.
How to Use
-
Import the desired timetable class in your DAG file:
from custom_timetables import ( MonthlyLastDay, MonthlyOnDay, MonthlyMultipleDays, QuarterlyFirstDay, QuarterlyLastDay, YearlyFirstDay, YearlyWeekdayOccurrence, WeeklyOnDay, BiweeklyOnDay, SemiMonthly, EveryNDays, BusinessDayOfMonth, MonthlyLastDayExceptWeekend, CronTimetable, EveryNInterval, )
-
Set the timetable in your DAG:
from airflow import DAG dag = DAG( "my_dag", timetable=SemiMonthly(hour=8, minute=0), )
Timetable Classes and Examples
| Pattern | Class | Sample Usage | Description |
|---|---|---|---|
| Last day of month | MonthlyLastDay |
MonthlyLastDay(hour=18, minute=0) |
Last day of each month at 18:00 |
| Specific day of month | MonthlyOnDay |
MonthlyOnDay(day=10, hour=9, minute=0) |
10th of each month at 09:00 |
| Multiple days in month | MonthlyMultipleDays |
MonthlyMultipleDays(days=[1, 15], hour=8, minute=0) |
1st and 15th of each month at 08:00 |
| First day of quarter | QuarterlyFirstDay |
QuarterlyFirstDay(hour=7, minute=0) |
First day of each quarter at 07:00 |
| Last day of quarter | QuarterlyLastDay |
QuarterlyLastDay(hour=17, minute=30) |
Last day of each quarter at 17:30 |
| First day of year | YearlyFirstDay |
YearlyFirstDay(hour=0, minute=0) |
January 1st each year at midnight |
| Nth weekday in month | MonthlyWeekdayOccurrence |
MonthlyWeekdayOccurrence(weekday=0, n=2, hour=9, minute=0) |
2nd Monday of each month at 09:00 |
| Nth weekday in year/month | YearlyWeekdayOccurrence |
YearlyWeekdayOccurrence(month=11, weekday=0, n=1, hour=9, minute=0) |
1st Monday of November at 09:00 |
| Weekly on specific weekday | WeeklyOnDay |
WeeklyOnDay(weekday=2, hour=10, minute=0) |
Every Wednesday at 10:00 |
| Biweekly on specific weekday | BiweeklyOnDay |
BiweeklyOnDay(weekday=4, hour=8, minute=0) |
Every other Friday at 08:00 |
| Semi-monthly (15th and last day) | SemiMonthly |
SemiMonthly(hour=8, minute=0) |
15th and last day of each month at 08:00 |
| Every N days | EveryNDays |
EveryNDays(interval_days=10, hour=7, minute=0) |
Every 10 days at 07:00 |
| Nth business day of month | BusinessDayOfMonth |
BusinessDayOfMonth(n=1, hour=9, minute=0) |
1st business day of each month at 09:00 |
| Last business day of month | BusinessDayOfMonth |
BusinessDayOfMonth(n=-1, hour=17, minute=0) |
Last business day of each month at 17:00 |
| First working day (England bank holidays) | BusinessDayOfMonth |
BusinessDayOfMonth(n=1, hour=9, tz="Europe/London", country="GB", subdiv="ENG") |
1st working day, skipping weekends and England holidays |
| Last day except weekend (move to Friday) | MonthlyLastDayExceptWeekend |
MonthlyLastDayExceptWeekend(hour=18, minute=0) |
Last day of month at 18:00, or previous Friday |
| Cron expression | CronTimetable |
CronTimetable("0 9 15 * *", timezone="America/New_York") |
15th of every month at 09:00 (or any cron pattern) |
| Every N hours/minutes | EveryNInterval |
EveryNInterval(interval_hours=6) or EveryNInterval(interval_minutes=45) |
Every 6 hours or every 45 minutes |
Notes
- All timetables accept a
tzortimezoneparameter (default:"America/New_York"). - For weekday parameters: Monday=0, ..., Sunday=6.
- For
MonthlyWeekdayOccurrenceandYearlyWeekdayOccurrence,n=-1means "last" occurrence. - For
BusinessDayOfMonth,n=-1means "last business day". BusinessDayOfMonthandMonthlyLastDayExceptWeekendaccept optionalcountry/subdiv/observed(ISO 3166, via PyPIholidays). Defaultcountry=Noneis weekends only and does not importholidays. Example:country="GB", subdiv="ENG"for England bank holidays. US state:country="US", subdiv="NY". The calendar is not stored on the timetable — only those three kwargs are serialized.
Optional holiday calendars
Install with the extra (or pip install holidays next to the plugin):
pip install "airflow-custom-timetables[holidays]"
from custom_timetables import BusinessDayOfMonth, MonthlyLastDayExceptWeekend
# Weekends only — same as before. holidays is not imported.
BusinessDayOfMonth(n=1, hour=9, tz="Europe/London")
# Skip England bank holidays as well as Saturday/Sunday.
BusinessDayOfMonth(
n=1, hour=9, tz="Europe/London",
country="GB", subdiv="ENG",
)
# Last weekday of the month, walking back over US federal holidays too.
MonthlyLastDayExceptWeekend(hour=18, country="US")
observed=True (the library default) also skips substitute weekdays (e.g. Monday when Christmas is a Sunday). Pass observed=False to use the calendar date only.
When a calendar is loaded, that country's weekend set is used (holidays calendar.weekend) instead of hardcoded Monday–Friday. Countries with a Friday–Saturday weekend therefore skip those days.
CronTimetable Usage Examples
For more advanced patterns, combine these classes or use CronTimetable for full cron flexibility.
The CronTimetable class allows you to use any cron expression for scheduling.
You can specify the cron string and an optional timezone.
from custom_timetables import CronTimetable
# 15th of every month at 09:00
timetable = CronTimetable("0 9 15 * *", timezone="America/New_York")
# Every Monday and Wednesday at 18:30
timetable = CronTimetable("30 18 * * 1,3", timezone="America/New_York")
# Every day at midnight UTC
timetable = CronTimetable("0 0 * * *", timezone="UTC")
# Every weekday (Mon-Fri) at 07:15
timetable = CronTimetable("15 7 * * 1-5", timezone="America/New_York")
# Every 10 minutes
timetable = CronTimetable("*/10 * * * *", timezone="America/New_York")
Tip: You can use any valid cron expression supported by Airflow. See crontab.guru for help building cron expressions.
Development
pip install -e ".[dev]"
pytest
Airflow is stubbed in tests, so you do not need an Airflow install to run
them. pendulum and holidays come in with the dev extra.
The pip import path is still custom_timetables. The package lives under
src/custom_timetables/ (_base.py for the shared clock/period machinery,
_timetables.py for the concrete classes).
License
MIT. Original work by Jorge Marquez; subsequent packaging, holiday calendars, and refactor by Gary Dukes. Source: airflow-dev/airflow-custom-timetables. See LICENSE.
Release files for airflow-custom-timetables 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| airflow_custom_timetables-0.2.0.tar.gz | 17.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| airflow_custom_timetables-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size:32.8 kB
Release files / airflow_custom_timetables-0.2.0.tar.gz
| Download URL | airflow_custom_timetables-0.2.0.tar.gz |
|---|---|
| Size | 17.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
dbb2e623c3620287d6cff3ab61c0526f985348b5073de7e2b93b8ea9eac53a43
|
|
BLAKE2b-256 checksum How to use checksums |
26e69b3e95e77dbf73f33e8ab6de834e217125ebbee24b9ceb9eef6c52e52491
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"CachyOS Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / airflow_custom_timetables-0.2.0-py3-none-any.whl
| Download URL | airflow_custom_timetables-0.2.0-py3-none-any.whl |
|---|---|
| Size | 15.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
746f7ed9ccd2778bdc9dcfa3c770f949aa8427ddb82930182ed2e199df84686a
|
|
BLAKE2b-256 checksum How to use checksums |
b07f008dbbd38de5457ea93d68fccd76d7ce4d8d1127ecd2b4d085e0d883c114
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"CachyOS Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|