Import Calendar Events for Today

I’ve read the threads here in the forum on importing calendar events along with trying to manipulate calendar actions based on the Calendar Drafts Script Reference… but my limited skills are preventing me from achieving my goal - which is:

Create an Action that inserts “today’s” calendar events from “all” my calendars in the following format:
Time (in 24h) + Event Title

That’s it… all I am looking for.

I’d like to avoid date and calendar pickers… and eliminate all the necessary date data…

Is this possible?

This should do what you want in a new draft, if you add an action with a single script step:


// script to get all events for
// all calendars for the next 
// day, and add to a new draft
// by tf2

// get all calendars
let cals = Calendar.getAllCalendars();

// set a header string to hold
// the result 
aDate = new Date();

aStr = aDate.getFullYear()+'-' + (pad2(aDate.getMonth()+1)) + '-'+ pad2(aDate.getDate());
result = "# Today's Agenda for ";
result += aStr + "\n\n";

// cycle thru calendars
for (let cal of cals) {
  if (cal) {
    // set today & tomorrow
    let today = new Date();
    let tomorrow = new Date(today);
    tomorrow.setDate(tomorrow.getDate() + 1);
    // get all events
    let events = cal.events(today, tomorrow);
    // cycle thru each event
    for (let event of events) {
      // get start date
      let start = event.startDate;
      // get hour, minutes
      let st_hour = start.getHours();
      let st_min = pad2(start.getMinutes());
      // add to result
      result += st_hour+":"+st_min+" — ";
      result += event.title;
      result += "\n";
    }
  }
}

let d = new Draft();
d.content = result;
d.addTag("agenda");
d.update();

// function to ensure minutes 
// are two digits; from
// https://electrictoolbox.com/pad-number-two-digits-javascript/
function pad2(number) {
  return (number < 10 ? '0' : '') + number
}

6 Likes

Amazing! Is it possible to prepend on the end of each line the name of the calendar?

@tf2 This is brilliant - thank you so much.

Reading through it helps me understand the process.

Curious about two parts:

  1. Researching “Appending Script to Current Draft” appears to have limitations. If “Append” is not an option - could the script simply add the results to the clipboard?

For example - running the script returns:

15:30 — Meeting 1
18:30 — Meeting 2

I could then paste the result directly into the draft I want to see it in.

  1. What date parameter would I change in order to get tomorrows events?

Many thanks again!

Easy to do. In the below version, I added a setting (preCal) at the top. If you change it from false to true, it will prepend the calendar to each event.


// version 0.1.1 (20210526)
// script to get all events for
// all calendars for the next 
// day, and add to a new draft
// by tf2

// SETTINGS

// true=>prepend calendar name 
let preCal = false;

// get all calendars
let cals = Calendar.getAllCalendars();

// set a header string to hold
// the result 
aDate = new Date();

aStr = aDate.getFullYear()+'-' + (pad2(aDate.getMonth()+1)) + '-'+ pad2(aDate.getDate());
result = "# Today's Agenda for ";
result += aStr + "\n\n";

// cycle thru calendars
for (let cal of cals) {
  if (cal) {
    // set today & tomorrow
    let today = new Date();
    let tomorrow = new Date(today);
    tomorrow.setDate(tomorrow.getDate() + 1);
    // get all events
    let events = cal.events(today, tomorrow);
    // cycle thru each event
    for (let event of events) {
      // get start date
      let start = event.startDate;
      // get hour, minutes
      let st_hour = start.getHours();
      let st_min = pad2(start.getMinutes());
      // add to result
      if (preCal) {
        result += cal.title;
        result += " — ";
      }
      result += st_hour+":"+st_min+" — ";
      result += event.title;
      result += "\n";
    }
  }
}

let d = new Draft();
d.content = result;
d.addTag("agenda");
d.update();

// function to ensure minutes 
// are two digits; from
// https://electrictoolbox.com/pad-number-two-digits-javascript/
function pad2(number) {
  return (number < 10 ? '0' : '') + number
}

1 Like

Working through today and tomorrow:

Days = 24 hours…

Changing this line:

