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()