RORK LABJP
ACQUISITION — Rork makes its first acquisition, buying Paperline, a macOS app that generates native Swift apps with AIFUNDING — The $15M seed led by Left Lane Capital backs Rork's push to redefine how mobile apps are built and monetizedGROWTH — Rork Max reportedly hit $1.5M ARR within three days of launch and doubled annual revenue in two weeksENGINE — Rork Max runs on Claude Code and Claude Opus 4.6, the first web Swift builder aiming to replace XcodeSPLIT — Standard Rork uses React Native (Expo); Rork Max generates native Swift across the whole Apple ecosystemPRICING — Start free; paid plans begin at $25/month, with Rork Max at $200/monthACQUISITION — Rork makes its first acquisition, buying Paperline, a macOS app that generates native Swift apps with AIFUNDING — The $15M seed led by Left Lane Capital backs Rork's push to redefine how mobile apps are built and monetizedGROWTH — Rork Max reportedly hit $1.5M ARR within three days of launch and doubled annual revenue in two weeksENGINE — Rork Max runs on Claude Code and Claude Opus 4.6, the first web Swift builder aiming to replace XcodeSPLIT — Standard Rork uses React Native (Expo); Rork Max generates native Swift across the whole Apple ecosystemPRICING — Start free; paid plans begin at $25/month, with Rork Max at $200/month
Articles/Dev Tools
Dev Tools/2026-06-25Advanced

Why Your 9 AM Reminder Stops Arriving Abroad — Making Expo Local Notifications Survive Time Zones and DST

Daily reminders built with Rork (Expo) can drift to the wrong local time when users travel or DST flips. Here is the timeInterval trap, and a design that reschedules against local wall-clock time, with working code.

Rork452expo-notifications6Time ZonesLocal Notifications2React Native182DST

Premium Article

A user of one of my wallpaper apps once wrote in to say that the morning "image of the day" was arriving in the evening after they moved to Europe. It fired reliably at 9 AM in Japan, so my first guess was something server-side. The real cause sat much earlier in the stack: how the local notification trigger was built.

Running about six healing- and manifestation-style apps on my own, the bugs I dread most are the ones that never reproduce on my own device. A drifting daily reminder is the textbook example. This article separates out exactly why Expo local notifications drift across time zones and DST, and lays out a rescheduling design that keeps them anchored to local time, with code you can drop in.

Why "local 9 AM" stops arriving

The drift comes down to one distinction: does the trigger point at an absolute instant, or at a time on the device's local calendar? expo-notifications triggers fall into three families.

Trigger typeWhat it points atAcross travel / DST
timeInterval (n seconds)An absolute instant (fixed seconds from scheduling time)Drifts
date (a fixed Date)An absolute instant (one point in UTC)Drifts
daily / calendar (hour, minute)A time on the device's local calendarTracks correctly

The pattern that bites most is computing "seconds until the next 9 AM" and passing it to a timeInterval trigger. It looks right, but at scheduling time it is frozen into an absolute "fire N seconds from now." When the user moves nine hours east, that promised instant does not move. So it rings in the evening locally.

Passing a fixed Date drifts for the same reason. new Date(2026, 5, 26, 9, 0) is baked into one UTC instant using the device's current offset, so after a move it no longer lines up with the local clock.

I had shipped both. Because I wanted to vary the message per day, I had deliberately stacked one date at a time instead of using a calendar trigger. In exchange for per-day copy, I gave up time-zone resilience.

The basics: if you only need local time, use DAILY

If the same copy at the same local time every day is enough, the answer is simple. With SchedulableTriggerInputTypes.DAILY, the OS re-evaluates against the local calendar, so both travel and DST switches are absorbed for you.

import * as Notifications from 'expo-notifications';
 
export async function scheduleDailyReminder(hour: number, minute: number) {
  // Clear the existing daily slot first, then re-add (avoid duplicates)
  await Notifications.cancelAllScheduledNotificationsAsync();
 
  await Notifications.scheduleNotificationAsync({
    content: {
      title: 'Your image of the day is here',
      body: 'A quiet change of mood for your home screen.',
    },
    trigger: {
      type: Notifications.SchedulableTriggerInputTypes.DAILY,
      hour,   // interpreted in the device's local time
      minute,
    },
  });
}

On iOS this maps to a UNCalendarNotificationTrigger; on Android, to a repeating alarm. Both mean "local hour:minute," so a reminder set in Tokyo still rings at the local 9 AM nine hours away. Swapping timeInterval for DAILY is, in fact, the most common one-line fix.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
Why timeInterval/Date triggers drift across time zones and DST, and how the DAILY calendar trigger tracks local time instead
A design that stores the intended local hour:minute and rebuilds the schedule on foreground when a time-zone change is detected
Safe next-fire computation that handles the spring-forward gap (a 2:30 that does not exist) while keeping a rolling window under the 64-notification cap
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

Dev Tools2026-06-22
Your Daily Reminder Stops Firing After a Couple of Weeks — iOS's Invisible 64-Notification Cap
When a daily reminder built with Rork (Expo) goes silent after a while, the cause is usually iOS's 64 pending-notification limit. Design a repeating calendar trigger for fixed messages and a rolling reschedule for daily-changing content, with working code that survives DST and multiple reminders.
Dev Tools2026-04-05
Rork × expo-notifications: A Complete Guide to Local Notifications — Scheduling, Repeating, and Reminders
A complete guide to implementing local notifications in Rork using expo-notifications. Learn how to schedule one-time and repeating reminders with clear code examples.
Dev Tools2026-06-25
When an Image-Heavy Rork App Quietly Bloats Its Cache and Dies on Memory — Field Notes on Measuring and Capping
In a Rork app where images are the product, expo-image's disk cache and resident memory creep up over a session and surface as OOM crashes. Here's how I measured the bloat, where I set caps, and what I trimmed on the delivery side — with working code, in the order that actually helped.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →