Provide alarm choices based on first morning meeting

With the release of Shortcuts 2.1, which enables creating (and enabling) alarms for any input time, I’ve finished my “Bedtime” routine, which starts and ends in Shortcuts, but handles more complex logic in Scriptable.

Basic high-level architecture:

  1. Run Scriptable script in background
    1. From a list of Calendar titles, pull list of all calendar events before 11:01AM. Exclude all-day events & choose the earliest event in that subset.
    2. Generate alarm options:
      • latestAlarm is set to 1 hour before the first event (if there is one)
      • If latestAlarm is more than 7 hours away, give an alarm option for 9 hours from now, 8, or 7, depending on how much time is between now and latestAlarm
      • If no event before 11:01, return alarm options for 9, 8, and 7 hours from now.
    3. Create object with first event details (if there is one) and alarm options
    4. Convert object to string (via JSON.stringify), copy to pasteboard, and return to Shortcuts
  2. Shortcut actions (link to shortcut):
    1. Parse dictionary
    2. Add “Other” option to alarm options (allows manual setting)
    3. Display next event details and present list of alarm options
    4. Other bedtime-related actions (brightness, Do not Disturb, volume)

Picture of alarm prompt & Scriptable code below. Feedback welcome!

const now = new Date();
const tomorrow11 = new Date();
const tomorrowStart = new Date();
const tomorrow11Date =
  now.getHours() >= 5 ? now.getDate() + 1 : now.getDate();
tomorrow11.setDate(tomorrow11Date);
tomorrow11.setHours(11, 1, 0);
tomorrowStart.setDate(tomorrow11Date);
tomorrowStart.setHours(0, 0, 0, 0);

const prependZero = number => number < 10 ? `0${number}` : String(number);
const dateSorter = (a, b) =>
  a.startDate.getTime() - b.startDate.getTime();
const timeStr = (dateObj) => [
  prependZero(dateObj.getHours()),
  ':',
  prependZero(dateObj.getMinutes())
].join('');


const calNames = ['*Work', '*Personal'];
let allEvents = [];
for (const calIndex in calNames) {
  const cal = await Calendar.forEventsByTitle(
    calNames[calIndex]);
  const filteredEvents = await CalendarEvent.between(
    tomorrowStart, tomorrow11, [cal]
  );
  allEvents = allEvents.concat(filteredEvents);
}
allEvents = allEvents
  .filter(event => !event.isAllDay)
  .sort(dateSorter);


const in7Hours = new Date();
const in8Hours = new Date();
const in9Hours = new Date();
in7Hours.setHours(now.getHours() + 7, now.getMinutes(), 0, 0);
in8Hours.setHours(now.getHours() + 8, now.getMinutes(), 0, 0);
in9Hours.setHours(now.getHours() + 9, now.getMinutes(), 0, 0);


let result;
if (allEvents.length) {
  const {
    startDate: firstEventStart,
    title: firstEventTitle
  } = allEvents[0];
  
  const latestAlarm = new Date(firstEventStart);
  latestAlarm.setHours(firstEventStart.getHours() - 1, firstEventStart.getMinutes(), 0, 0);
  console.log(latestAlarm);
  
  const hoursToLatestAlarm = (latestAlarm.getTime() - now.getTime()) / (1000*60*60);
  const hourBasedAlarm =
    (hoursToLatestAlarm >= 9 && {label: `in 9 hours (${timeStr(in9Hours)})`, time: timeStr(in9Hours)})
    || (hoursToLatestAlarm >= 8 && {label: `in 8 hours (${timeStr(in8Hours)})`, time: timeStr(in8Hours)})
    || (hoursToLatestAlarm > 7 && {label: `in 7 hours (${timeStr(in7Hours)})`, time: timeStr(in7Hours)})
    || null;
    
  const alarmOptions = [
    {
      label: `one hour before (${timeStr(latestAlarm)})`,
      time: timeStr(latestAlarm)
    },
    hourBasedAlarm
  ];

  result = {
    firstEvent: {
      title: firstEventTitle,
      time: timeStr(firstEventStart)
    },
    alarmOptions
  };

} else {
  result = {
    firstEvent: null,
    alarmOptions: [
      {label: `in 9 hours (${timeStr(in9Hours)})`, time: timeStr(in9Hours)},
      {label: `in 8 hours (${timeStr(in8Hours)})`, time: timeStr(in8Hours)},
      {label: `in 7 hours (${timeStr(in7Hours)})`, time: timeStr(in7Hours)}
    ]
  }
}

console.log(result);

Pasteboard.copy(JSON.stringify(result));
3 Likes

Cool, thanks for sharing your script!

1 Like