Outlook stores every all-day event in UTC
Updated
TL;DR
Outlook keeps every all-day event created through Microsoft Graph as midnight to midnight UTC, whatever time zone the request named. Read as UTC, an all-day event on October 28 becomes 8 PM on October 27 to 8 PM on October 28 for a New York mailbox, and shows as busy on the wrong evening. Read all-day events as dates in the mailbox's own time zone, and ask Graph for a day either side when you query by time range.
What we saw
While testing Pyas on real Outlook.com and Microsoft 365 mailboxes in October 2026, we created all-day events through Microsoft Graph with isAllDay: true and a time zone such as America/New_York, then read them back.
Observed on Outlook.com and Microsoft 365, 2026-10-26 and 2026-10-27
Every all-day event created through Graph came back with its start and end at midnight UTC, whatever time zone the request had named.
A free/busy query for a New York mailbox with an all-day event on October 28 reported it busy from 00:00 to 00:00 UTC, which is 8 PM on October 27 to 8 PM on October 28 in New York.
All-day events created in Outlook itself can keep a real time zone, so a calendar can hold both kinds. Code that treats every all-day event's times as instants in a zone gets the UTC ones wrong for anyone west of London, and moves them to the next morning for anyone well east of it.
Why it matters
An all-day event is a date, not a span of hours. Shown as an instant range in UTC, it slides into the evening before in the Americas. Free/busy is worse: a mailbox looks busy from the evening before and free on the evening of the day itself, so a scheduler offers times on a day off and refuses them the night before.
Querying by time range has its own edge. Outlook decides whether an all-day event falls in your window by its UTC midnight, so a window drawn in the mailbox's local time can miss all-day events at either end.
What to do
- Read all-day events as dates. Take the date part of the start and end, and place it in the mailbox's time zone (from
mailboxSettings), not in UTC. An event Outlook kept in a real zone still uses that zone. - Widen time-range queries by a day each side when you list events or read free/busy, then keep only the blocks that overlap the window you meant.
- Compare providers by date, not instant. On Google Calendar an all-day event is already a date (
start.date), so the same all-day date compared as instants across Google and Outlook will not match.
How Pyas handles it
Pyas reads every Outlook all-day event midnight to midnight in the mailbox's own time zone, in event reads and in free/busy, the same way Google's all-day events come back. Free/busy asks Outlook for a day either side of your window and keeps only the blocks that overlap it, so an all-day event is busy on the right day in both providers.