shift-to-ical / app.py
Tal
reedit
9e79a81
import re
from datetime import datetime, timedelta
from icalendar import Calendar, Event, Timezone, TimezoneStandard, TimezoneDaylight, vRecur
import pytz
import uuid
import gradio as gr
import os
# Define the shift pattern to match: DD.MM. Day ShiftCode HH.MM - HH.MM
shift_pattern = re.compile(
r'(\d{2}\.\d{2})\.?\s+' # Date (DD.MM or DD.MM.)
r'[A-Za-z]{2}\s+' # Day abbreviation (ignored)
r'([A-Za-z0-9]+)\s+' # Shift code (allow lowercase)
r'(\d{2}\.\d{2})\s+-\s+(\d{2}\.\d{2})' # Time range
)
def create_shift_calendar(shift_data, year=datetime.now().year):
# Split data into lines and remove empty lines
lines = [line.strip() for line in shift_data.split('\n') if line.strip()]
# Create a new calendar with specified format
cal = Calendar()
cal.add('prodid', '-//Gemini AI//Google Calendar Creator//EN')
cal.add('version', '2.0')
cal.add('calscale', 'GREGORIAN')
cal.add('method', 'PUBLISH')
cal.add('x-wr-calname', 'Work Shifts')
cal.add('x-wr-timezone', 'Europe/Helsinki')
# Add timezone component for Europe/Helsinki
tz = Timezone()
tz.add('tzid', 'Europe/Helsinki')
# Standard time (UTC+2)
std = TimezoneStandard()
std.add('tzname', 'EET')
std.add('dtstart', datetime(1970, 10, 25, 3, 0, 0))
std.add('tzoffsetto', timedelta(hours=2))
std.add('tzoffsetfrom', timedelta(hours=3))
std.add('rrule', vRecur(freq='YEARLY', bymonth=10, byweekday=['SU'], bysetpos=-1))
tz.add_component(std)
# Daylight saving time (UTC+3)
dst = TimezoneDaylight()
dst.add('tzname', 'EEST')
dst.add('dtstart', datetime(1970, 3, 29, 2, 0, 0))
dst.add('tzoffsetto', timedelta(hours=3))
dst.add('tzoffsetfrom', timedelta(hours=2))
dst.add('rrule', vRecur(freq='YEARLY', bymonth=3, byweekday=['SU'], bysetpos=-1))
tz.add_component(dst)
cal.add_component(tz)
# Get the timezone object
helsinki_tz = pytz.timezone('Europe/Helsinki')
for line in lines:
match = shift_pattern.match(line)
if not match:
print(f"Warning: Could not parse line '{line}'")
continue
try:
# Extract components from the match groups
date_str = match.group(1) # First group: date (DD.MM)
shift_code = match.group(2) # Second group: shift code
start_time_str = match.group(3) # Third group: start time
end_time_str = match.group(4) # Fourth group: end time
# Create datetime objects and localize to Helsinki time
start_dt_naive = datetime.strptime(
f"{date_str}.{year} {start_time_str.replace('.', ':')}",
"%d.%m.%Y %H:%M"
)
end_dt_naive = datetime.strptime(
f"{date_str}.{year} {end_time_str.replace('.', ':')}",
"%d.%m.%Y %H:%M"
)
# Localize to Helsinki timezone and convert to UTC
start_dt_helsinki = helsinki_tz.localize(start_dt_naive)
end_dt_helsinki = helsinki_tz.localize(end_dt_naive)
start_dt_utc = start_dt_helsinki.astimezone(pytz.utc)
end_dt_utc = end_dt_helsinki.astimezone(pytz.utc)
# Create event with UTC times
event = Event()
event.add('summary', f'Work Shift: {shift_code}')
event.add('dtstart', start_dt_utc)
event.add('dtend', end_dt_utc)
event.add('description', f'Shift code: {shift_code}')
event.add('uid', str(uuid.uuid4()))
event.add('dtstamp', datetime.utcnow())
# Add event to calendar
cal.add_component(event)
except ValueError as e:
print(f"Warning: Could not parse date/time on line '{line}'. Error: {e}")
continue
return cal.to_ical() # Return the calendar data
def generate_ical(shift_data):
"""
Takes raw shift data, generates an iCalendar file, and returns the path to the file.
"""
if not shift_data:
# Gradio needs a file path to be returned, so create an empty file.
with open("shifts_empty.ics", "w") as f:
pass
return "shifts_empty.ics"
ics_data = create_shift_calendar(shift_data)
# Define the output file path
file_path = "shifts.ics"
# Write the iCalendar data to the file
with open(file_path, 'wb') as f:
f.write(ics_data)
return file_path
# Create the Gradio interface
demo = gr.Interface(
fn=generate_ical,
inputs=gr.Textbox(
lines=15,
label="Paste Shift Data Here",
placeholder="Example:\n01.07.\tTi\tA5\t07.30 - 15.42\n02.07.\tKe\tI2\t13.00 - 21.00\n..."
),
outputs=gr.File(label="Download iCalendar File"),
title="Shift to iCalendar Converter",
description="Paste your shift data in the format 'DD.MM. Day ShiftCode HH.MM - HH.MM' (one shift per line). The script will generate a downloadable .ics file that you can import into your calendar application.",
allow_flagging="never",
)
if __name__ == "__main__":
demo.launch()