to tomorrow.setDate(tomorrow.getDate() + 2); = 48 hours… > helpful if planning for tomorrow and more than 24 is required to capture tomorrows events.

Still working though either appending or capturing the result to the clipboard…

Was able to add the events results to the clipboard by following @sylumer code to post the resulting string to the clipboard.

Here is my modified version of @tf2 script <thank you!>

I can’t figure out how to completely eliminate the date from the first line - but I can delete it manually:

// script to get all events for
// all calendars for the next 
// day, and add to a new draft
// by tf2

// get all calendars
let cals = Calendar.getAllCalendars();

// set a header string to hold
// the result 
aDate = new Date();

aStr = aDate.getFullYear()+'-' + (pad2(aDate.getMonth()+1)) + '-'+ pad2(aDate.getDate());
result = "";
result += aStr + "\n";

// cycle thru calendars
for (let cal of cals) {
  if (cal) {
    // set today & tomorrow
    let today = new Date();
    let tomorrow = new Date(today);
    tomorrow.setDate(tomorrow.getDate() + 2);
    // get all events
    let events = cal.events(today, tomorrow);
    // cycle thru each event
    for (let event of events) {
      // get start date
      let start = event.startDate;
      // get hour, minutes
      let st_hour = start.getHours();
      let st_min = pad2(start.getMinutes());
      // add to result
      result += st_hour+":"+st_min+" ";
      result += event.title;
      result += "\n";
    }
  }
}

let d = new Draft();
d.content = result;
app.setClipboard(result);
console.log(result);
d.update();

// function to ensure minutes 
// are two digits; from
// https://electrictoolbox.com/pad-number-two-digits-javascript/
function pad2(number) {
  return (number < 10 ? '0' : '') + number
}

Here’s a revised version that does several things by changing values at the top of the script:

  1. Set whether to prepend the calendar name to each line

  2. Set a date range to display events from, as an offset from today’s date, in days (0 is today, 1 is tomorrow, etc.) — note that this handles month ends nicely, so May 31 + 1 day should give you June 1 instead of May 32, thanks to a function I found online.

  3. Customizes the header based on the date range you pick, with a special “today” value if you set the offsets to 0 and 1

  4. Choose whether to append the result to the current draft or create a new draft.

Enjoy.


// version 0.1.2 (20210526.2)
// script to get all events for
// all calendars for the next 
// day, and add to a new draft
// by tf2

// SETTINGS

// prepend calendar name 
// to each line: set true
let preCal = false;

// append agenda to current
// draft: set true
// new draft: set false
let append = true;

// date offset determined the
// range of days for which
// events are shown. Number is
// added to today's date; 0 means
// today, 1 means tomorrow, etc
let st = 0;
let en = 1;
// set today's date
let aDate = new Date();
// set start date
let sDate = aDate.addDays(st);
let eDate = aDate.addDays(en)

// get all calendars
let cals = Calendar.getAllCalendars();

// set header
// set a header string to hold
// the result 
let sStr = sDate.getFullYear()+'-' + (pad2(aDate.getMonth()+1)) + '-'+ pad2(aDate.getDate());
let eStr = eDate.getFullYear()+'-' + (pad2(aDate.getMonth()+1)) + '-'+ pad2(aDate.getDate());
result = "# Agenda for ";
// customize header for today
if (st == 0 && en == 1){
  result += "today\n\n";
}
else {
  result += sStr + " to " + eStr + "\n\n";
}

// cycle thru calendars
for (let cal of cals) {
  if (cal) {
    // set today & tomorrow
    let startD = new Date();
    startD.setDate(startD.getDate() + st);
    let endD = new Date(startD);
    endD.setDate(endD.getDate() + en);
    // get all events
    let events = cal.events(startD, endD);
    // cycle thru each event
    for (let event of events) {
      // get start date
      let start = event.startDate;
      // get hour, minutes
      let st_hour = start.getHours();
      let st_min = pad2(start.getMinutes());
      // add to result
      if (preCal) {
        result += cal.title;
        result += " — ";
      }
      result += st_hour+":"+st_min+" — ";
      result += event.title;
      result += "\n";
    }
  }
}

