← Back to all articles
•6 min read

Debugging RRule Timezone Drifts in Healthcare Scheduling

How floating UTC timestamps caused recurring doctor interview slots to drift across day boundaries, and how we normalized recurrence rules with strict UTC evaluation in production.

RRuleRecurrenceGoogle CalendarTimezonesTypeScript

The Problem: Silent Recurrence Slippage

In healthcare recruitment platforms like MRecruit, interview rounds for clinical specialists often repeat on a weekly cadence across multiple hospital departments. To power recurring calendar schedules without storing hundreds of redundant database rows, we adopted the RFC-5545 iCalendar specification via the rrule.js library.

Under testing, recurring appointments scheduled at 09:30 AM local Indian Standard Time (IST, UTC+05:30) began appearing on Google Calendar at 04:00 AM UTC. However, when recruiters in other timezones or DST-shifting regions inspected the recurrence list, meetings near midnight began wrapping onto the previous day.

The root problem stemmed from JavaScript’s native Date object parsing strings without timezone specifiers as local browser time, whereas rrule.js internally performs recurrence calculations using UTC methods unless floating mode is explicitly configured.

Naïve date initialization vs normalized UTC recurrence optionstypescript
// ❌ Naïve initialization: browser local timezone pollution
const naiveRule = new RRule({
  freq: RRule.WEEKLY,
  interval: 1,
  byweekday: [RRule.MO, RRule.WE],
  dtstart: new Date(slot.dateString), // Interpreted as local device time!
});

// ✅ Production normalization: strict UTC boundaries
const normalizedRule = new RRule({
  freq: RRule.WEEKLY,
  interval: 1,
  byweekday: [RRule.MO, RRule.WE],
  dtstart: new Date(Date.UTC(year, month, day, hours, minutes, 0)),
  tzid: 'UTC',
});

The Fix: Normalized UTC Evaluation with Local Presentation

To resolve this permanently across all microservices and frontend clients, we established a strict boundary: all recurrence rules are generated, serialized, and evaluated in pure UTC.

When querying future interview slots via rule.between(startUtc, endUtc), the calculation occurs in UTC coordinate space. The resulting timestamps are only transformed into local user time at the final presentation tier using Intl.DateTimeFormat or dayjs.tz.

Furthermore, we added comprehensive unit test suites asserting that occurrences generated across daylight saving transitions and boundary hours (e.g. 23:30 UTC) never shift calendar dates unexpectedly.

Key Takeaways for Scheduling Systems

1. Never store localized RFC-5545 strings in the database. Always persist pure UTC RRULE definitions.

2. Separate calculation from display: evaluate occurrences in UTC, then project into the user’s designated timezone at render time.

3. Provide explicit UI indicators letting recruiters toggle between viewing interview times in hospital local time and recruiter device time.

PK
Pushyamithra Kasthuri
Front-End Web Developer · Hyderabad, India
Portfolio navigation

Find a flow or action

Search all twelve flows, sections, and display settings.