Every night at 12:15 AM my computer tells me to go to bed.

No, really. A voice says “time to sleep.” Then this window pops up:

yeah, it's a little verbose

If I hit “ok” the window pops up again a minute later. Sometimes I move the popup out of view and ignore it for a few minutes. Sometimes I snooze it and keep coding (or writing - it’s 12:28 AM right now). But most of the time I go to bed.

Why do you do this

I am a night owl. I like to stay up late.

Sometimes that’s fine! I’ve accepted that when I’m deep in the weeds optimizing finder display speeds I do much of my best work at night, and I don’t want to prevent that.

But this easily carries over to nights where I’m not doing anything worthwhile. And on those nights staying up late on my computer means looking at the same few reddit, hacker news, twitter, and twitch tabs. I don’t get much out of this; I’m happier when I just go to bed.

This has always been a problem for me. But it’s a bigger problem because I work for myself right now. There’s no pressure on me to wake up for a job; if I stay up too late I can just sleep in.

I’ve found that a 3:30 AM bedtime means that I can either have a fun social life or a productive work life but not both. I want both. And this is an effective way to nudge myself towards behavior that makes me long-term happy.

These popups help me because they:

  • Are easy to snooze when I need to. Snoozing them is just editing a file; I never hesitate to edit the file when I’m doing something interesting.
  • Are hard to snooze when I shouldn’t. I mean, they’re not literally hard to snooze, but I know that I’m going against the spirit of the popup if I snooze them to hit “more” on Hacker News.
  • Are easy to ignore for a minute if need be - I can move it to the side of the screen to finish reading something.
  • Are hard to ignore forever - it’s an always on top window! It’s annoying! It gets in the way!

How does this work?

I have a script called go-to-bed.sh in my ~/bin. I run it out of cron:

% crontab -l
* * * * * /Users/nroyalty/bin/go-to-bed.sh -from-cron

I specify `-from-cron` to make the script a little easier to test.

The script starts by grepping ps to see if there’s already an undismissed ‘Time to Sleep’ notification displayed and exits if there is - this prevents it from piling up notifications. It then does some math to check whether the current time is between the bounds in my ~/.go-to-sleep file and, if it is, uses afplay, say, and some AppleScript to play a sound, make a voice say “Time to Sleep”, and display a popup window.

You can grab the script from this gist. Here it is in full:

#!/usr/bin/env bash -x

# config file looks like
#START_TIME:0.16
#END_TIME:6.0
#HUSHED_UNTIL:2023-11-23T1:40:00

if [ "$1" = "-from-cron" ]
then
    if ps aux | grep osascript | grep -q 'Time to Sleep'
    then
        echo There is probably a pending window, exiting
        exit 0
    fi
fi

alert_title="Time to Sleep"
alert_message=$(
cat <<'EOF'
Most of the time when you are using your computer at this hour you aren't doing anything worthwhile.
If you're programming or doing something else cool, edit ~/.go-to-bed to hush this message until tomorrow.
Otherwise you should consider sleeping or doing something more fun! Read a book or a new yorker article. Play a game. Play your piano.
EOF
)
start_time=$(cat ~/.go-to-bed | grep START_TIME | cut -d: -f2)
end_time=$(cat ~/.go-to-bed | grep END_TIME | cut -d: -f2)
time=$(date +%H.%M)

convert_to_minutes() {
    hours=$(echo $1 | cut -d. -f1)
    minutes=$(echo $1 | cut -d. -f2)
    echo $((10#$hours * 60 + 10#$minutes))
}

# Convert HH:MM to minutes
start_minutes=$(convert_to_minutes $start_time)
end_minutes=$(convert_to_minutes $end_time)
current_minutes=$(convert_to_minutes $time)

if (( $current_minutes >= $start_minutes && $current_minutes < $end_minutes ))
then
    echo in window, maybe notifying
    hush_time=$(cat ~/.go-to-bed | grep HUSHED_UNTIL | cut -d: -f2-)
    zone=$(date +%z)
    sec_hush=$(date -j -f '%Y-%m-%dT%H:%M:%S%z' "${hush_time}${zone}" +"%s")
    sec_now=$(date +%s)

    if (( $sec_hush > $sec_now ))
    then
        echo hushed, doing nothing
    else
        echo notifiying
        afplay /System/Library/Sounds/Blow.aiff
        say -v Daniel time to sleep
        osascript -e "display alert \"${alert_title}\" message \"${alert_message}\""
    fi
fi

I hope you get use out of this script; I’ve been using it for a few months and it’s been tremendously helpful for me.

It’s also prompted me to push my “get off the computer if you’re just messing around” threshold earlier. I originally had the script prompt me at 2 AM. I’ve repeatedly moved that back; right now it’s at 12:15 AM. And writing this post made me realize that those 15 minutes aren’t doing me any favors - so I’ve moved it to midnight.

Happy sleeping.