// append to a new draft or
// create a new one, using
// append setting from top
if (append) {
  result = "\n"+result;
  draft.append(result);
  draft.addTag("agenda");
  draft.update;
}
else {
  let d = new Draft();
  d.content = result;
  d.addTag("agenda");
  d.update();
}

// function to ensure dates
// and times are padded 
// to two digits; from
// https://electrictoolbox.com/pad-number-two-digits-javascript/
function pad2(number) {
  return (number < 10 ? '0' : '') + number
}

// function to add days to
// a date, from:
// https://stackoverflow.com/questions/563406/add-days-to-javascript-date
Date.prototype.addDays = function(days) {
    var date = new Date(this.valueOf());
    date.setDate(date.getDate() + days);
    return date;
}


3 Likes

You already did a great job by providing such powerful automation. However, I have another suggestion/request. Are you interested to expand your script to include reminders from Apple Reminders and create a section with heading: Due today or something similar?
This way you could get your mini dashboard for today which will include both events and reminders😊

Great idea. I can probably do this, but maybe not until tonight. (This is more or less how I’m teaching myself Javascript, so thank you!)

2 Likes

Some more settings and functionality, mostly to incorporate reminders:

  • set remind in the settings section to change how reminders are handled:
    • false (no quotes) ignores Reminders altogether
    • ”inc” (with quotes) retrieves just incomplete tasks
    • ”comp” (with quotes) retrieves just completed tasks
    • ”due” (with quotes) retrieves incomplete tasks that have due dates on or after the start date (same as for events, set separately, st)
  • set preRem to true (no quotes) to add the name of the Reminder list each reminder belongs to
  • if you use MultiMarkdown or some other syntaxes, the headings should stand out and the checkboxes should be tappable; however, marking a task as done in the draft won’t affect the original reminder in any way.

// version 0.1.3 (20210526.3)
// script to get all events for
// all calendars for the next 
// day, and add to a new draft
// by tf2

// SETTINGS

// append agenda to current
// draft: set true
// new draft: set false
let append = true;

// date offset determined the
// range of days for which
// events are shown. Number is
// added to today's date; 0 means
// today, 1 means tomorrow, etc
let st = 0;
let en = 1;
// set today's date
let aDate = new Date();
let aaDate = new Date();
// set start date
let sDate = aDate.addDays(st);
let eDate = aaDate.addDays(en)

// include Reminders?
// no = false (no quotes)
// "inc" = all incomplete tasks
// "due" = incomplete tasks due in
//		range set above
// "comp" = completed tasks
// "all" = all tasks
var remind = "due";

// prepend calendar name
// or reminder list 
// to each line: set true
let preCal = false;
let preRem = false;


// START SCRIPT PROPER

// set header
// set a header string to hold
// the result 
let sStr = sDate.getFullYear()+'-' + (pad2(sDate.getMonth()+1)) + '-'+ pad2(sDate.getDate());
let eStr = eDate.getFullYear()+'-' + (pad2(eDate.getMonth()+1)) + '-'+ pad2(eDate.getDate());
result = "# Agenda for ";
// customize header for today
if (st == 0 && en == 1){
  result += "today\n\n";
}
else {
  result += sStr + " to " + eStr + "\n\n";
}

// get all calendars
let cals = Calendar.getAllCalendars();

// cycle thru calendars
for (let cal of cals) {
  if (cal) {
    // set today & tomorrow
    let startD = new Date();
    startD.setDate(startD.getDate() + st);
    let endD = new Date(startD);
    endD.setDate(endD.getDate() + en);
    // get all events
    let events = cal.events(startD, endD);
    // cycle thru each event
    for (let event of events) {
      // get start date
      let start = event.startDate;
      // get hour, minutes
      let st_hour = start.getHours();
      let st_min = pad2(start.getMinutes());
      // add to result
      if (preCal) {
        result += cal.title;
        result += " — ";
      }
      result += st_hour+":"+st_min+" — ";
      result += event.title;
      result += "\n";
    }
  }
}

