Introduction
Calendars and scheduling boards are core requirements for task management tools, booking platforms, and workout trackers. In Streamlit, while st.date_input() is fantastic for basic single-date selections, how do you handle complex tasks like multiple events on a calendar, event logs, or schedule overlays? For another date-driven build, see the age calculator tutorial.
In this tutorial, you will learn the top techniques for building an interactive Calendar Event Log application using Python and Streamlit, complete with custom session state tracking.
🎬 Watch the Full Video Tutorial:
Watch the expert guide on YouTube: Python EXPERT Shares Top Streamlit Calendar App Building Techniques
Prerequisites
Install Streamlit if you haven’t already:
pip install streamlit
Step 1: Create the Calendar App Script
Create a new file named calendar_app.py and write the following code:
import streamlit as st
from datetime import datetime, date
st.set_page_config(page_title="Streamlit Calendar App", page_icon="📅", layout="wide")
st.title("📅 Advanced Streamlit Calendar Board")
# Initialize event list in session state so it persists across runs
if "events" not in st.session_state:
st.session_state.events = [
{"date": date(2024, 5, 13), "title": "Streamlit Calendar Launch Video", "type": "Work"},
{"date": date(2024, 5, 15), "title": "Upload New Python Tutorial", "type": "Personal"}
]
# Layout columns
col1, col2 = st.columns([1, 2])
with col1:
st.subheader("➕ Add Calendar Event")
event_date = st.date_input("Select Event Date:", value=date.today())
event_title = st.text_input("Event Title:", "New Meeting")
event_type = st.selectbox("Category:", ["Work", "Personal", "Meeting", "Urgent"])
if st.button("Add Event to Calendar"):
if event_title:
st.session_state.events.append({
"date": event_date,
"title": event_title,
"type": event_type
})
st.success(f"Added: '{event_title}' for {event_date}!")
else:
st.error("Please enter an event title!")
with col2:
st.subheader("📋 Schedule Board")
# Filtering options
selected_filter = st.radio("Filter by Category:", ["All", "Work", "Personal", "Meeting", "Urgent"], horizontal=True)
# Filter and display events
filtered_events = st.session_state.events
if selected_filter != "All":
filtered_events = [e for e in st.session_state.events if e["type"] == selected_filter]
# Sort events by date
filtered_events = sorted(filtered_events, key=lambda x: x["date"])
if not filtered_events:
st.info("No events scheduled for this filter.")
else:
for idx, event in enumerate(filtered_events):
# Formatting styling based on type
badge_color = {
"Work": "🔵",
"Personal": "🟢",
"Meeting": "🟣",
"Urgent": "🔴"
}.get(event["type"], "⚪")
with st.container():
st.markdown(f"### {badge_color} {event['title']}")
st.write(f"**Date:** {event['date'].strftime('%A, %B %d, %Y')} | **Category:** `{event['type']}`")
st.markdown("---")
Step 2: Run the App
Run the Streamlit server:
streamlit run calendar_app.py
How It Works
Python’s built-in calendar module does the date heavy lifting with zero dependencies: calendar.monthrange(year, month) returns the weekday of the first day and the number of days, which is everything needed to lay out a grid. The app renders that grid as a markdown table inside st.markdown, styling today’s cell with bold/emphasis.
Navigation is two buttons (prev/next month) writing to st.session_state["offset"] — a month offset from today. Each click changes the offset and triggers a rerun that recomputes the grid. This tiny session-state counter is the same pattern that drives the quiz app’s question index.
For event planning, st.date_input complements the grid: users pick a date and the app stores notes against it in st.session_state (or a JSON file for persistence). Combining the visual month view with a date picker covers most personal-calendar needs in under 150 lines.
Key Concepts Covered
st.session_state— Key to persisting event lists in memory. Without it, the event array resets back to defaults every time a button is clicked.- Object Sorting — Sorting lists based on date dictionaries using lambda parameters:
key=lambda x: x["date"].
What to Try Next
- Export to CSV: Provide a button to download the scheduled events list as a CSV file.
- Integration: Connect with standard Google Calendar API endpoints using Python requests.
Common Errors & Fixes
-
Calendar doesn’t move when clicking next — the offset lives in a local variable; store it in
st.session_stateso it survives reruns. -
ValueError: day is out of range for month— you carried a selected day (31) into a shorter month; clamp the day withmin(day, monthrange(...)[1])after switching months. -
Week starts on Monday but you want Sunday — call
calendar.setfirstweekday(calendar.SUNDAY)before generating the grid. -
Table renders as raw pipes — the markdown table needs a header separator row; check the renderer’s table format expectations.
-
Buttons flash but nothing changes — both prev/next handlers write the same key; give each
st.buttona distinctkeyand update the offset by ±1 in the right one.
FAQ
Can I persist events between sessions?
Yes — dump the events dict to a JSON file after each change and load it at startup. For multi-user apps, graduate to a database.
How do I highlight multiple event days?
Build a set of event dates and apply the emphasis style to any cell whose date is in the set.
Why not use a ready-made calendar component?
Community components exist, but building the grid teaches the session-state and rerun patterns you will reuse in every Streamlit project.
Can I show the whole year at once?
Yes — calendar.TextCalendar or calendar.HTMLCalendar renders a full year; loop months into 12 columns with st.columns(4) for a year-at-a-glance view.