Deprecated: Return type of I::current() should either be compatible with Iterator::current(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/public/kirby/toolkit/lib/i.php on line 62

Deprecated: Return type of I::next() should either be compatible with Iterator::next(): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/public/kirby/toolkit/lib/i.php on line 91

Deprecated: Return type of I::key() should either be compatible with Iterator::key(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/public/kirby/toolkit/lib/i.php on line 71

Deprecated: Return type of I::valid() should either be compatible with Iterator::valid(): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/public/kirby/toolkit/lib/i.php on line 101

Deprecated: Return type of I::rewind() should either be compatible with Iterator::rewind(): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/public/kirby/toolkit/lib/i.php on line 53

Deprecated: Return type of Collection::count() should either be compatible with Countable::count(): int, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/public/kirby/toolkit/lib/collection.php on line 80

Deprecated: parse_str(): Passing null to parameter #1 ($string) of type string is deprecated in /home/public/kirby/toolkit/lib/url.php on line 135
One Tap Less | Parsing natural language in Drafts 4

Parsing natural language in Drafts 4


Warning: Trying to access array offset on value of type null in /home/public/kirby/vendors/parsedownextra.php on line 241

Warning: Trying to access array offset on value of type null in /home/public/kirby/vendors/parsedownextra.php on line 241

Deprecated: preg_match(): Passing null to parameter #2 ($subject) of type string is deprecated in /home/public/kirby/vendors/parsedownextra.php on line 241

Drafts 4 is out, it is everything you expected and all the cool kids are talking about it. The new feature I was most excited about is the introduction of Javascript into actions, which could solve a couple of issues I had over the years.

If you ever used Fantastical or Due, you know that natural language parsing is better than chocolate. Now I want you to count how many task managers you know that implement natural language parsing. Exactly, none1.

I find frustrating that even smothered by options, several of them directed to power users, NLP is still an improbable feature request to most2. So I built one.

Some acknowledgements first, I'm a Javascript rookie and I'm aware of some poor practices in the script. It parses a few string possibilities, definitely not all. This is the kind of "you break, you pay" kind of deal, I won't be able to cover your use case if it breaks the script as it probably would require diving into regular expressions and I need a break from those.

You can check the script on this gist.

How it works?

The first step is looking if you're using a day of the week as date, such as Thursday or Mon, or even a more direct input, like Tomorrow or Today. These values must be converted into a Date object so we can grab the day, month and year. Finding the current or the next day is fine, calculating per weekday makes things slightly more complicated, this is the snippet that does it:

if (weekLower === "today") {
        weekDifference = 0;
    } else if (weekLower === "tomorrow") {
        weekDifference = 86400000;
    } else {
        week = weekdays[weekLower];
        if (todayWeek < week) {
            weekDifference = (week - todayWeek) * 86400000;
        } else {
            weekDifference = ((week - todayWeek) + 6) * 86400000;
        }
    }

All date calculations are made with the Epoch date to avoid issues. Also, the Date object returns a value between 0 and 11 for month, so we have 2 different variables to handle the month values.

If one of those strings is not found, the script chases for month names, like January or Dec. It also looks for the day right next to it, so the format is January 1 or Dec 25. If this fails as well, the script assumes you're using a numeric date and accepts dates in the m/d/y format3. If nothing is found, the date is set to today and we go look for the time.

When looking for the hour, the first stop is checking the written variables: noon, afternoon, evening and morning. You can define these in the times dictionary. Otherwise it looks for numerical values, first it checks if the result is preceded by "in", turning it into a relative time, or not, making it a static time4. If all these fail, the script uses the default values from the times array.

I created the script so you could include notes to your tasks. Only the topmost line of your draft is parsed, everything below becomes a single note, also stored in a tag, but could be [[line|2..]].

The script outputs 5 different template tags and I'll use a simple string as example, Call a cab tomorrow 4pm:

  • [[task]] — Your task without the date parameters. Output: Call a cab;
  • [[notes]] — Every line below the top most. Empty, in this case;
  • [[due]] — The full date, currently in the YYYY-MM-DD HH:mm format. Output: 2014-10-20 16:00;
  • [[dueDate]] — Only the date in the YYYY-MM-DD format. Output: 2014-10-20;
  • [[dueTime]] — Only the time in the HH:mm format. Output: 16:00.

Then you can include the tags into your URL actions, for example, to make better use of GoodTask for your reminders:

Reminder to GoodTask

goodtask://x-callback-url/add?text=[[task]]&due=[[due]]&notes=[[notes]]

Maybe you're hanging there for the upcoming version of Things:

Task to Things

things:add?title=[[task]]&notes=[[notes]]&dueDate=[[dueDate]]

Here's also support for 2Do and Firetask.

Some strings I used for testing the script that you may want to compare with your scenarios:

  • Take the garbage out in 30 minutes;
  • Take the dog to the vet at 7/30 8 pm;
  • Do the twist thursday;
  • Meeting on Oct 18 7pm;
  • Meeting on 2/3 5:45pm;
  • Meeting at noon;
  • Lunch with Matthew at 1:30 Monday;
  • Lunch with John on Friday 3:00 am;
  • Lunch tomorrow at 1pm;
  • Event in 5 days.

Parse All the Things!

After all the hocus-pocus, it is easy to forget this would be impossible without Greg's dedication to Drafts 4, now a Universal app. You should get it right now and play with the new keyboard extensions, version control, new template tags and a beautiful design. I'm certain you'll read a lot more about the things Drafts can do in the upcoming weeks.


  1. Fantastical's integration with Reminders doesn't count. 

  2. Some barely have a decent URL scheme to add tasks. Looking at you Omnifocus. 

  3. You can omit the year at least or tweak the script to accept different formatting. 

  4. The script also does AM/PM conversion, but not time zones, sorry about that.