Skip to content

fix(ios): preserve reminder time zones - #1124

Merged
robertying merged 3 commits into
robertying:mainfrom
lyrenius:main
Oct 2, 2026
Merged

robertying merged 3 commits into
robertying:mainfrom
lyrenius:main

Conversation

@lyrenius

Copy link
Copy Markdown
Contributor

Problem

Assignment reminders synced on an iPhone using Beijing time look correct locally. However, after iCloud synchronization to a Mac using PDT, a deadline of 23:00 Beijing time can still appear as 23:00 PDT. The corresponding local time should be 08:00 PDT.

learnX already passes Asia/Shanghai when syncing reminders. The problem is that expo-calendar subsequently creates startDateComponents and dueDateComponents using Calendar.current without retaining the time zone. The saved reminder therefore contains a floating local time.

Fix

Patch expo-calendar's iOS implementation to build date components in the resolved time zone and explicitly preserve that zone on both the start and due dates.

The resolved time zone is retained during initialization, so it is available even when a new EKReminder does not yet have date components. This applies to both creation and updates. All-day reminders remain date-only.

Verification

  • TypeScript typecheck and ESLint passed.
  • Built and launched the app on an arm64 iOS 27 simulator.
  • Exercised the production sync functions with real EventKit storage, covering creation, updates, deadline changes, and a 30-minute early deadline.
  • Saved reminders with the app running in Asia/Shanghai, then restarted it in America/Los_Angeles and read the same records back. Their UTC deadlines remained unchanged:
    • October: 23:00 Beijing → 08:00 PDT.
    • December: 23:00 Beijing → 07:00 PST.

These are scripted simulator checks using test data. Physical iPhone-to-Mac iCloud synchronization has not been tested.

Screenshots

Both screenshots are from the patched simulator app. The test harness reads the same saved EventKit reminder and displays its deadline in learnX's assignment detail view.

The underlying deadline is 2026-10-01T15:00:00Z in both screenshots:

  • App process time zone Asia/Shanghai: 23:00.
  • App process time zone America/Los_Angeles: 08:00 PDT.
image image

Copilot AI balanced review requested due to automatic review settings September 30, 2026 14:05

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Repository owner deleted a comment from claude Bot Sep 30, 2026
@robertying

Copy link
Copy Markdown
Owner

@claude review the PR. Also check if the fix works for both reminders and calendar events, as well as if the same issue exists on Android.

@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Claude finished @robertying's task in 22s —— View job


Review of #1124

  • Read the patch and the learnX call sites (src/helpers/event.ts)
  • Check reminders vs. calendar events
  • Check Android

node_modules isn't installed in this checkout, so I reviewed the patch and src/helpers/event.ts only. I did not read expo-calendar's upstream Swift or Kotlin sources, so the event and Android points below are inferred from the patch, the call sites and general EventKit/Android behavior. I did not build or run anything.

Summary

The fix looks correct and minimal for iOS reminders. I have no blocking concerns.

Code notes (patches/expo-calendar+58.0.0.patch)

  • createDateComponents now builds a Gregorian calendar in the resolved zone and sets components.timeZone. Calendar(identifier: .gregorian) is used instead of Calendar.current when a zone is given, so a non-Gregorian user calendar (Buddhist, Japanese) can no longer change the year fields. Because timeZone is nil by default, callers that pass no zone keep the old Calendar.current behavior.
  • resolvedTimeZone is captured before the start/due components are set. This handles the case where reminder.timeZone is nil on a fresh EKReminder. Both branches (nullableSet and explicit zone) update it correctly.
  • All-day reminders stay date-only, with no timeZone on the components. That is the right behavior, because a floating date is what all-day reminders should be.
  • If no time zone is supplied and reminder.timeZone is nil, the result is the old floating behavior. That is fine, and learnX always passes TZ.
  • Minor: the patch file doesn't say which upstream version or issue it targets. Consider filing or linking an upstream expo-calendar issue so the patch can eventually be dropped. The existing Android hunks in this file (the IS_PRIMARY and HarmonyOS workaround) are unrelated to this PR.
  • Minor: the PR checks a UTC round trip on the simulator only. Before merging, I'd confirm on a real device pair that the reminder shows 08:00 on a Mac in PDT.

Reminders vs. calendar events

  • Reminders (fixed by this PR): EKReminder stores startDateComponents and dueDateComponents as DateComponents. Without .timeZone these are floating local times, which is the bug. The patch fixes the two call sites in ExpoCalendarReminder.swift.
  • Events (no change needed): EKEvent stores absolute Date values for startDate and endDate, plus an optional timeZone. src/helpers/event.ts:134-138 and :316-322 already pass an absolute date and timeZone: TZ, so no floating-time problem arises. The patch doesn't touch events, and as far as I can tell it doesn't need to.
  • Caveat: if you want to be sure, verify on-device that an event created in Beijing time shows the correct local time on a Mac in PDT. I couldn't confirm this here.

Android

  • Android has no DateComponents equivalent. Events are written to CalendarContract.Events as a UTC-millis DTSTART/DTEND plus an EVENT_TIMEZONE, so the instant is preserved by construction, and TZ is already passed through.
  • Android's CalendarContract has no reminder entity. expo-calendar's Android reminder APIs are unsupported, so the reminder path is iOS-only. Assignment reminders on Android would go through the calendar-event path.
  • So I don't expect the same bug on Android. The exception would be all-day events, which Android stores as UTC midnight; learnX doesn't appear to create those for deadlines.

Suggestions

  1. Add a short comment or PR link in the patch header noting it can be dropped once upstream fixes it.
  2. Optionally verify one event and one reminder on a real iPhone-to-Mac pair, since only simulator checks were run.

Comment thread patches/expo-calendar+58.0.0.patch Outdated
Comment thread patches/expo-calendar+58.0.0.patch Outdated
@robertying

Copy link
Copy Markdown
Owner

感谢同学!

也请同学将自己加入到 About 页面中。可在此 PR 中更新。

@lyrenius

lyrenius commented Oct 1, 2026

Copy link
Copy Markdown
Contributor Author

谢谢!我已在 44310dc 中将我自己加入 About 页的贡献者列表,并添加了 GitHub 主页链接。

@robertying
robertying merged commit 18b9318 into robertying:main Oct 2, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants