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
that made this database what it became
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
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”
Name | Days | Repeats 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
Name | Created time | Habits |
---|
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"
)
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
Click the days(blobs) to interact!
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
and day()
returns the day of the date as a number
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
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
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
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
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)
last_day
: Following the same logic, we could get the maximum and keep the
defaults as -1
so it doesn’t get selected
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
prev_day
: And last but not least, following the logic from next_day
,
here’s the implementation of 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
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?