Check room availability on calendar

Hi! I’m new to automation on iOS, but not to scripting in general. I was wondering if anyone had an example that could check the availability of reservable room (or a person, for that matter) on an Exchange calendar. I’d like to write something that checks a list of rooms and books the first available room for 30 minutes. Thanks for any help!

I’d look at using Microsoft Flow rather than Scriptable for this given you want to work with Exchange. I don’t know if it can do everything you would need, but in terms of triggering from iOS (it’s a cloud hosted engine a bit like IFTTT or Zapier, but focused primarily on Microsoft platform integrations), I have to think it’s your best shot.

The flow can be triggered by a press in the flow app - I’ve been playing with Flow recently and it’s awesome for that.

1 Like

Thanks for the suggestions! I checked out Flow and MS Graph, and there’s a beta API to find meeting times. Unfortunately, it’s not going to work for me because we haven’t been given appropriate permissions. It’s been interesting to learn about though.

In case this is helpful for anyone, here’s what I’m doing to solve this problem.

At my job you book a meeting room by creating a calendar event and inviting the meeting room to the event. If the meeting room is available, it’ll accept the invitation otherwise it’ll reject it. Also each meeting room has a calendar that the events are put into. This is setup in Google Calendar somehow, I’m not sure exactly how.

The script below checks if a meeting room is available now for between half an hour and an hour. If any meeting room is available, it presents a list with the available meeting rooms, prompting the user to select one. The selected meeting room is booked for whatever time it is available, up to one hour and at least half an hour.

If the script is run with Siri, it automatically chooses a meeting room and books it.

The script below assumes that the name of the calendar that belongs to the meeting room starts with “Meeting room:” but that can easily be changed.

let cals = await Calendar.forEvents()
cals = cals.filter(isMeetingRoomCal)
let nowSchedule = await allTimeSlotsAvailableNow(cals, 1800, 3600)
await run(nowSchedule)

async function run(schedule) {
  if (schedule.length == 0) {
    if (config.runsWithSiri) {
      Speech.speak("All meeting rooms are booked.")
    } else {
      presentNoTimeSlots()
    }
    return
  }
  let idx
  if (config.runsWithSiri) {
    idx = 0
  } else {
    let alert = new Alert()
    alert.title = "Choose meeting room"
    alert.message = "Booking meeting room for now"
    for (e of schedule) {
      let cal = e.calendar
      let ts = e.timeSlot
      let strDur = strDuration(ts.duration)
      let title = ""
        + prettyRoomName(cal.title)
        +" ("+strDur+")"
      alert.addAction(title)
    }
    alert.addCancelAction("Cancel")
    idx = await alert.presentSheet()
    if (idx == -1) {
      return
    }
  }
  let e = schedule[idx]
  let cal = e.calendar
  let ts = e.timeSlot
  addEvent(cal, ts.startDate, ts.endDate)
  if (config.runsWithSiri) {
    let str = "I have booked "
      + prettyRoomName(cal.title)
      + " until "
      + strTime(ts.endDate)
    let table = new UITable()
    let row = new UITableRow()
    row.addText(
      prettyRoomName(cal.title),
      "Booked until " + strTime(ts.endDate))
    table.addRow(row)
    QuickLook.present(table)
    Speech.speak(str)
  }
}

function presentNoTimeSlots() {
  let alert = new Alert()
  alert.title = "No meeting room"
  alert.message = "There is no meeting room available."
  alert.presentAlert()
}

function addEvent(cal, start, end) {
  let event = new CalendarEvent()
  event.calendar = cal
  event.title = "Støvring"
  event.startDate = start
  event.endDate = end
  event.save()
}

async function allTimeSlotsAvailableNow(cals, minDuration, maxDuration) {
  let result = []
  for (cal of cals) {
    let schedule = await CalendarEvent.today([cal])
    let timeSlots = availableTimeSlots(schedule)
    let capped = cappedTimeSlots(timeSlots)
    let nowTimeSlot = timeSlotAvailableNow(capped, minDuration, maxDuration)
    if (nowTimeSlot != null) {
      result.push({
        calendar: cal,
        timeSlot: nowTimeSlot
      })
    }
  }
  return result
}

