A Python package for semester-related utilities.
Project description
hm-semester
A Python package for generating ICS calendar files for Hochschule München (HM) semesters. Create calendars with semester dates, breaks, holidays, and weekly/biweekly lecture schedules.
Features
- Semester Calendar: Generate calendars with semester start/end dates and breaks (Christmas, Easter, Pentecost)
- Lecture Agenda: Create individual lecture events with holiday-aware scheduling
- Biweekly Support: Handle biweekly lectures that maintain alternating patterns even when holidays interrupt
- Update Tracking: Deterministic UIDs and SEQUENCE numbers for calendar updates
- Multi-language: Support for German and English labels
Usage
Semester Calendar (Holidays & Breaks)
Generate a calendar with semester dates and break periods.
CLI
python -m hm_semester --year 2025 --semester winter --lang en
This creates winter_semester_2025_en.ics with:
- Semester start and end dates
- Christmas break (Winter semester)
- Easter and Pentecost breaks (Summer semester)
Python API
from hm_semester.semester import generate_calendar
cal = generate_calendar(2025, "winter", "en")
with open("semester.ics", "wb") as f:
f.write(cal.to_ical())
Lecture Agenda
Generate individual lecture events with holiday-aware scheduling.
Basic Example
from datetime import time
from hm_semester.agenda import WeeklyEvent, create_agenda
events = [
WeeklyEvent(
summary="Algorithms",
course_id="CS101",
weekday=0, # Monday
start_time=time(9, 0),
end_time=time(11, 0),
location="Room 101",
),
WeeklyEvent(
summary="Database Systems",
course_id="CS202",
weekday=2, # Wednesday
start_time=time(14, 0),
end_time=time(16, 0),
location="Lab 305",
biweekly=True, # Every 2 weeks
start_week=1,
),
]
cal = create_agenda(events, 2026, "en", "summer")
with open("lectures.ics", "wb") as f:
f.write(cal.to_ical())
WeeklyEvent Parameters
summary(str): Event titlecourse_id(str): Course identifier for deterministic UID generationweekday(int): Day of week (0=Monday, 6=Sunday)start_time(time): Lecture start timeend_time(time): Lecture end timelocation(str, optional): Room or buildingbiweekly(bool, optional): If True, event occurs every 2 weeksstart_week(int, optional): For biweekly, which week to start (1, 2, 3, ...)timezone(str, optional): Timezone name (default: "Europe/Berlin")sequence(int, optional): Version number for updates (default: 0)
Holiday-Aware Scheduling
Lectures automatically skip semester breaks:
- Individual events are generated (no RRULE)
- Holidays are excluded from the schedule
- Biweekly lectures maintain alternating pattern even when holidays interrupt
- Example: If a biweekly lecture would be in week 7 (holiday), it shifts to week 8
Biweekly Patterns
Use start_week to stagger multiple biweekly groups:
# Four groups meeting every 2 weeks, staggered by 1 week
groups = [
WeeklyEvent("Group A", "grpA", 0, time(10, 0), time(12, 0),
biweekly=True, start_week=1),
WeeklyEvent("Group B", "grpB", 0, time(10, 0), time(12, 0),
biweekly=True, start_week=2),
WeeklyEvent("Group C", "grpC", 0, time(10, 0), time(12, 0),
biweekly=True, start_week=3),
WeeklyEvent("Group D", "grpD", 0, time(10, 0), time(12, 0),
biweekly=True, start_week=4),
]
Updating Calendars
When room locations or times change, increment the sequence parameter:
# Initial calendar
events = [
WeeklyEvent("Algorithms", "CS101", 0, time(9, 0), time(11, 0),
location="Room 101", sequence=0),
]
cal = create_agenda(events, 2026, "en", "summer")
# Later: room changed
events[0].location = "Room 999"
events[0].sequence = 1
updated_cal = create_agenda(events, 2026, "en", "summer")
For Thunderbird/local calendar apps: Delete the old calendar and import the new one.
For CalDAV/subscribed calendars: Events with the same UID and higher SEQUENCE are automatically updated.
Examples
See examples/create_agenda_example.py for a complete example.
Output Format
- Each lecture gets a unique event with format:
"Course Name (1)","Course Name (2)", etc. - UIDs are deterministic:
{course_id}-{year}-{semester}-lesson-{number}@hm.edu - VTIMEZONE components are included for proper timezone handling
- SEQUENCE field tracks version numbers for updates
Installation
From PyPI
pip install hm-semester
From source
git clone https://github.com/DavidMStraub/hm-semester.git
cd hm-semester
pip install .
Development
Run tests:
pytest tests/
Release Process
This package uses GitHub Actions for automated PyPI releases:
- Update version in
pyproject.toml - Commit and push changes
- Create a new release on GitHub
- The workflow automatically builds and publishes to PyPI and TestPyPI
The workflow uses Trusted Publishing (no API tokens needed). Configure trusted publishers:
- PyPI: Add GitHub repository as trusted publisher at https://pypi.org/manage/account/publishing/
- TestPyPI: Add GitHub repository at https://test.pypi.org/manage/account/publishing/
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file hm_semester-26.1.0.tar.gz.
File metadata
- Download URL: hm_semester-26.1.0.tar.gz
- Upload date:
- Size: 12.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5132e6c99dd7d8d1e09fcbd964c131dfef54b167f6aaa4c60b3020be4e3cfc01
|
|
| MD5 |
425aed1268126f996eef9bc674de5c0a
|
|
| BLAKE2b-256 |
193abdc859d6de5a2b877f4ed15d3944d378352647d44d5cfe623bb939381cec
|
Provenance
The following attestation bundles were made for hm_semester-26.1.0.tar.gz:
Publisher:
pypi-release.yml on DavidMStraub/hm-semester
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
hm_semester-26.1.0.tar.gz -
Subject digest:
5132e6c99dd7d8d1e09fcbd964c131dfef54b167f6aaa4c60b3020be4e3cfc01 - Sigstore transparency entry: 871991386
- Sigstore integration time:
-
Permalink:
DavidMStraub/hm-semester@5fd795385064213ed31832d289146694c0619f2f -
Branch / Tag:
refs/heads/main - Owner: https://github.com/DavidMStraub
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-release.yml@5fd795385064213ed31832d289146694c0619f2f -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file hm_semester-26.1.0-py3-none-any.whl.
File metadata
- Download URL: hm_semester-26.1.0-py3-none-any.whl
- Upload date:
- Size: 9.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0e989ec0f96231af8eaf41bd985d8e38f72b1418dd8fdea63c4dcf6e057ecb17
|
|
| MD5 |
5f5c93c5aa427f4f22bb510ef8f71411
|
|
| BLAKE2b-256 |
e90b06d117ffecf0a10bbbf4754f892e7a46c9cce9a90193087e20a83f778f7b
|
Provenance
The following attestation bundles were made for hm_semester-26.1.0-py3-none-any.whl:
Publisher:
pypi-release.yml on DavidMStraub/hm-semester
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
hm_semester-26.1.0-py3-none-any.whl -
Subject digest:
0e989ec0f96231af8eaf41bd985d8e38f72b1418dd8fdea63c4dcf6e057ecb17 - Sigstore transparency entry: 871991411
- Sigstore integration time:
-
Permalink:
DavidMStraub/hm-semester@5fd795385064213ed31832d289146694c0619f2f -
Branch / Tag:
refs/heads/main - Owner: https://github.com/DavidMStraub
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-release.yml@5fd795385064213ed31832d289146694c0619f2f -
Trigger Event:
workflow_dispatch
-
Statement type: