Task recurrence

Here’s a dumb question for you: if I’m building a task manager, how do I make a task recur every second week on Wednesday and Thursday?

Seems simple, right? If you were marking an event on your calendar and it recurred every second week on Wednesday and Thursday you’d be golden. If you were administering this by hand and had to pencil in the due date for the next task, you’d be fine.

So I thought when I started implementing recurring tasks in my tiny personal [vue][]-based task manager. Turns out it was harder than I thought.

Programming recurring tasks is hard

Why? After all, we have a whole subsection of an RFC defining this kind of thing for events, and even a calculator where you can input your settings and create the relevant string. It has a bunch of flexibility, allowing your events to recur daily, weekly, monthly (either on the 1st of the month, or the 2nd Thursday of the month), or yearly. And even with all this flexibility, the resulting recurrence rule string (or RRULE string if you’re a date nerd) is pretty compact and easy-to-parse.

So what makes tasks harder? After a bunch of beating my head against the wall, I’m here to tell you that it’s the fact that tasks are of unknown duration.

What do I mean by this? Whenever a task appears on your list, you never know when it’ll be completed. Events on your calendar, well, if they overrun their alloted time it’s the exception rather than the rule. But tasks can stick around for days, weeks, months, or years. And if your recurrence logic doesn’t take this into account, you’ll find yourself with duplicates of tasks piling up before long.

The trolley problem but for executive function

Let’s consider a basic example: I set a task in my task manager to water my plants. I want this task to recur every morning, because I have super-thirsty plants.

Now on Wednesday I’m out of town for something, and I don’t water my plants. No worries! On Thursday morning, I open my task manager and see the task looking at me. All good! I water my plants, and mark the task as done.

Now let’s consider what the task manager sees: I’ve just marked a task (with a start date of, let’s say, 0:00 on Wednesday morning, and a recurrence rule of every 1 day) complete. When is the next task?

  • You could argue that by literal definition, my next task should start at 0:00 on Thursday, despite the fact that this is in the past. After all, it recurs every day. When you just watered those plants, you gave them Wednesday’s water - now give them today’s water too!
  • Or you could reason that you never want to create a task whose start date is in the past, in which case you should set your next task to start at 0:00 on a Friday. Which is fine until that one edge case where you actually want the tasks to queue up like cars at a toll gate.

Again, when you’re making events this doesn’t matter because when events are over, they’re over, and you’d never actually want a calendar event which overlaps with its next occurence, and even if you did, you could work out the recurrence rules to take this into account.

OK, so let’s say you’ve solved that conundrum. Here’s our next one:

After killing all my plants through overwatering, I’ve determined that I only need to water them every two days.

On Monday morning, a task pops up in my task manager telling me I need to water my plants. I do so, and mark it done. A new task gets created with a start date of 0:00 on Wednesday (two days after the current one). All good so far!

Now I go away on Wednesday and don’t get to water my plants. That’s fine! On Thursday morning I water the plants and mark the task done.

When is the next task? Again we’re faced with a dilemma:

  • The last task started on Wednesday. Two days from Wednesday is Friday. Done!
  • We only just watered the plants! We know they drown in too much water. Two days from now is Saturday. Done!

The problem in both these cases is that we need to define not just the frequency with which our task recurs, but also whether we measure this from the task’s assigned date or from its completion. This additional specification makes our task recurrence pattern more transparent and useful.

OK, we’ve solved that problem, now for a new set of problems.

Let’s say I’ve just acquired a particularly fussy plant. It only needs watering once every five days, but it really needs to be watered within a 24-hour window. No skips and no delays! So it’s time for a new task - this time with a start date and a due date:

  • Task: Water the fussy plant
  • Start: 0:00 Monday
  • Due: 0:00 Tuesday
  • Recurs: Every 5 days, based on completed date

So we water said plant on Monday, all good. The task recurs, and our new start and due date are Saturday and Sunday respectively. Then on Saturday we go lie in the sun all day and forget to water it. Rushing in on Sunday morning, we give the plant a water, hoping it hasn’t noticed our tardiness.

When does our task repeat now? The task was completed on Sunday, and five days from now is next Friday. Again, two options:

  • If we base our recurrence on the start date (ie the task starts five days after completion), then the next task starts on Friday and is due on Saturday.
  • If we base our recurrence on the due date (ie the task is due five days after completion), then the task starts on Thursday and is due on Friday.