// retrieve reminders if remind
// is set to true at top
if (remind) {
  let rLists = ReminderList.getAllReminderLists();
  let remResult = "";
  for (let rList in rLists){
    if (remind == "all"){
      var rems = rLists[rList].tasks;
    }
    else if (remind == "comp") {
      var rems = rLists[rList].completeTasks;
    }
    else { // incomplete + due
      var rems = rLists[rList].incompleteTasks;
    }
    for (let rem in rems) {
      let val = rems[rem].isCompleted
      if (val == true) {
      	var symb = "- [x] ";
      }
      else {
        var symb = "- [ ] ";
      }
      let text = "";
      if (preRem = true) {
        text += rLists[rList].title+": "; 
      }
      text += rems[rem].title;
      let dueD = rems[rem].dueDate;
      if (remind == "due") {
      	if (dueD) {
          if (dueD.valueOf() >= sDate.valueOf()){
            let remDstr = dueD.getFullYear() + "-" + (dueD.getMonth()+1) + "-" + dueD.getDate();
            remResult += symb + text + " (due: " + remDstr + ")\n";
          }          
        }
      }
      else { // all incomplete tasks
        remResult += symb + text + "\n";
      }
    } // end rem in rems
  } // end rList in rLists
  if (remResult != "") {
    result += "\n## Reminders\n\n" + remResult;
  }
} // end if remind

// append to a new draft or
// create a new one, using
// append setting from top
if (append) {
  result = "\n"+result;
  draft.append(result);
  draft.addTag("agenda");
  draft.update;
}
else {
  let d = new Draft();
  d.content = result;
  d.addTag("agenda");
  d.update();
}

// function to ensure dates
// and times are padded 
// to two digits; from
// https://electrictoolbox.com/pad-number-two-digits-javascript/
function pad2(number) {
  return (number < 10 ? '0' : '') + number
}

// function to add days to
// a date, from:
// https://stackoverflow.com/questions/563406/add-days-to-javascript-date
Date.prototype.addDays = function(days) {
    var date = new Date(this.valueOf());
    date.setDate(date.getDate() + days);
    return date;
}

7 Likes

Looks like I missed this one the first time round, but I’m glad it resurfaced. I currently run a shortcut every morning that archives yesterday’s activity (calendar, completed reminders and links to modified drafts) in yesterday’s daily journal. Been meaning to port that shortcut to Drafts, and this looks like it might provide me with a good foundation for doing so. Thanks!

3 Likes

Hi all,
Just an amazing thread here with plenty of solution!!
I have multiple Calendar, (Perso, Pro…) how is possible to order the result by time and not by agenda/calendar? To have a clear view of the day starting with the fist appointment and next with the second in a time line

Thanks
regards
Patrick

2 Likes

I’m delighted to find this thread! I’ve been wrestling with a number of ways to do this since I upgraded to Sonoma and no longer have access to icalBuddy.

I’ll try to share my version when I get it running (I’m kind of new to Drafts).

It took me a bit to learn how to program/script in Drafts. I am in debt to @tf2 for starting this thread. I would have been lost without his work (and all the others who contributed). @Patrick68, I hope I’ve done this in a form that’s useful to you.

// script to get all events for
// all calendars for the today, only 
// day, and add to a new draft
// by herbslick, 8 Feb 2024
// inspired by tf2

// get all calendars
let cals = Calendar.getAllCalendars();


// set a header string to hold
// the result 
aDate = new Date();

// Initialize the string with a header 'Today's Agenda for YYYY-MM-DD'
aStr = aDate.getFullYear()+'-' + (pad2(aDate.getMonth()+1)) + '-'+ pad2(aDate.getDate());
result = "# Today's Agenda for ";
result += aStr + "\n\n";