function timeSlotAvailableNow(timeSlots, minDuration, maxDuration) {
  let now = new Date()
  now.setMilliseconds(0)
  for (timeSlot of timeSlots) {
    let tsStartDate = timeSlot.startDate
    let tsEndDate = timeSlot.endDate
    if (tsStartDate.getTime() <= now.getTime() && tsEndDate.getTime() >= now.getTime()) {
      let duration = (tsEndDate.getTime() - tsStartDate.getTime()) / 1000
      if (duration > maxDuration) {
        let endDate = new Date(tsStartDate.getTime() + maxDuration * 1000)
        return {
          startDate: tsStartDate,
          endDate: endDate,
          duration: maxDuration
        }
      } else if (duration > minDuration) {
        return timeSlot
      }
    }
  }
  return null
}

function cappedTimeSlots(timeSlots) {
  let result = []
  let now = new Date()
  now.setMilliseconds(0)
  for (timeSlot of timeSlots) {
    let tsStartDate = timeSlot.startDate
    let tsEndDate = timeSlot.endDate
    if (tsStartDate.getTime() < now.getTime()) {
      let duration = Math.floor((tsEndDate.getTime() - now.getTime()) / 1000)
      if (duration > 0) {
        result.push({
          startDate: now,
          endDate: tsEndDate,
          duration: duration
        })
      }
    } else {
      result.push(timeSlot)
    }
  }
  return result
}

function availableTimeSlots(schedule) {
  schedule.sort(sortByStartDate)
  let dayStartDate = createDate(00, 00)
  let dayEndDate = createDate(23, 59)
  let timeSlots = []
  if (schedule.length == 0) {
    timeSlots.push({
      startDate: dayStartDate,
      endDate: dayEndDate
    })
  } else {    
    for (let i = 0; i < schedule.length; i++) {
      let entry = schedule[i]
      if (i == 0) {
        let beforeEventTimeSlot = createTimeSlot(dayStartDate, entry.startDate)
        let afterEventTimeSlot = createTimeSlot(entry.endDate, dayEndDate)
        if (beforeEventTimeSlot) {
          timeSlots.push(beforeEventTimeSlot)
        }
        if (afterEventTimeSlot && schedule.length == 1) {
          timeSlots.push(afterEventTimeSlot)
        }
      } else if (i == schedule.length - 1) {
        let timeSlot = createTimeSlot(entry.endDate, dayEndDate)
        if (timeSlot) {
          timeSlots.push(timeSlot)
        }
      } else {
        let nextEntry = schedule[i + 1]
        let timeSlot = createTimeSlot(entry.endDate, nextEntry.startDate)
        if (timeSlot) {
          timeSlots.push(timeSlot)
        }
      }
    }      
  }
  return timeSlots
}

function createTimeSlot(start, end) {
  let duration = Math.floor((end.getTime() - start.getTime()) / 1000)
  if (duration > 0) {
    return {
      startDate: start,
      endDate: end,
      duration: duration
    }
  } else {
    return null
  }
}

function sortByStartDate(a, b) {
  let aTime = a.startDate.getTime()
  let bTime = b.startDate.getTime()
  if (aTime < bTime) {
    return -1
  } else if (aTime > bTime) {
    return 1
  } else {
    return 0
  }
}

function createDate(hh, mm) {
  let date = new Date()
  date.setHours(hh, mm, 00, 00)
  return date
}

function isMeetingRoomCal(cal) {
  let title = cal.title.toLowerCase()
  let needle = "meeting room"
  return title.startsWith(needle)
}

function prettyRoomName(name) {
  let needle = /meeting room: /gi
  return name.replace(needle, "")
}

function strDuration(secs) {
  let mins = Math.floor(secs / 60)
  return mins + " mins"
}

function strTime(date) {
  return ""
    + zeroPrefix(date.getHours())
    + ":"
    + zeroPrefix(date.getMinutes())
}

function zeroPrefix(num) {
  return (num < 10 ? "0" : "") + num
}
2 Likes

Thanks Simon!

This is basically exactly what I was looking for. Exchange treats room reservation the same way, so I was hoping this would work. I modified a few details, including changing ‘meeting room’ to fit the convention used at work, but it’s just not working. I’m guessing that the way Google Calendar and Exchange treat meeting room calendars must be a little different, as it can’t find any available rooms.

Time to learn Javascript so I can try to debug the issue. Thanks again!