'Date filters not working as expected when querying Microsoft Graph API

I'm trying to filter the results I'm getting from the Users/Calendar/List Events endpoint of the MS Graph API. While trying to figure out what's going on I've been using the Graph Explorer. If I make a request with a filter as follows:

https://graph.microsoft.com/v1.0/users/{redactedUPN}/events?$filter=start/dateTime ge '2022-05-03T00:00:00.0000000Z'

I get back a bunch of results, one of which is this all day event (I've removed most of the response's body for clarity):

{            
    "subject": "Annual leave",
    "start": {
        "dateTime": "2022-05-04T00:00:00.0000000",
        "timeZone": "UTC"
    },
    "end": {
        "dateTime": "2022-05-05T00:00:00.0000000",
        "timeZone": "UTC"
    }
},

As you can see, this event starts at midnight (UTC) on 4th May 2022. If I now modify my request to filter for events a day later, thus:

https://graph.microsoft.com/v1.0/users/{redactedUPN}/events?$filter=start/dateTime ge '2022-05-04T00:00:00.0000000Z'

This same event is not included in the results. I would expect it to be included because I'm using the ge predicate, meaning "greater than or equal to" according to the docs, and my missing event's start datetime is identical to the date I've passed in my filter. Just to prove the point, if I change the predicate and use eq (equals) instead:

https://graph.microsoft.com/v1.0/users/{redactedUPN}/events?$filter=start/dateTime eq '2022-05-04T00:00:00.0000000Z'

I get no events at all in my response. Even if I copy and paste the value from the response to the first request into the filter for the subsequent requests, the event doesn't get returned. What gives? Is this a time zone thing? I hoped that by including the Z in my time filter I was being specific enough (note that it doesn't work without the Z either).

As a side note, the documentation for the Graph API isn't great. The only way I learned to use the format start/dateTime to reference the nested property was by coming across it in an issue posted by a user on GitHub. I can't see it anywhere in the docs.

Update

I've just tried another request, using the eq predicate and setting the time filter to an hour earlier than I'm expecting (my time zone is UTC+1), like this:

https://graph.microsoft.com/v1.0/users/{redactedUPN}/events?$filter=start/dateTime eq '2022-05-03T23:00:00.0000000Z'

That query does return the event in question, although the response is the same as the one posted above, with the time showing as midnight on the 4th May and the time zone as UTC. So it definitely looks like a time zone issue, but surely if my filter matches the response's value exactly that should work?!



Solution 1:[1]

TL;DR - the value of the start/dateTime property for all-day events will always be 00:00:00, regardless of time zone, but when filtering all-day events by date/time, you must either include a Prefer header with the appropriate time zone or specify the date/time in UTC equivalent to midnight in your time zone.

Ok, I think I've got to the bottom of this, although I'd argue that this represents a bug in the Graph API and I'll be raising an issue accordingly.

It seems that this behaviour is a peculiarity of all-day events. For regular, single-instance events which are not all-day events, the value shown in the response from the Graph API for the start.dateTime property is, as the docs explain, in the format you specify in your request's Prefer header, defaulting to UTC if no Prefer header is included.

However, for all-day events, the value in the start.dateTime property is always midnight on the day of the event, regardless of what time zone is specified in the Prefer header. Somewhat bizarrely, if I omit the Prefer header, my all-day event's start property looks like this:

"start": {
    "dateTime": "2022-05-04T00:00:00.0000000",
    "timeZone": "UTC"
}

If I specify my time zone (Prefer: outlook.timezone="Europe/London"), the response is this:

"start": {
    "dateTime": "2022-05-04T00:00:00.0000000",
    "timeZone": "Europe/London"
}

Same time, different time zone, which is, at best, confusing and, I'd argue, wrong.

To make matters worse, as my attempts to filter these responses has shown, the datetime shown in the response's start.dateTime property isn't necessarily what's actually in the database. If I use the filter $filter=start/dateTime eq 2022-05-04 00:00:00.0000000' with no Prefer header, I get no results.

Change the filter to an hour earlier, though, and my all-day event is included in the response. That's fair enough, because an all-day event which runs from 00:00:00 - 23:59:59 in my time zone (UTC+1) is running from 23:00:00 - 22:59:59 UTC. The problem is that the response still shows the start.dateTime in the response as "2022-05-04T00:00:00.0000000".

Effectively, I'm saying "get me all events which start at exactly 23:00:00 UTC" and the response is saying, "ok, here's a result that matches your filter - it starts at 00:00:00 UTC". That makes no sense!

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Philip Stratford