Skip to content

How todo apps understand 'tomorrow at 9am'

A short tour of natural-language date parsing, the regex tricks behind it, and why on-device AI is changing what is possible.

8 min read

Type “Email Sam tomorrow at 9am” into a good todo app and one of two things should happen. Either you get the task back as a clean object with title “Email Sam” and a due date set to 9:00 AM the following day, or the app does nothing and you have to fill out a date picker. The difference between those two outcomes is what determines whether you actually use the app.

Natural-language date parsing has been around for decades. It powered Quicksilver. It powered Fantastical. Today it powers basically every productivity app worth shipping. But the way it works under the hood has changed in interesting ways over the last two or three years, and the result is that 2026 is the first year where this technology is genuinely solved for English in most apps and starting to work well in other languages.

This post is a tour of how it works.

The basic shape of the problem

A user types a sentence. The app needs to:

  1. Decide whether the sentence even contains a date.
  2. If it does, find which span of the text refers to the date.
  3. Resolve that span into an absolute date and time.
  4. Remove the date span from the sentence so the remaining text is just the task title.

Each of these steps has its own failure modes. Most early implementations were brittle on step one, generating false positives. “Pick up the 15 reports” should not get a due date of the 15th of the month. “Buy 3 apples” should not be interpreted as some kind of three-something. “Email Sam at IBM” is probably not pinning the time to “at I.B.M.”

The classic strategy is regular expressions. They are surprisingly good at this. A handful of carefully written patterns can recognize over 90% of the date phrases people actually type into todo apps:

  • \bin\s+(a|an|\d+|some|several)\s+(min|minute|minutes|hour|hours|hr|hrs|day|days|week|weeks|month|months)\b
  • \b\d{1,2}(:\d{2})?\s*(am|pm)\b
  • \b(tomorrow|today|tonight|noon|midnight)\b
  • \b(monday|tuesday|wednesday|thursday|friday|saturday|sunday)\b

Combine fifteen or twenty of these patterns and you cover the long tail. Then you stitch the matches together: a day word plus a time word becomes a specific instant. A weekday name with no qualifier means “the next occurrence of that weekday.” A bare time word with no day means today.

Where regex falls down

Regex hits a wall at three kinds of input:

  1. Vague phrases. “Sometime next week” is a date but not a specific one.
  2. Ambiguity. “Friday afternoon” could be 2 PM or 4 PM depending on convention.
  3. Misspellings and slang. “tmrw at 9p” is parseable by a human but not by a strict regex without a lot of variants.

For a long time, todo apps just shrugged at these. If you typed “tmrw at 9p” you got nothing. If you typed “first thing tomorrow” you got nothing. The user learned to type the formal version.

The interesting development of the last year is that on-device language models can now handle these cases without sending anything over the network. Apple’s Foundation Models framework, available since macOS 26, ships a model that runs locally and is fast enough to use as a fallback when regex misses. Google has a similar offering on Android. The resulting pattern looks like this:

Two-stage parsing pipeline Diagram showing a sentence flowing through a fast regex stage. If regex matches, the result emits immediately. If not, the sentence is forwarded to an on-device language model fallback that emits a structured result. User input "in 30 minutes" Regex stage ~17 patterns, <1ms match Structured result due: 2:30 PM no match On-device model Foundation Models, ~50ms
The two-stage pipeline used by TodoBar and similar apps. The fast path handles the common case in under a millisecond. The model handles the long tail.

The fast path covers the 90% case. The model handles the rest. Neither stage sends the user’s text over the network. Privacy and speed are both preserved.

Why “in 2 hours” is harder than it looks

The phrase “in 2 hours” looks easy. It is not.

The user could mean any of the following:

  • 2 hours from right now, to the minute.
  • 2 hours from right now, rounded to the nearest 15 minutes for cleanliness.
  • 2 hours from right now, but if that lands during the user’s marked sleeping hours, the next morning.
  • 2 hours from the start of the next hour (“in 2 hours” said at 1:55 PM might mean 4:00 PM, not 3:55 PM).

Most apps make a choice and stick with it. The most common is “2 hours from right now, to the minute,” which trades elegance for predictability. A user who typed “in 2 hours” at 1:23 PM gets a notification at 3:23 PM. They learn to type whole numbers when they want clean times.

Why “tomorrow” defaults to 9 AM

When you type “tomorrow” with no time, an app has to pick a time. The convention has converged on 9:00 AM in nearly every app on the market. Why?

Because the cost of guessing wrong is asymmetric. If the app picks 9 AM and you wanted 11 AM, you can adjust quickly when you notice. If the app picks midnight or some random other hour, you get a notification while you are asleep or you miss it entirely. 9 AM is the safe default for almost every kind of work task.

A few apps default to 8 AM. A few use the time of day at which you typed the task (“you said it at 2 PM, so probably you mean 2 PM tomorrow”). The latter is clever but unreliable, because most people who type a task at 2 PM are doing so during the work day for tasks they want to do during a future work day, not at exactly 2 PM the next day.

What this looks like inside TodoBar

TodoBar uses the two-stage pattern described above. Type any task with a temporal phrase and the regex layer fires first, in under a millisecond. If it finds a date, the task appears in your list with the right due time attached. If the regex layer cannot resolve the phrase, the on-device Foundation Models classifier takes a shot, with a typical latency of around 50 milliseconds. Either way, no text leaves your Mac.

The patterns the regex layer recognizes are documented in the support page. As a quick reference, all of these work:

  • “in 30 minutes”
  • “in 2 hours”
  • “in a couple hours”
  • “tomorrow”
  • “tomorrow at 9am”
  • “tonight”
  • “tonight at 8”
  • “next Friday”
  • “Friday at 5pm”
  • “May 12”
  • “May 12 at 2pm”
  • “the 15th”
  • “in 3 days”
  • “1 week from now”
  • “first thing tomorrow”

If you type something the parser cannot handle, the task still gets added with no due date, and you can attach one manually from the row’s bell button. Failure mode of the parser is “no date attached,” not “task lost.”

Where this all goes

The next interesting frontier is multilingual parsing. Spanish phrases like “mañana a las 9” should work as well as the English equivalent, and increasingly do in apps that take localization seriously. Apple’s on-device model handles Spanish well in our internal testing. The same approach will work for French, German, Japanese, and probably most major languages within the next year.

The pieces are all here. What used to be an expensive cloud round-trip is now a local function call that takes 50 milliseconds. The fact that an indie todo app can ship that, for free, with no per-user inference bill, is the actual story.

TodoBar is a friendly menu bar todo list for macOS. Plain-English due dates, global hotkey, iCloud sync. Pay once, yours forever.

Get TodoBar on the App Store