// cycle thru calendars
for (let cal of cals) {
  if (cal) {
	// Get the name of the Calendar, 'thisCal'
    let thisCal = cal.title;
    // set today & tomorrow
    let today = new Date().setHours(0,0,0,0);
    let tomorrow = new Date(today);
    tomorrow.setDate(tomorrow.getDate() + 1);
    // get all events
    let events = cal.events(today, tomorrow);
    // cycle thru each event
    for (let event of events) {
      // get start date
      let start = event.startDate;
      // get hour, minutes
      let st_hour = pad2(start.getHours());
      let st_min = pad2(start.getMinutes());
      // get end date
      let end = event.endDate;
      // get hour, minutes
      let en_hour = pad2(end.getHours());
      let en_min = pad2(end.getMinutes());
      // add to result
      // Check to see if its an "all-day" event, where start time is "0". If so, process it without start-end times
      if (st_hour != "00") {
        startTimeFmt = start.toString("h:mm tt");
        endTimeFmt = end.toString("h:mm tt");
        result += st_hour+st_min+en_hour+en_min+"• "; // this sets up a sortable lead for each event and ends it with a bullet-space
		result += startTimeFmt+"-"+endTimeFmt+", "; // this is the pretty-formatted start to end time
        result += event.title; // this appends the event title (or the name of the event)
		result += " ("+thisCal+")"; // this appends the calendar name in parenthesis to the event
        result += "\n"; // this adds the obligatory newline, linefeed character
      } else {
		// handling the "all-day" events here...
        result += st_hour+st_min+en_hour+en_min+"• ";  // this sets up a sortable lead for each event and ends it with a bullet-space. It will be '0000-2359• '
		result += event.title; // since this is an all-day event, there is no start or end time, so just put in the event title (the name of the all-day event)
		result += " ("+thisCal+")"; // this appends the calendar name in parenthesis to the event
		result += "\n"; // this adds the obligatory newline, linefeed character
      }
    }
  }
}

// At this point one will have a text string of events sorted by calendar names and within each calendar sorted by time
// but what I want is a day's events sorted by time across all calendars


// To sort the 'result' text, first we split the text string by the linefeed characeters into an array
let event_lines = result.split("\n");

// Next we sort the 'event_lines' -- this is a very simple sort, which is why the time was the leading character of each event
event_lines.sort();

// Now it's time to reassemble the 'result' string from the sorted event_lines, while removing the first 10 characters that had the sortable time in front
result = "";
for (eventLine of event_lines) {
  let cleanEventLine = eventLine.substring(10); // take off the first 10 characters containing 'hhmmhhmm• ' leaving the fancy formatted events
  result += cleanEventLine+"\n\n";
}

// And then hand it over to what 'tf2' had originally done to make a new Draft document!

let d = new Draft();
d.content = result;
d.addTag("agenda");
d.update();

// function to ensure minutes 
// are two digits; from
// https://electrictoolbox.com/pad-number-two-digits-javascript/
function pad2(number) {
  return (number < 10 ? '0' : '') + number
}

That’s my first script. Comments/edits welcome!

More than perfect Thanks for that

I have added the remiders
Not yet perfect but useful for me

// script to get all events for
// all calendars for the today, only 
// day, and add to a new draft
// by herbslick, 8 Feb 2024
// inspired by tf2

// date offset determined the
// range of days for which
// events are shown. Number is
// added to today's date; 0 means
// today, 1 means tomorrow, etc
let st = 0;
let en = 1;

// set today's date
let aDate = new Date();
let aaDate = new Date();
// set start date
let sDate = aDate.addDays(st);
let eDate = aaDate.addDays(en)

// include Reminders?
// no = false (no quotes)
// "inc" = all incomplete tasks
// "due" = incomplete tasks due in
//		range set above
// "comp" = completed tasks
// "all" = all tasks
var remind = "due";

// prepend calendar name
// or reminder list 
// to each line: set true
let preCal = true;
let preRem = false;

// get all calendars
let cals = Calendar.getAllCalendars();


// set a header string to hold
// the result 
//aDate = new Date();

// Initialize the string with a header 'Today's Agenda for YYYY-MM-DD'
aStr = aDate.getFullYear()+'-' + (pad2(aDate.getMonth()+1)) + '-'+ pad2(aDate.getDate());
result = "# Today's ## Agenda for ";
result += aStr + "\n\n";

