Spaces:
Sleeping
Sleeping
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() | |