Skip to content

EventItem diffDays calculation breaks when device timezone ≠ container timeZone #250

@abdomadi

Description

@abdomadi

Bug Description

When the device's local timezone differs from the timeZone prop on CalendarContainer, all events render on the wrong day (typically diffDays: -1, placing them one column to the left and off-screen).

Affects: v2.5.6 (likely all v2.x)
Environment: React Native 0.83, Expo SDK 55, Hermes, Reanimated v4

Root Cause

In src/components/EventItem.tsx (lines 84-110), the diffDays calculation iterates from startUnix to eventStartUnix and checks each day against visibleDates:

const dayStartUnix = parseDateTime(dayUnix).startOf('day').toMillis();
if (!visibleDates[dayStartUnix]) {
  diffDays--;
}

The problem: visibleDates keys are midnight timestamps computed in one timezone context (from the page layout system), but parseDateTime(dayUnix).startOf('day').toMillis() computes midnight in the device's local timezone. When these differ (e.g., device in America/Toronto UTC-5, container timeZone="Africa/Tripoli"), the midnight timestamps never match — so diffDays-- fires for every day, collapsing all events to column -1.

Additionally, forceUpdateZone() in src/utils/dateUtils.ts hardcodes zone: 'local' instead of using the container's timeZone, which contributes to the mismatch.

Steps to Reproduce

  1. Set device timezone to anything other than UTC (e.g., America/Toronto, UTC-5)
  2. Create a CalendarContainer with timeZone="UTC" (or any timezone ≠ device)
  3. Pass events with start: { dateTime: "2026-03-23T08:00:00+00:00", timeZone: "UTC" }
  4. Observe: all events have diffDays: -1 regardless of their actual date

Expected Behavior

Events should render on the correct day column based on the container's timeZone, independent of the device's local timezone.

Workaround

Replace the midnight-comparison loop in EventItem.tsx with a range-based lookup against visibleDates:

const sortedVisible = Object.keys(visibleDates)
  .map(Number)
  .sort((a, b) => a - b);
let diffDays = -1;
for (let i = 0; i < sortedVisible.length; i++) {
  const visEnd =
    i < sortedVisible.length - 1
      ? sortedVisible[i + 1]
      : sortedVisible[i] + MILLISECONDS_IN_DAY;
  if (eventStartUnix >= sortedVisible[i] && eventStartUnix < visEnd) {
    diffDays = i;
    break;
  }
}

This is timezone-agnostic — it checks which visibleDates interval the event's unix timestamp falls into, rather than comparing midnight timestamps across timezone contexts.

We're using this via pnpm patch in production. Happy to open a PR if helpful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions