|
#! /usr/bin/env python3 |
|
|
|
import datetime |
|
from datetime import date, timedelta |
|
import os.path |
|
import sys |
|
import argparse |
|
|
|
from google.auth.transport.requests import Request |
|
from google.oauth2.credentials import Credentials |
|
from google_auth_oauthlib.flow import InstalledAppFlow |
|
from googleapiclient.discovery import build |
|
from googleapiclient.errors import HttpError |
|
|
|
parser = argparse.ArgumentParser(description="Calculate calendar days for infant development development based on https://thewonderweeks.com and the baby's due date.") |
|
parser.add_argument("due_date", type=str, help="Due date string in ISO 8601 e.g. 2026-03-15") |
|
parser.add_argument("--no_dry_run", action="store_true", help="Create Google Calendar events. CALENDAR_ID must be set and a credentials.json file must be in the same directory.") |
|
args = parser.parse_args() |
|
|
|
DUE_DATE = date.fromisoformat(args.due_date) |
|
|
|
with open('./weeks.txt', 'r') as f: |
|
entries = [line.rstrip() for line in f] |
|
|
|
class Event(): |
|
leap = None |
|
start = None |
|
kind = None |
|
end = None |
|
|
|
def __init__(self, leap, start, kind): |
|
self.leap = leap |
|
self.start = start |
|
self.kind = kind |
|
|
|
def __str__(self): |
|
if self.end: |
|
return f'Leap {self.leap}\t\t{self.start_date()} - {self.end_date()}' |
|
else: |
|
return f'Leap {self.leap} {self.kind}\t{self.start_date()}' |
|
|
|
def name(self): |
|
if self.end: |
|
return f'Leap {self.leap}' |
|
else: |
|
return f'Leap {self.leap} {self.kind_symbol()}' |
|
|
|
def kind_symbol(self): |
|
match self.kind: |
|
case "sunny": |
|
return "☀️" |
|
case "fussy": |
|
return "🌩" |
|
|
|
def start_date(self): |
|
return DUE_DATE + timedelta(days=self.start) |
|
|
|
def end_date(self): |
|
return DUE_DATE + timedelta(days=self.end) if self.end else None |
|
|
|
def parse_weeks(weeks): |
|
pieces = weeks.split('.') |
|
|
|
days = int(pieces[0]) * 7 |
|
|
|
if weeks.endswith('.5'): |
|
days += 3 |
|
|
|
return days |
|
|
|
events = [] |
|
current_multi_day_event = None |
|
leap = 1 |
|
for entry in entries: |
|
weeks, kind = entry.split(' ') |
|
|
|
if not kind in ['start', 'end', 'fussy', 'sunny']: |
|
raise ValueError(f'Not a valid event type: {kind}') |
|
|
|
days = parse_weeks(weeks) |
|
|
|
match kind: |
|
case 'start': |
|
event = Event(leap, days, kind) |
|
events.append(event) |
|
current_multi_day_event = event |
|
case 'end': |
|
current_multi_day_event.end = days |
|
current_multi_day_event = None |
|
case 'fussy': |
|
event = Event(leap, days, kind) |
|
events.append(event) |
|
case 'sunny': |
|
event = Event(leap, days, kind) |
|
events.append(event) |
|
leap += 1 |
|
|
|
SCOPES = ['https://www.googleapis.com/auth/calendar.events'] |
|
CALENDAR_ID = os.getenv('CALENDAR_ID') |
|
|
|
def authenticate_google_calendar(): |
|
creds = None |
|
|
|
if os.path.exists('token.json'): |
|
creds = Credentials.from_authorized_user_file('token.json', SCOPES) |
|
|
|
if not creds or not creds.valid: |
|
if creds and creds.expired and creds.refresh_token: |
|
creds.refresh(Request()) |
|
else: |
|
flow = InstalledAppFlow.from_client_secrets_file('credentials.json', SCOPES) |
|
creds = flow.run_local_server(port=0) |
|
|
|
with open('token.json', 'w') as token: |
|
token.write(creds.to_json()) |
|
|
|
return build('calendar', 'v3', credentials=creds) |
|
|
|
def create_calendar_event(service, name, start, end=None): |
|
try: |
|
if not end: |
|
# Single day event: end date is exactly 1 day after the start date. |
|
end = start + datetime.timedelta(days=1) |
|
|
|
event_body = { |
|
'summary': name, |
|
'start': { |
|
'date': start.isoformat(), |
|
}, |
|
'end': { |
|
'date': end.isoformat(), |
|
} |
|
} |
|
|
|
return service.events().insert(calendarId=CALENDAR_ID, body=event_body).execute() |
|
|
|
except HttpError as error: |
|
print(f"❌ An error occurred interacting with the Calendar API: {error}", file=sys.stderr) |
|
|
|
if args.no_dry_run: |
|
service = authenticate_google_calendar() |
|
|
|
for event in events: |
|
print(event) |
|
gcal_event = create_calendar_event(service, event.name(), event.start_date(), event.end_date()) |
|
print(f'\tCreated {event}\n\t{gcal_event.get('htmlLink')}') |
|
|
|
else: |
|
for event in events: |
|
print(event) |