[Script] Syncing dates to google calendar like in Dynalist

Hi everyone,

I wanted to replicate the feature from Dynalist where dates are synced to the google calendar. I wrote a python script that parses the vault for dates [[YYYY-MM-DD]] and creates events in the google calendar with a link to open them in Obsidian.

Steps to get this working:

  1. Create your google calendar API to create your credentials.json as described here Python quickstart  |  Google Calendar  |  Google for Developers
  2. Update the constants at the top of the python script
  3. Run the script every time to sync new dates to the calendar
import glob
import sys
import os
import re
import datetime
import pickle
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request

# Constants
SCOPES = ['https://www.googleapis.com/auth/calendar']
CALENDAR_ID = 'YOUR_CALENDAR_ID'
VAULT = 'YOUR_VAULT_NAME'

def setup_service():
    creds = None
    token_file = 'token.pickle'
    
    # Load or generate token
    if os.path.exists(token_file):
        with open(token_file, 'rb') as token:
            creds = pickle.load(token)
    
    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_file, 'wb') as token:
            pickle.dump(creds, token)
    
    service = build('calendar', 'v3', credentials=creds)
    return service

def main():
    # Handle command line arguments
    if len(sys.argv) != 2:
        print('Usage: python cal-md-dates.py <directory>')
        return

    service = setup_service()
    
    # Parse MD files and create events
    md_directory = sys.argv[1]
    # Recursively find all md files in directory
    md_files = glob.glob(md_directory + '/**/*.md', recursive=True)

    # Retrieve all events in the calendar
    all_events = service.events().list(
        calendarId=CALENDAR_ID,
        singleEvents=True,
        orderBy='startTime'
    ).execute()
    # Create a db of events mapping from (text, date) to event_id
    event_db = {}
    for event in all_events['items']:
        event_db[(event.get('summary', 'Untitled'),
                  event['start']['date'],
                  event.get('description', ''))] = event['id']
    
    for file_path in md_files:
        with open(file_path, 'r') as f:
            content = f.read()
            dates = re.findall(r'^\s*([\-\*#]*)((.*?)\[\[(\d{4}-\d{2}-\d{2})\]\].*)', content, flags=re.MULTILINE)
            
            for match in dates:
                type = match[0]
                anchor = match[1].strip().replace(' ', '%20')
                text = match[2].strip() or 'Untitled'
                date_str = match[3]
                obsidian_file = (file_path
                                    .replace(md_directory, '')
                                    .strip('/')
                                    .replace('.md', '')
                                    .replace('/', '%2F'))
                obsidian_link = f'obsidian://open?vault={VAULT}&file={obsidian_file}'
                if type.startswith('#'):
                    obsidian_link += f'%23{anchor}'
                print(f'Found: "{text}" on "{date_str}" at "{obsidian_link}"')
                date = datetime.datetime.strptime(date_str, '%Y-%m-%d').date()

                # Skip if event already exists
                if (text, date_str, obsidian_link) in event_db:
                    print(f'Skipping event: "{text}" on "{date_str}"')
                    continue

                print (f'Creating event: "{text}" on "{date_str}"')
                event = {
                    'summary': text,
                    'start': {
                        'date': date.strftime('%Y-%m-%d'),
                        'timeZone': 'UTC',
                    },
                    'end': {
                        'date': (date + datetime.timedelta(days=1)).strftime('%Y-%m-%d'),
                        'timeZone': 'UTC',
                    },
                    'description': obsidian_link,
                }
                service.events().insert(calendarId=CALENDAR_ID, body=event).execute()

if __name__ == '__main__':
    main()

3 Likes