CalDAV (RFC4791) client library for Python with comprehensive calendar server interaction capabilities
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Journal entry management and free/busy scheduling functionality for calendar coordination, availability management, and personal journaling within CalDAV systems.
Represents CalDAV journal entries (VJOURNAL components) for personal notes, meeting minutes, and diary-style calendar entries.
class Journal(CalendarObjectResource):
"""
Journal entry class for VJOURNAL components.
Inherits all methods from CalendarObjectResource including:
- load(), save(), delete()
- copy(), move()
- change_uid()
- expand() (for recurring journals)
"""Usage Examples:
import caldav
from datetime import datetime, timezone
import uuid
# Get existing journals
calendar = principal.calendars()[0]
journals = calendar.journals()
if journals:
journal = journals[0]
# Get journal information
component = journal.icalendar_component
summary = component.get('SUMMARY', 'No title')
description = component.get('DESCRIPTION', 'No content')
dtstart = component.get('DTSTART')
print(f"Journal: {summary}")
print(f"Date: {dtstart}")
print(f"Content: {description}")
# Modify journal
component['SUMMARY'] = 'Updated Journal Entry'
component['DESCRIPTION'] = 'Updated content with new insights'
journal.save()
print("Journal updated successfully")Create new journal entries with comprehensive VJOURNAL support for documentation, meeting notes, and personal journaling.
# Journal creation through calendar.save_journal() - see calendar-management.md
# Additional journal-specific creation patterns:
def create_simple_journal(summary, content, date=None):
"""
Helper function to create simple journal iCalendar data.
Parameters:
- summary: str, journal entry title
- content: str, journal entry content/description
- date: datetime, entry date (default: now)
Returns:
str: iCalendar data for the journal entry
"""Usage Examples:
# Create a meeting minutes journal entry
def create_meeting_minutes(meeting_title, attendees, notes, meeting_date=None):
if meeting_date is None:
meeting_date = datetime.now(timezone.utc)
journal_uid = f"{uuid.uuid4()}@example.com"
attendee_list = ", ".join(attendees) if attendees else "No attendees recorded"
full_content = f"""Meeting Attendees: {attendee_list}
Meeting Notes:
{notes}"""
ical_data = f"""BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//My Journal App//My Journal App//EN
BEGIN:VJOURNAL
UID:{journal_uid}
DTSTART:{meeting_date.strftime('%Y%m%dT%H%M%SZ')}
SUMMARY:{meeting_title} - Meeting Minutes
DESCRIPTION:{full_content}
CATEGORIES:meeting-minutes
DTSTAMP:{datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ')}
END:VJOURNAL
END:VCALENDAR"""
return ical_data
# Create and save meeting minutes
meeting_date = datetime(2025, 9, 15, 14, 0, tzinfo=timezone.utc)
minutes_ical = create_meeting_minutes(
meeting_title="Weekly Team Sync",
attendees=["alice@example.com", "bob@example.com", "charlie@example.com"],
notes="""
- Reviewed sprint progress: 80% complete
- Discussed upcoming deadlines for Q4
- Alice raised concerns about resource allocation
- Action items:
* Bob to follow up on server migration
* Charlie to update documentation
* Schedule architecture review for next week
""",
meeting_date=meeting_date
)
journal = calendar.save_journal(minutes_ical)
print(f"Created meeting minutes: {journal.id}")
# Create personal journal entry
personal_ical = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//My App//My App//EN
BEGIN:VJOURNAL
UID:daily-reflection-20250915@example.com
DTSTART:20250915T200000Z
SUMMARY:Daily Reflection - September 15, 2025
DESCRIPTION:Today was productive. Made good progress on the CalDAV integration project. The documentation is coming along well and the team is aligned on the technical approach. Tomorrow I'll focus on testing the authentication flows.
CATEGORIES:personal,reflection
END:VJOURNAL
END:VCALENDAR"""
personal_journal = calendar.save_journal(personal_ical)
# Create project log entry
project_log_ical = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//My App//My App//EN
BEGIN:VJOURNAL
UID:project-log-001@example.com
DTSTART:20250915T170000Z
SUMMARY:CalDAV Integration - Development Log
DESCRIPTION:Completed implementation of event creation and modification. All unit tests passing. Next steps: implement recurring event support and add error handling for edge cases. Estimated completion: end of week.
CATEGORIES:project-log,development
END:VJOURNAL
END:VCALENDAR"""
project_journal = calendar.save_journal(project_log_ical)Search and filter journal entries by date, category, content, and other properties.
# Journal search patterns using calendar.search() - see calendar-management.md
def find_journals_by_category(calendar, category):
"""Find journal entries with specific category."""
journals = calendar.search(comp_filter="VJOURNAL")
matching = []
for journal in journals:
categories = journal.icalendar_component.get('CATEGORIES', [])
if isinstance(categories, str):
categories = [categories]
if category.lower() in [cat.lower() for cat in categories]:
matching.append(journal)
return matching
def find_journals_by_date_range(calendar, start_date, end_date):
"""Find journal entries within date range."""
return calendar.date_search(
start=start_date,
end=end_date,
compfilter="VJOURNAL"
)
def find_journals_by_text(calendar, search_text):
"""Find journal entries containing specific text."""
return calendar.search(
comp_filter="VJOURNAL",
text_match=search_text
)Usage Examples:
from datetime import datetime, timedelta
# Find meeting minutes from last week
last_week = datetime.now() - timedelta(days=7)
this_week = datetime.now()
recent_journals = find_journals_by_date_range(calendar, last_week, this_week)
print(f"Journal entries from last week: {len(recent_journals)}")
# Find all meeting minutes
meeting_journals = find_journals_by_category(calendar, "meeting-minutes")
print(f"Meeting minutes entries: {len(meeting_journals)}")
# Search for specific project mentions
project_journals = find_journals_by_text(calendar, "CalDAV integration")
print(f"Project-related journals: {len(project_journals)}")
# List all journal categories
all_journals = calendar.journals()
categories = set()
for journal in all_journals:
journal_categories = journal.icalendar_component.get('CATEGORIES', [])
if isinstance(journal_categories, str):
journal_categories = [journal_categories]
categories.update(journal_categories)
print(f"Journal categories: {', '.join(sorted(categories))}")Represents CalDAV free/busy objects (VFREEBUSY components) for availability coordination and scheduling support.
class FreeBusy(CalendarObjectResource):
"""
Free/busy information class for VFREEBUSY components.
Inherits all methods from CalendarObjectResource including:
- load(), save(), delete()
- copy(), move()
- change_uid()
"""Usage Examples:
# Get existing free/busy objects
freebusy_objects = calendar.freebusy()
if freebusy_objects:
fb = freebusy_objects[0]
# Get free/busy information
component = fb.icalendar_component
dtstart = component.get('DTSTART')
dtend = component.get('DTEND')
organizer = component.get('ORGANIZER')
print(f"Free/busy period: {dtstart} to {dtend}")
print(f"Organizer: {organizer}")
# Get free/busy periods
freebusy_periods = component.get('FREEBUSY', [])
if not isinstance(freebusy_periods, list):
freebusy_periods = [freebusy_periods]
for period in freebusy_periods:
print(f"Busy period: {period}")Request free/busy information for scheduling coordination and meeting planning.
# Free/busy requests are typically made through Principal or Calendar objects
# See principal-calendar.md and calendar-management.md for details
def request_freebusy_info(principal, start_time, end_time, attendees):
"""
Request free/busy information for attendees.
Parameters:
- principal: Principal object
- start_time: datetime, start of time range
- end_time: datetime, end of time range
- attendees: list[str], attendee email addresses
Returns:
FreeBusy: Free/busy information object
"""
return principal.freebusy_request(start_time, end_time, attendees)Usage Examples:
from datetime import datetime, timedelta, timezone
# Request free/busy information for meeting planning
meeting_start = datetime(2025, 9, 20, 14, 0, tzinfo=timezone.utc) # 2 PM UTC
meeting_end = meeting_start + timedelta(hours=2) # 2 hour meeting
attendees = [
"alice@example.com",
"bob@example.com",
"charlie@example.com"
]
# Request through principal
freebusy_info = principal.freebusy_request(
start=meeting_start,
end=meeting_end,
attendees=attendees
)
if freebusy_info:
print("Free/busy information retrieved")
# Analyze the response (format depends on server implementation)
component = freebusy_info.icalendar_component
organizer = component.get('ORGANIZER', 'Unknown')
# Check for busy periods
busy_periods = component.get('FREEBUSY', [])
if not isinstance(busy_periods, list):
busy_periods = [busy_periods]
print(f"Found {len(busy_periods)} busy periods")
for i, period in enumerate(busy_periods):
print(f" Busy period {i+1}: {period}")
# Request through calendar
calendar_freebusy = calendar.freebusy_request(
start=meeting_start,
end=meeting_end,
attendees=attendees
)Create free/busy objects for publishing availability information.
def create_freebusy_object(start_time, end_time, busy_periods=None, organizer=None):
"""
Helper function to create free/busy iCalendar data.
Parameters:
- start_time: datetime, start of free/busy period
- end_time: datetime, end of free/busy period
- busy_periods: list[tuple], list of (start, end) busy time tuples
- organizer: str, organizer email address
Returns:
str: iCalendar data for the free/busy object
"""Usage Examples:
# Create a free/busy object manually
def create_weekly_freebusy(week_start, busy_times, organizer_email):
week_end = week_start + timedelta(days=7)
fb_uid = f"freebusy-{week_start.strftime('%Y%m%d')}@example.com"
ical_data = f"""BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//My App//My App//EN
BEGIN:VFREEBUSY
UID:{fb_uid}
DTSTART:{week_start.strftime('%Y%m%dT%H%M%SZ')}
DTEND:{week_end.strftime('%Y%m%dT%H%M%SZ')}
ORGANIZER:mailto:{organizer_email}
DTSTAMP:{datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ')}
"""
# Add busy periods
for start, end in busy_times:
ical_data += f"FREEBUSY;FBTYPE=BUSY:{start.strftime('%Y%m%dT%H%M%SZ')}/{end.strftime('%Y%m%dT%H%M%SZ')}\n"
ical_data += """END:VFREEBUSY
END:VCALENDAR"""
return ical_data
# Create free/busy for current week
week_start = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0)
# Subtract days to get to Monday
week_start -= timedelta(days=week_start.weekday())
busy_times = [
(week_start + timedelta(days=0, hours=9), week_start + timedelta(days=0, hours=17)), # Monday 9-5
(week_start + timedelta(days=1, hours=9), week_start + timedelta(days=1, hours=17)), # Tuesday 9-5
(week_start + timedelta(days=2, hours=9), week_start + timedelta(days=2, hours=12)), # Wednesday 9-12
(week_start + timedelta(days=3, hours=14), week_start + timedelta(days=3, hours=17)), # Thursday 2-5
(week_start + timedelta(days=4, hours=9), week_start + timedelta(days=4, hours=15)), # Friday 9-3
]
freebusy_ical = create_weekly_freebusy(
week_start=week_start,
busy_times=busy_times,
organizer_email="user@example.com"
)
# Note: Free/busy objects are typically created by the server automatically
# Manual creation is mainly for testing or special use cases
print("Free/busy object created (for testing purposes)")# Common journal properties that can be accessed via icalendar_component
JOURNAL_PROPERTIES = {
"SUMMARY": "str", # Journal entry title
"DESCRIPTION": "str", # Journal entry content
"DTSTART": "datetime", # Entry date/time
"CATEGORIES": "list[str]", # Entry categories/tags
"CLASS": "str", # PUBLIC, PRIVATE, CONFIDENTIAL
"STATUS": "str", # DRAFT, FINAL, CANCELLED
"ORGANIZER": "vCalAddress", # Entry creator
"ATTENDEE": "list[vCalAddress]", # Associated attendees (for meeting minutes)
}
# Common journal categories
JOURNAL_CATEGORIES = [
"meeting-minutes",
"personal",
"reflection",
"project-log",
"development",
"notes",
"diary",
"daily-standup",
"retrospective"
]# Common free/busy properties that can be accessed via icalendar_component
FREEBUSY_PROPERTIES = {
"DTSTART": "datetime", # Start of free/busy period
"DTEND": "datetime", # End of free/busy period
"ORGANIZER": "vCalAddress", # Free/busy publisher
"ATTENDEE": "list[vCalAddress]", # Attendees the free/busy applies to
"FREEBUSY": "list[period]", # Free/busy time periods
"URL": "str", # URL for free/busy information
}
# Free/busy types (FBTYPE parameter)
FREEBUSY_TYPES = {
"FREE": "Time is free",
"BUSY": "Time is busy (default)",
"BUSY-UNAVAILABLE": "Time is busy and unavailable",
"BUSY-TENTATIVE": "Time is tentatively busy"
}Install with Tessl CLI
npx tessl i tessl/pypi-caldav