Extensions to the standard Python datetime module
—
Complete implementation of iCalendar recurrence rules (RFC 5545) for generating recurring dates. Supports complex patterns, exclusions, caching, and efficient iteration over large date ranges.
Core recurrence rule implementation supporting all iCalendar recurrence patterns with optional caching for performance.
class rrule:
def __init__(self, freq, dtstart=None, interval=1, wkst=None, count=None,
until=None, bysetpos=None, bymonth=None, bymonthday=None,
byyearday=None, byweekno=None, byweekday=None, byhour=None,
byminute=None, bysecond=None, cache=False):
"""
Create a recurrence rule.
Parameters:
- freq (int): Frequency constant (YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, SECONDLY)
- dtstart (datetime, optional): Start date/time for recurrence
- interval (int): Interval between recurrences (default: 1)
- wkst (int, optional): Week start day (0=Monday, 6=Sunday)
- count (int, optional): Maximum number of occurrences
- until (datetime, optional): End date/time for recurrence
- bysetpos (int/list, optional): Position in set of occurrences
- bymonth (int/list, optional): Months (1-12)
- bymonthday (int/list, optional): Days of month (1-31, -31 to -1)
- byyearday (int/list, optional): Days of year (1-366, -366 to -1)
- byweekno (int/list, optional): Week numbers (1-53, -53 to -1)
- byweekday (int/weekday/list, optional): Weekdays
- byhour (int/list, optional): Hours (0-23)
- byminute (int/list, optional): Minutes (0-59)
- bysecond (int/list, optional): Seconds (0-59)
- cache (bool): Enable caching for performance
"""
def between(self, after, before, inc=False):
"""
Generate occurrences between two dates.
Parameters:
- after (datetime): Start date (exclusive)
- before (datetime): End date (exclusive)
- inc (bool): Include boundary dates if True
Returns:
list[datetime]: List of occurrences in range
"""
def before(self, dt, inc=False):
"""
Get occurrence before specified date.
Parameters:
- dt (datetime): Reference date
- inc (bool): Include dt if it's an occurrence
Returns:
datetime | None: Previous occurrence or None
"""
def after(self, dt, inc=False):
"""
Get occurrence after specified date.
Parameters:
- dt (datetime): Reference date
- inc (bool): Include dt if it's an occurrence
Returns:
datetime | None: Next occurrence or None
"""
def count(self):
"""
Get total number of occurrences.
Returns:
int: Total count (may be infinite)
"""Usage Examples:
from dateutil.rrule import rrule, DAILY, WEEKLY, MONTHLY, YEARLY
from datetime import datetime
# Daily recurrence for 10 days
start = datetime(2023, 1, 1, 9, 0)
daily = rrule(DAILY, dtstart=start, count=10)
dates = list(daily)
# Weekly on Mondays and Wednesdays for 8 weeks
weekly = rrule(WEEKLY, dtstart=start, count=16, byweekday=(0, 2)) # Monday, Wednesday
# Monthly on 15th of each month for 1 year
monthly = rrule(MONTHLY, dtstart=start, count=12, bymonthday=15)
# Yearly on specific date until end date
until_date = datetime(2030, 12, 31)
yearly = rrule(YEARLY, dtstart=start, until=until_date, bymonth=12, bymonthday=25)
# Complex pattern: Every other Tuesday in January and July
complex_rule = rrule(WEEKLY, dtstart=start, interval=2,
byweekday=1, bymonth=(1, 7)) # Tuesday = 1Predefined constants for specifying recurrence frequency.
YEARLY = 0 # Annual recurrence
MONTHLY = 1 # Monthly recurrence
WEEKLY = 2 # Weekly recurrence
DAILY = 3 # Daily recurrence
HOURLY = 4 # Hourly recurrence
MINUTELY = 5 # Per-minute recurrence
SECONDLY = 6 # Per-second recurrenceWeekday specifications for use in recurrence rules, supporting nth occurrence patterns.
# Weekday constants (Monday=0 to Sunday=6)
MO = weekday(0)
TU = weekday(1)
WE = weekday(2)
TH = weekday(3)
FR = weekday(4)
SA = weekday(5)
SU = weekday(6)
class weekday:
def __init__(self, wkday, n=None):
"""
Weekday specification for rrules.
Parameters:
- wkday (int): Weekday number (0=Monday, 6=Sunday)
- n (int, optional): Nth occurrence (1 for first, -1 for last, etc.)
Note: Unlike relativedelta.weekday, n=0 is not allowed
Raises:
ValueError: If n is 0
"""Usage Examples:
from dateutil.rrule import rrule, MONTHLY, MO, FR
from datetime import datetime
start = datetime(2023, 1, 1)
# Every first Monday of each month
first_monday = rrule(MONTHLY, dtstart=start, byweekday=MO(1))
# Every last Friday of each month
last_friday = rrule(MONTHLY, dtstart=start, byweekday=FR(-1))
# Every second and fourth Tuesday
second_fourth_tue = rrule(MONTHLY, dtstart=start, byweekday=(TU(2), TU(4)))
# All Mondays and Fridays (no nth specification)
mon_fri = rrule(WEEKLY, dtstart=start, byweekday=(MO, FR))Collection class for combining multiple rrules with inclusion and exclusion rules.
class rruleset:
def __init__(self, cache=False):
"""
Create a set of recurrence rules.
Parameters:
- cache (bool): Enable caching for performance
"""
def rrule(self, rrule):
"""
Add an rrule to the set.
Parameters:
- rrule (rrule): Recurrence rule to include
"""
def rdate(self, rdate):
"""
Add a single date to the set.
Parameters:
- rdate (datetime): Date to include
"""
def exrule(self, exrule):
"""
Add an exclusion rule to the set.
Parameters:
- exrule (rrule): Recurrence rule for dates to exclude
"""
def exdate(self, exdate):
"""
Add an exclusion date to the set.
Parameters:
- exdate (datetime): Date to exclude
"""
# Same iteration methods as rrule
def between(self, after, before, inc=False): ...
def before(self, dt, inc=False): ...
def after(self, dt, inc=False): ...
def count(self): ...Usage Examples:
from dateutil.rrule import rrule, rruleset, DAILY, WEEKLY, MO, FR
from datetime import datetime
# Create a complex schedule
schedule = rruleset()
# Add daily meetings Monday-Friday
start = datetime(2023, 1, 2, 9, 0) # Monday
weekdays = rrule(DAILY, dtstart=start, byweekday=(MO, TU, WE, TH, FR))
schedule.rrule(weekdays)
# Add extra meeting on first Friday of each month
monthly_meeting = rrule(MONTHLY, dtstart=start, byweekday=FR(1))
schedule.rrule(monthly_meeting)
# Exclude holidays
holiday1 = datetime(2023, 7, 4, 9, 0) # July 4th
holiday2 = datetime(2023, 12, 25, 9, 0) # Christmas
schedule.exdate(holiday1)
schedule.exdate(holiday2)
# Exclude entire holiday week
holiday_week = rrule(DAILY, dtstart=datetime(2023, 12, 25, 9, 0), count=5)
schedule.exrule(holiday_week)
# Get all meetings in January 2023
jan_meetings = schedule.between(
datetime(2023, 1, 1),
datetime(2023, 2, 1)
)Parse recurrence rules from iCalendar-style strings.
def rrulestr(s, **kwargs):
"""
Parse rrule from RFC 5545 string format.
Parameters:
- s (str): RRULE string (e.g., "FREQ=DAILY;COUNT=10")
- **kwargs: Additional parameters passed to rrule constructor
Returns:
rrule: Parsed recurrence rule
Raises:
ValueError: If string format is invalid
"""Usage Examples:
from dateutil.rrule import rrulestr
from datetime import datetime
# Parse from iCalendar format
rule_str = "FREQ=WEEKLY;BYDAY=MO,WE,FR;COUNT=10"
rule = rrulestr(rule_str, dtstart=datetime(2023, 1, 2))
# More complex pattern
complex_str = "FREQ=MONTHLY;BYMONTHDAY=15;BYMONTH=3,6,9,12"
quarterly = rrulestr(complex_str, dtstart=datetime(2023, 1, 1))
# With until date
until_str = "FREQ=DAILY;INTERVAL=2;UNTIL=20231231T235959Z"
every_other_day = rrulestr(until_str, dtstart=datetime(2023, 1, 1))from dateutil.rrule import rrule, DAILY
from datetime import datetime
# Large recurrence with caching
start = datetime(2020, 1, 1)
large_rule = rrule(DAILY, dtstart=start, count=10000, cache=True)
# First iteration builds cache
dates = list(large_rule) # Slower first time
# Subsequent operations use cache
more_dates = list(large_rule) # Much faster
count = large_rule.count() # Instantfrom dateutil.rrule import rrule, rruleset, MONTHLY, WEEKLY, MO, TU, WE, TH, FR
from datetime import datetime
def create_business_schedule():
"""Create complex business meeting schedule."""
schedule = rruleset()
start = datetime(2023, 1, 1, 14, 0) # 2 PM meetings
# Weekly team meetings on Tuesdays
team_meetings = rrule(WEEKLY, dtstart=start, byweekday=TU)
schedule.rrule(team_meetings)
# Monthly all-hands on first Thursday
all_hands = rrule(MONTHLY, dtstart=start, byweekday=TH(1))
schedule.rrule(all_hands)
# Quarterly board meetings on 15th
board_meetings = rrule(MONTHLY, dtstart=start, interval=3, bymonthday=15)
schedule.rrule(board_meetings)
# Exclude summer vacation period
vacation_start = datetime(2023, 7, 1, 14, 0)
vacation_rule = rrule(DAILY, dtstart=vacation_start, count=14)
schedule.exrule(vacation_rule)
return schedule
schedule = create_business_schedule()
meetings_2023 = schedule.between(
datetime(2023, 1, 1),
datetime(2024, 1, 1)
)from dateutil.rrule import rrule, DAILY
from dateutil.tz import gettz
from datetime import datetime
# Timezone-aware recurrence
eastern = gettz('America/New_York')
start = datetime(2023, 1, 1, 9, 0, tzinfo=eastern)
daily_meetings = rrule(DAILY, dtstart=start, count=30, byweekday=(MO, TU, WE, TH, FR))
# All generated dates will be timezone-aware
for meeting in daily_meetings:
print(f"{meeting} ({meeting.tzinfo})")from datetime import datetime
class rrule:
# Iteration protocol
def __iter__(self): ...
def __getitem__(self, item): ...
def __contains__(self, item): ...
def __len__(self): ...
class rruleset:
# Same iteration protocol as rrule
def __iter__(self): ...
def __getitem__(self, item): ...
def __contains__(self, item): ...
def __len__(self): ...
class weekday:
weekday: int # 0-6 (Monday-Sunday)
n: int | None # Nth occurrence (cannot be 0)
# Frequency constants
FrequencyType = int # One of YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, SECONDLY
# Common parameter types
DateTimeList = list[datetime] | datetime
IntList = list[int] | int
WeekdayList = list[weekday | int] | weekday | intInstall with Tessl CLI
npx tessl i tessl/pypi-python-dateutil