|
from urllib.request import urlopen |
|
from datetime import datetime, timedelta, date |
|
import sys |
|
from collections import defaultdict |
|
import re |
|
|
|
def parse_datetime(dt_str): |
|
"""Parse datetime string from ICS format""" |
|
dt_str = dt_str.split('Z')[0].split('T') |
|
|
|
if len(dt_str) == 1: # Date only |
|
return datetime.strptime(dt_str[0], '%Y%m%d') |
|
else: # DateTime |
|
return datetime.strptime(dt_str[0] + dt_str[1], '%Y%m%d%H%M%S') |
|
|
|
def parse_rrule(rrule_str, start_date, end_date): |
|
""" |
|
Parse recurring rule and return list of dates |
|
Basic implementation for common recurrence patterns |
|
""" |
|
dates = [] |
|
if not rrule_str: |
|
return dates |
|
|
|
rrule_parts = dict(part.split('=') for part in rrule_str.split(';') if '=' in part) |
|
freq = rrule_parts.get('FREQ', '') |
|
count = int(rrule_parts.get('COUNT', '0')) |
|
until_str = rrule_parts.get('UNTIL', '') |
|
interval = int(rrule_parts.get('INTERVAL', '1')) |
|
|
|
if until_str: |
|
try: |
|
until = parse_datetime(until_str) |
|
except ValueError: |
|
until = None |
|
else: |
|
until = None |
|
|
|
current = start_date |
|
occurrences = 0 |
|
|
|
while True: |
|
# Stop conditions |
|
if count and occurrences >= count: |
|
break |
|
if until and current > until: |
|
break |
|
if current > end_date: |
|
break |
|
|
|
dates.append(current) |
|
occurrences += 1 |
|
|
|
# Calculate next occurrence based on frequency |
|
if freq == 'DAILY': |
|
current = current + timedelta(days=interval) |
|
elif freq == 'WEEKLY': |
|
current = current + timedelta(weeks=interval) |
|
elif freq == 'MONTHLY': |
|
# Simple month addition, might need refinement for edge cases |
|
year = current.year + ((current.month - 1 + interval) // 12) |
|
month = ((current.month - 1 + interval) % 12) + 1 |
|
try: |
|
current = current.replace(year=year, month=month) |
|
except ValueError: |
|
# Handle edge case for months with different number of days |
|
current = (current.replace(day=1) + timedelta(days=32)).replace(day=1) |
|
elif freq == 'YEARLY': |
|
try: |
|
current = current.replace(year=current.year + interval) |
|
except ValueError: |
|
# Handle February 29 edge case |
|
current = current.replace(year=current.year + interval, day=28) |
|
else: |
|
break |
|
|
|
return dates |
|
|
|
def get_event_duration(start, end): |
|
"""Calculate duration between two datetime objects in hours""" |
|
return (end - start).total_seconds() / 3600 |
|
|
|
def parse_ics_from_url(url, event_summary_filter=None): |
|
"""Parse ICS file from URL and calculate duration of events""" |
|
yearly_monthly_hours = defaultdict(lambda: defaultdict(float)) |
|
|
|
try: |
|
with urlopen(url) as response: |
|
content = response.read().decode('utf-8').splitlines() |
|
|
|
in_event = False |
|
event_data = {} |
|
all_events = [] |
|
|
|
for line in content: |
|
line = line.strip() |
|
|
|
if line == 'BEGIN:VEVENT': |
|
in_event = True |
|
event_data = {} |
|
elif line == 'END:VEVENT': |
|
if in_event and 'DTSTART' in event_data and 'DTEND' in event_data: |
|
all_events.append(event_data.copy()) |
|
in_event = False |
|
elif in_event and ':' in line: |
|
key, value = line.split(':', 1) |
|
# Handle properties with parameters |
|
if ';' in key: |
|
key = key.split(';')[0] |
|
event_data[key] = value |
|
|
|
# Find date range for all events |
|
all_dates = [] |
|
for event in all_events: |
|
try: |
|
all_dates.append(parse_datetime(event['DTSTART'])) |
|
all_dates.append(parse_datetime(event['DTEND'])) |
|
except (KeyError, ValueError): |
|
continue |
|
|
|
if not all_dates: |
|
return yearly_monthly_hours |
|
|
|
min_date = min(all_dates) |
|
max_date = max(all_dates) |
|
|
|
# Process each event |
|
for event in all_events: |
|
try: |
|
summary = event.get('SUMMARY', '').lower() |
|
if event_summary_filter and event_summary_filter.lower() not in summary: |
|
continue |
|
|
|
start = parse_datetime(event['DTSTART']) |
|
end = parse_datetime(event['DTEND']) |
|
duration = get_event_duration(start, end) |
|
|
|
if(duration > 8): |
|
# print("La durée excède 8h, on coupe") |
|
duration = 8 |
|
|
|
# Handle recurring events |
|
if 'RRULE' in event: |
|
occurrences = parse_rrule(event['RRULE'], start, max_date) |
|
for occurrence_start in occurrences: |
|
# Calculate corresponding end time |
|
occurrence_end = occurrence_start + (end - start) |
|
year = occurrence_start.year |
|
month = occurrence_start.month |
|
yearly_monthly_hours[year][month] += duration |
|
else: |
|
# Single event |
|
year = start.year |
|
month = start.month |
|
yearly_monthly_hours[year][month] += duration |
|
|
|
except (KeyError, ValueError) as e: |
|
print(f"Erreur lors du traitement d'un événement : {e}") |
|
continue |
|
|
|
except Exception as e: |
|
print(f"Erreur lors de la récupération ou l'analyse du fichier ICS : {e}") |
|
sys.exit(1) |
|
|
|
return yearly_monthly_hours |
|
|
|
def hours_to_days_str(hours): |
|
"""Convert hours to days and return formatted string""" |
|
days = hours / 8 |
|
if days == 1: |
|
return f"(1 jour)" |
|
elif days.is_integer(): |
|
return f"({int(days)} jours)" |
|
return f"({days:.1f} jours)" |
|
|
|
def display_yearly_monthly_hours(yearly_monthly_hours): |
|
"""Display hours grouped by year and month in French""" |
|
if not yearly_monthly_hours: |
|
print("Aucun événement trouvé") |
|
return |
|
|
|
month_names = { |
|
1: "Janvier", 2: "Février", 3: "Mars", 4: "Avril", |
|
5: "Mai", 6: "Juin", 7: "Juillet", 8: "Août", |
|
9: "Septembre", 10: "Octobre", 11: "Novembre", 12: "Décembre" |
|
} |
|
|
|
|
|
print("\nTemps passé par année et par mois :") |
|
print("=" * 70) |
|
|
|
for year in sorted(yearly_monthly_hours.keys()): |
|
year_total = 0 |
|
print(f"\nAnnée {year}") |
|
print("-" * 70) |
|
|
|
for month in sorted(yearly_monthly_hours[year].keys()): |
|
hours = yearly_monthly_hours[year][month] |
|
year_total += hours |
|
days_str = hours_to_days_str(hours) |
|
print(f"{month_names[month]:10} : {hours:6.1f} heures {days_str:>15}") |
|
|
|
print("-" * 70) |
|
year_days_str = hours_to_days_str(year_total) |
|
print(f"Total Année : {year_total:6.1f} heures {year_days_str:>15}") |
|
|
|
def main(): |
|
if len(sys.argv) < 2: |
|
print("Usage: python script.py <url_ics> [filtre_evenement]") |
|
sys.exit(1) |
|
|
|
ics_url = sys.argv[1] |
|
event_filter = sys.argv[2] if len(sys.argv) > 2 else None |
|
|
|
if event_filter: |
|
print(f"Filtrage des événements contenant : {event_filter}") |
|
|
|
yearly_monthly_hours = parse_ics_from_url(ics_url, event_filter) |
|
display_yearly_monthly_hours(yearly_monthly_hours) |
|
|
|
if __name__ == "__main__": |
|
main() |