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:
- Create your google calendar API to create your credentials.json as described here Python quickstart | Google Calendar | Google for Developers
- Update the constants at the top of the python script
- 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()