// cycle thru calendars
for (let cal of cals) {
  if (cal) {
	// Get the name of the Calendar, 'thisCal'
    let thisCal = cal.title;
    // set today & tomorrow
    let today = new Date().setHours(0,0,0,0);
    let tomorrow = new Date(today);
    tomorrow.setDate(tomorrow.getDate() + 1);
    // get all events
    let events = cal.events(today, tomorrow);
    // cycle thru each event
    for (let event of events) {
      // get start date
      let start = event.startDate;
      // get hour, minutes
      let st_hour = pad2(start.getHours());
      let st_min = pad2(start.getMinutes());
      // get end date
      let end = event.endDate;
      // get hour, minutes
      let en_hour = pad2(end.getHours());
      let en_min = pad2(end.getMinutes());
      // add to result
      // Check to see if its an "all-day" event, where start time is "0". If so, process it without start-end times
      if (st_hour != "00") {
        startTimeFmt = start.toString("h:mm tt");
        endTimeFmt = end.toString("h:mm tt");
        result += st_hour+st_min+en_hour+en_min+"• "; // this sets up a sortable lead for each event and ends it with a bullet-space
		result += startTimeFmt+"-"+endTimeFmt+", "; // this is the pretty-formatted start to end time
        result += event.title; // this appends the event title (or the name of the event)
		result += " ("+thisCal+")"; // this appends the calendar name in parenthesis to the event
        result += "\n"; // this adds the obligatory newline, linefeed character
      } else {
		// handling the "all-day" events here...
        result += st_hour+st_min+en_hour+en_min+"• ";  // this sets up a sortable lead for each event and ends it with a bullet-space. It will be '0000-2359• '
		result += event.title; // since this is an all-day event, there is no start or end time, so just put in the event title (the name of the all-day event)
		result += " ("+thisCal+")"; // this appends the calendar name in parenthesis to the event
		result += "\n"; // this adds the obligatory newline, linefeed character
      }
    }
  }
}

// At this point one will have a text string of events sorted by calendar names and within each calendar sorted by time
// but what I want is a day's events sorted by time across all calendars


// To sort the 'result' text, first we split the text string by the linefeed characeters into an array
let event_lines = result.split("\n");

// Next we sort the 'event_lines' -- this is a very simple sort, which is why the time was the leading character of each event
event_lines.sort();

// Now it's time to reassemble the 'result' string from the sorted event_lines, while removing the first 10 characters that had the sortable time in front
result = "";
for (eventLine of event_lines) {
  let cleanEventLine = eventLine.substring(10); // take off the first 10 characters containing 'hhmmhhmm• ' leaving the fancy formatted events
  result += cleanEventLine+"\n\n";
}

// retrieve reminders if remind
// is set to true at top
if (remind) {
  let rLists = ReminderList.getAllReminderLists();
  let remResult = "";
  for (let rList in rLists){
    if (remind == "all"){
      var rems = rLists[rList].tasks;
    }
    else if (remind == "comp") {
      var rems = rLists[rList].completeTasks;
    }
    else { // incomplete + due
      var rems = rLists[rList].incompleteTasks;
    }
    for (let rem in rems) {
      let val = rems[rem].isCompleted
      if (val == true) {
      	var symb = "- [x] ";
      }
      else {
        var symb = "- [ ] ";
      }
      let text = "";
      if (preRem = true) {
        text += rems[rem].title+" ("+rLists[rList].title+") "; 
      }
	  
      if (preRem = false) {
        text += rems[rem].title; 
	}
      
      let dueD = rems[rem].dueDate;
      if (remind == "due") {
      	if (dueD) {
          if (dueD.valueOf() >= sDate.valueOf()){
            let remDstr = dueD.getFullYear() + "-" + (dueD.getMonth()+1) + "-" + dueD.getDate();
            remResult += symb + text + " (Due: " + remDstr + ")\n";
          }          
        }
      }
      else { // all incomplete tasks
        remResult += symb + text + "\n";
      }
    } // end rem in rems
  } // end rList in rLists
  if (remResult != "") {
    result += "\n## Reminders\n\n" + remResult;
  }
} // end if remind

// And then hand it over to what 'tf2' had originally done to make a new Draft document!

let d = new Draft();
d.content = result;
d.addTag("agenda");
d.update();

// function to ensure minutes 
// are two digits; from
// https://electrictoolbox.com/pad-number-two-digits-javascript/
function pad2(number) {
  return (number < 10 ? '0' : '') + number
}

Thanks again !!

2 Likes

I will check that out! Many thanks!