Skip to content

Find a meeting time across several people's calendars and working hours

Updated

TL;DR

Read everyone's busy times (Google's freeBusy, Graph's getSchedule or calendarView), turn each person's working hours into intervals in their own time zone, subtract the busy times, intersect the results across required attendees, and cut the free time into slots of the meeting's length. Outlook gives you each mailbox's working hours; Google's Calendar API doesn't, so you need a default or the user's own setting. Graph's findMeetingTimes does some of this, but only for Microsoft 365 work accounts.

The steps

  1. Busy times for everyone, in UTC, over the search window. Free/busy across Google and Outlook covers the calls and their limits.
  2. Working hours for each person, as intervals in their own time zone, day by day, so clock changes are handled.
  3. Free time per person: their working-hour intervals minus their busy times, with buffers added around each busy block.
  4. The intersection across required attendees.
  5. Slots: cut the shared free time into blocks of the meeting's length, starting on round times (every 15 or 30 minutes), no earlier than now plus the minimum notice.
  6. Optional attendees and rooms, checked per slot, to rank or filter.

Where working hours come from

Outlook has them: GET /me/mailboxSettings/workingHours returns days, start and end, and the time zone, with MailboxSettings.Read, on work and personal accounts. getSchedule returns working hours with the free/busy too. Some mailboxes report working hours in a custom time zone with its own offset rules, which you'll need to map or fall back from.

Google doesn't expose them. The Calendar API's settings cover time zone, week start and the like, but not working hours; the working hours and location guide covers working location events, "only available on primary calendars, and to some Google Calendar users." For Google users, use hours your users set in your product, or a default such as Monday to Friday, 9:00 to 17:00, in the person's calendar time zone.

Graph's findMeetingTimes

Microsoft Graph has a call that suggests times itself, findMeetingTimes. It "checks the free/busy status in the primary calendars of the organizer and attendees", looks within work hours by default ("Monday to Friday, 8am to 5pm in the time zone set for the mailbox" unless the mailbox says otherwise), and ranks suggestions by a confidence score where free counts 100%, unknown 49% and busy 0%.

Its limits decide whether you can use it:

  • personal Microsoft accounts and app-only access are "Not supported",
  • it "assumes that any attendee who is a person is always required",
  • it only knows Outlook calendars, so a Google user in the meeting is invisible to it,
  • Microsoft notes the algorithm "undergoes fine-tuning from time to time", so results can change.

For meetings that mix Google and Outlook users, or include Outlook.com accounts, you compute the times yourself.

Details that make the result usable

  • Time zones. Build each person's working hours in their zone. A 9:00 to 17:00 day in London and one in New York overlap for three hours.
  • Buffers. Widen busy blocks by a few minutes before and after, so the slot doesn't start the minute another meeting ends.
  • Minimum notice. Don't offer a slot that starts in ten minutes.
  • Tentative events. Decide whether "tentative" is busy. Google's free/busy reports only busy; Outlook distinguishes tentative and out of office.
  • Optional people. Don't let them block a slot; report which optional attendees are free for each one.
  • Rooms. If the meeting needs a room, keep only slots where a room that fits is also free. See booking meeting rooms.
  • Calendars you can't read. Fail clearly for a required attendee, and say which results are partial for optional ones.

How Pyas does it

POST /v2/meeting-times does all of this across Google, Outlook and Pyas calendars in one call:

find-a-time.tsTypeScript
const slots = await pyas.meetingTimes.find({
  accountIds: [averyAccountId, samAccountId],
  from: '2026-11-03T00:00:00Z',
  to: '2026-11-07T00:00:00Z',
  durationMinutes: 30,
  buffer: { before: 10, after: 10 },
  minimumNoticeMinutes: 120,
})
// slots[0].startsAt, slots[0].endsAt, earliest first, in UTC
  • Up to 20 attendees, required or optional, over up to 62 days, with durations from 5 minutes to a day.
  • Working hours from the provider where it has them (Outlook), from your request where you pass them, or Monday to Friday 9:00 to 17:00 otherwise, each in the person's own zone. The response says which source was used for each attendee.
  • Buffers, minimum notice, slot steps of 5 to 60 minutes, and whether tentative counts as busy.
  • Rooms. Add rooms and Pyas picks matching rooms, smallest first, and keeps only slots with a free one.
  • Clear failures. A required attendee whose calendar can't be read fails the request with a hint; an optional one marks the result as partial.

Try it on sample people in several time zones in the sandbox.

How to get free/busy across Google and Outlook calendars in the right time zonesBook meeting rooms in Google Workspace and Microsoft 365 with an APITime zones in calendar APIs: IANA vs Windows names, clock changes and all-day events