All checks were successful
CI / lint-and-test (push) Successful in 21s
- Introduced a new `normalize_lang` function to standardize language codes across the application, ensuring consistent handling of user language preferences. - Refactored date handling utilities by adding `parse_utc_iso` and `parse_utc_iso_naive` functions for better parsing of ISO 8601 date strings, enhancing timezone awareness. - Updated various modules to utilize the new language normalization and date parsing functions, improving code clarity and maintainability. - Enhanced error handling in date validation to raise specific `DateRangeValidationError` exceptions, providing clearer feedback on validation issues. - Improved test coverage for date range validation and language normalization functionalities, ensuring robustness and reliability.
53 lines
1.7 KiB
Python
53 lines
1.7 KiB
Python
"""Generate ICS calendar containing only one user's duties (for subscription link)."""
|
|
|
|
from datetime import datetime, timezone
|
|
|
|
from icalendar import Calendar, Event
|
|
|
|
from duty_teller.db.models import Duty
|
|
from duty_teller.utils.dates import parse_utc_iso
|
|
|
|
# Summary labels by event_type (duty | unavailable | vacation)
|
|
SUMMARY_BY_TYPE: dict[str, str] = {
|
|
"duty": "Duty",
|
|
"unavailable": "Unavailable",
|
|
"vacation": "Vacation",
|
|
}
|
|
|
|
|
|
def build_personal_ics(duties_with_name: list[tuple[Duty, str]]) -> bytes:
|
|
"""Build a VCALENDAR (ICS) with one VEVENT per duty.
|
|
|
|
Args:
|
|
duties_with_name: List of (Duty, full_name). full_name is available for
|
|
DESCRIPTION; SUMMARY is taken from event_type (duty/unavailable/vacation).
|
|
|
|
Returns:
|
|
ICS file content as bytes (UTF-8).
|
|
"""
|
|
cal = Calendar()
|
|
cal.add("prodid", "-//Duty Teller//Personal Calendar//EN")
|
|
cal.add("version", "2.0")
|
|
cal.add("calscale", "GREGORIAN")
|
|
|
|
for duty, _full_name in duties_with_name:
|
|
event = Event()
|
|
start_dt = parse_utc_iso(duty.start_at)
|
|
end_dt = parse_utc_iso(duty.end_at)
|
|
# Ensure timezone-aware for icalendar
|
|
if start_dt.tzinfo is None:
|
|
start_dt = start_dt.replace(tzinfo=timezone.utc)
|
|
if end_dt.tzinfo is None:
|
|
end_dt = end_dt.replace(tzinfo=timezone.utc)
|
|
event.add("dtstart", start_dt)
|
|
event.add("dtend", end_dt)
|
|
summary = SUMMARY_BY_TYPE.get(
|
|
duty.event_type if duty.event_type else "duty", "Duty"
|
|
)
|
|
event.add("summary", summary)
|
|
event.add("uid", f"duty-{duty.id}@duty-teller")
|
|
event.add("dtstamp", datetime.now(timezone.utc))
|
|
cal.add_component(event)
|
|
|
|
return cal.to_ical()
|