import bisect from datetime import datetime, timedelta from flask import Flask, jsonify, redirect, request from google.auth.transport.requests import Request from google.oauth2.credentials import Credentials from googleapiclient.discovery import build import pytz # Monday to Friday WEEK_DAYS = {1, 2, 3, 4, 5} # 9 to 5 FROM = 9 TO = 17 TIMEZONE = pytz.timezone('America/New_York') AVAILABILITY_LIFETIME = timedelta(minutes=2) SCOPES = ['https://www.googleapis.com/auth/calendar'] UTC = pytz.timezone('UTC') creds = Credentials.from_authorized_user_file('creds.json', SCOPES) def read_datetime(dct): dt = datetime.fromisoformat(dct['dateTime']).replace(tzinfo=None) dt = pytz.timezone(dct['timeZone']).localize(dt) return dt def asutciso(dt): return dt.astimezone(UTC).isoformat()[:19] + 'Z' def aslocal(dt): return dt.astimezone(TIMEZONE).strftime('%Y-%m-%d %H:%M:%S') def get_availability(): if not creds.valid: creds.refresh(Request()) service = build('calendar', 'v3', credentials=creds) # Get events for the next 2 weeks now = datetime.utcnow() start = datetime(now.year, now.month, now.day) end = start + timedelta(days=14) events_result = service.events().list( calendarId='primary', timeMin=asutciso(now), timeMax=asutciso(end), maxResults=200, singleEvents=True, orderBy='startTime', ).execute() # Build a list of possible time slots for the next 2 weeks availability = [] for day in range(14): day = start + timedelta(days=day) if day.weekday() not in WEEK_DAYS: continue for hour in range(FROM, TO): for minute in (0, 30): availability.append(TIMEZONE.localize( day.replace(hour=hour, minute=minute), )) for event in events_result.get('items', []): event_start = read_datetime(event['start']) event_end = read_datetime(event['end']) # Find the first slot that starts less than 30min before this event first_idx = bisect.bisect_right( availability, event_start - timedelta(minutes=30), ) # Find the first slot that starts after this event ends last_idx = bisect.bisect_left( availability, event_end, ) # Remove the slots from the availability del availability[first_idx:last_idx] return availability availability = get_availability() availability_as_of_date = datetime.utcnow() app = Flask('calendar_booking_api') @app.route('/availability') def api_availability(): global availability, availability_as_of_date if ( availability_as_of_date is None or availability_as_of_date + AVAILABILITY_LIFETIME < datetime.utcnow() ): availability = get_availability() availability_as_of_date = datetime.utcnow() return jsonify({'availability': [asutciso(time) for time in availability]}) @app.route('/book', methods=['POST']) def api_book(): TODO