Problem Solving with Notion Fomula


So I recently went on the hunt in Notion Marketplace for a habit database. I couldn’t find any that fit my requirements and ended up making one

I made an announcement for it and everything, but that’s not what this blog is about

This blog is about all the grimy details Habits View 1 Habits View 2

that made this database what it became Laptop View

Here’s a sneak peek if you’d like to go through it yourself


The rest are intuitive so let’s start with them!

The Start of the Database

Before that I should layout what my requirements were for a habits database

It had to have the ability to

  • dynamically choose “due days” for each habit
  • skip weeks for each habit
  • keep habits on a timeout

or in other words

It had to have the capabilities of Google’s calendar system Google UI

I needed a column for

  • Name of Habits (text)
  • Completing Habits (button/action)
  • Inputing the days the habits are due (multi select)
  • Inputing the amount of weeks before the next batch of habits are due (number)

That was it, so I got to work

I created the Name column for the name of the habit, the Complete Habit button for completing habits, Days column for choosing which days habits are due and the Repeats Every (Weekly) column for the week duration of each “habit cycle”

NameDaysRepeats Every (Weekly)Complete Habit

Nerd Snipe

Then I needed a way to maintain state, i.e make the database “remember” the days a habit was completed, so I created a new database to store pages with a Created Time column and a connection to the main database

Logs View

NameCreated timeHabits

Then I needed a way to intuitively show the “habit cycle” of habit, so I wrote a little code for Week Status

ifs(
	prop("Repeats Every (Weekly)").empty() || prop("Repeats Every (Weekly)")== 1,
	"Weekly",
	"Every " + prop("Repeats Every (Weekly)")+ " Weeks" 
)

Weekly

Then I needed a way to get the status of each habit (Due Status) - when it’s due or if it’s completed - and at this point, I had already dug myself in too deep to walk away

Deep Dive

So I rolled up my sleeves and got to work

Walkthrough

Before that, let’s have a little itemized list of what we expect from this column

We expect the column to let us know

  • if the habit has been completed
  • if the habit is due, and if so when

really simple… right?

Okay okay, let’s deal with the first guy

To know if the habit has been completed, we need to know when the habit was completed, duhh

If you recall earlier, we created a Log database that maintains state/holds the time of when a habit was completed. Using a column type Notion calls “Rollup”, we can get the last time a habit has been completed

Last Completed


Click the days(blobs) to interact!

SunMonTueWedThuFriSat

Habit is Due

Nice! That was easy, what’s next?

We also need to know when the habit is due

Luckily we have days! Nice

Alright so we need the first due day on or after today, to act as a sort of right boundary

Currently we have Sun, Mon, Tue, Wed, Thu, Fri, Sat as possible due days, and “@today” for the day we’re on. We’ll need to represent them in a way we can compare what’s “before”, what’s “after” and what’s “on” in order to build our boundary

And the best way to do that is with numbers!

Luckily, there’s an easy way to get the “number” of today through Notion, and that’s day(now()).

now() returns a date

@today
July 29, 2025 7:05 PM

and day() returns the day of the date as a number

July 29, 2025 7:05 PM
2

Now you’d think, as a logical human being, surely Sunday is 0, Saturday is 6 and everything else just falls in line, but no, the day() function in Notion returns Sunday as 7, and Monday as 1, essentially making Sunday the “last day” of the week


So we have to fix that, luckily there’s an easy fix. That’s mod

Let n be an integer between 1 and 6, it can be proven that n ≡ n (mod 7)

And we know 7 ≡ 0 (mod 7), putting all that together we can get the actual number of a day using this in notion mod(day(now()), 7). This will come up a lot in the final code


So if we map Sun through Sat as 0 - 6 and use mod(day(now()), 7) to get the number of “today”, we have a consistent numbering system that can be used to compare days

Let’s call this “first day” on or after today when the habit is due next_day

curr_day

SunMonTueWedThuFriSat

Completed Habit!

What happens if there’s no next_day? What happens if the last due date for the week is Friday and we’re on Saturday? I guess we could always just go to the first due date of next week. Let’s call that first_day

curr_day

SunMonTueWedThuFriSat...

Completed Habit!


Get it? Cos it’s the first day the habit is due for the week

Nice! We have a variable - we’ll implement it later - we can use to get the next_day the habit is due

I guess we’ll also need to know the prev due date, cos what if the habit was completed for the previous due date and not the one coming up? Yeah that’ll be bad, we don’t want every habit afterwards to show it’s been completed because we completed it once. Let’s call this variable prev_day

curr_day

SunMonTueWedThuFriSat...

Habit is Due


And once again we’ll need the last_day to give some leeway to prev_day incase it can’t be accounted for in the current week

curr_day

...SunMonTueWedThuFriSat...

Habit is Due

That’s it! We have enough variables to form a boundary that ensures when a habit shows it’s completed, it really is

Let’s implement them!

Implementation

Get day: We already have a way to get the number of today but to get the number of Sun, Mon, Tue, Wed, Thu, Fri, Sat we’ll have to do a little mapping

{
  "Sun": 0,
  "Mon": 1,
  "Tue": 2,
  "Wed": 3,
  "Thu": 4,
  "Fri": 5,
  "Sat": 6
}

or in Notion terms

if(contains(prop("Days"), "Sun"), 0, <fallback>)
if(contains(prop("Days"), "Mon"), 1, <fallback>)
if(contains(prop("Days"), "Tue"), 2, <fallback>)
if(contains(prop("Days"), "Wed"), 3, <fallback>)
if(contains(prop("Days"), "Thu"), 4, <fallback>)
if(contains(prop("Days"), "Fri"), 5, <fallback>)
if(contains(prop("Days"), "Sat"), 6, <fallback>)

first_day: Since they’re all numbers, getting the minimum of all selected days should be enough to get the “first day”. And for days that aren’t selected, we could keep a default of 7 so it never gets selected(the maximum number is 7)

First Day

last_day: Following the same logic, we could get the maximum and keep the defaults as -1 so it doesn’t get selected

Last Day

next_day: For the next_day we’ll need the first day on or after today. Bear with me haha. It’s a lot like the logic for first_day with the only difference being, if the day matches but is before today then it’s ignored

Next Day

prev_day: And last but not least, following the logic from next_day, here’s the implementation of prev_day

Prev Day Sorry, I got bored and changed the code a bit, but it’s the same thing

Now you might be asking yourself, but TheCist, your next_day/prev_day clearly don’t overflow to the next and previous week… did you lie to me?

And to that I say

Till next
time

Author’s Notes

Jokes aside, there’s a lot more to it, like accounting for paused days, skipping weeks, calculating days till its due and the overflows, but I think this is a nice place to stop, and I’ll be writing another blog continuing from here

Real quick, following the current logic, were you able to make a habit due in the experience above?

Let me know!