fix(api): honour caller-supplied time bounds in event range queries#381
Merged
Conversation
`normalizeDateRange` was snapping `from` to start-of-day and `to` to end-of-day in server-local time, discarding any non-midnight time component the caller passed. A query like to=2026-05-19T02:59:59Z got widened to "end of Tuesday in server-local time", letting an event at 2026-05-19T14:00:00Z slip through even though the upper bound was hours earlier. Drop the `setHours` calls; pass the instants through. The Web frontend already supplies day-shaped bounds (see use-events.ts), so removing the snap is a no-op for it. The MCP `get_events` path, which passes precise UTC instants, now returns events strictly within the requested window. Adds unit tests covering both the regression and the parsing defaults.
ridafkih
reviewed
May 23, 2026
Comment on lines
+39
to
+45
| * Honour the exact instants supplied by the caller. Previously this snapped | ||
| * `from` to start-of-day and `to` to end-of-day in server-local time, which | ||
| * silently widened queries whose bounds carried a meaningful time component | ||
| * (e.g. MCP callers passing precise UTC instants). The web frontend already | ||
| * supplies day-shaped bounds (see hooks/use-events.ts), so removing the | ||
| * snap is a no-op for it. | ||
| */ |
Owner
There was a problem hiding this comment.
This is just for future reference, but I'd appreciate it if PR-specific context (this used to...) was trimmed in the comments for brevity. I know agents tend to like to overexplain in the comments, but the context loses its meaning once the offending code is omitted.
ridafkih
approved these changes
May 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
normalizeDateRangeinservices/api/src/utils/date-range.tssilently widens any narrow date range to a full server-local day, discarding the time component the caller passed. As a result,GET /api/events?from=…&to=…(and the MCPget_eventstool that wraps it) can return events that fall hours outside the requested window.Why
normalizeDateRangeis the last hop beforegetEventsInRangebuilds its SQLBETWEENclause:The
setHourscalls mutate the input to start-of-day / end-of-day in the server's local timezone, regardless of what the caller wrote. So a query like:becomes the SQL range
[2026-05-18 00:00 (server-local), 2026-05-19 23:59 (server-local)]— depending on the server'sTZ, that can pull in an event at2026-05-19T14:00:00Z, which is hours past the upper bound the caller wrote.The web frontend hides this because
applications/web/src/hooks/use-events.tsalready passes day-shaped bounds:So the snap was a no-op for the web client and effectively a footgun for any other caller. The MCP
get_eventstool, which passes precise UTC instants, is the obvious one — once the MCP is shipped to public clients, callers will pass everything from minute-precise to date-only inputs.Changes
Single file:
services/api/src/utils/date-range.ts.Drops the
setHoursmutations.normalizeDateRangenow just clones the inputs so the caller'sDateobjects don't get mutated downstream:parseDateRangeParamskeeps its current defaults (from = now,to = from + 1 week) since those are useful when callers omit the params entirely.Testing
Added
services/api/tests/utils/date-range.test.tscovering:from=2026-05-18T03:00:00Z,to=2026-05-19T02:59:59Zround-trips unchanged (was widening to end-of-day local before)normalizeDateRangereturns freshDateinstances (callers may mutate)parseDateRangeParamshonours exact ISO instantsparseDateRangeParamsdefaultstoto one week afterfromwhen onlyfromis suppliedVerified the web flow still works against an ICS-only feed: pagination keys stay day-shaped, results match what the calendar UI showed before.
Compatibility
setHours(23,59,59,999).T00:00:00Z/T23:59:59Zexplicitly.Out of scope
services/api/src/routes/api/v1/calendars/[calendarId]/invites.tsandservices/api/src/routes/api/events/index.ts; both forward whateverparseDateRangeParamsreturns and benefit from the same fix.