Tech With Galvan
Back to Articles
Development5/13/2024 • 3 min read

Calendar App using Python and Streamlit

Build a calendar web app with Python and Streamlit — navigate months, highlight today, and plan dates with an interactive UI.

Galvan
Galvan

Founder & Creator

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_state so it survives reruns.

  • ValueError: day is out of range for month — you carried a selected day (31) into a shorter month; clamp the day with min(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.button a distinct key and 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.