A date calculation Keyboard Maestro subroutine

The release of Keyboard Maestro 10, with its addition of subroutines, and a couple of recent questions on the Keyboard Maestro forum regarding date calculations got me thinking that I should make a subroutine that calculates how many days it will be until the next Monday, Tuesday, Wednesday, or whatever weekday. And while I’m at it, I should do the same for the most recent Monday, Tuesday, etc. The calculations could be done in the same subroutine, with the difference being that upcoming days return a positive number and previous days return a negative number.

There are two reasons I decided to have the subroutine return a number instead of a date:

Date calculations of this type (when is the next/previous weekday?) are typically done using modulo arithmetic, which is basically division where you return only the remainder. Ideally, the language you’re programming in assigns the numbers 0 through 6 to the days of the week (which weekday is Day 0 doesn’t matter as long as you know what it is) and you add or subtract days mod 7 to get the weekday of interest.

Keyboard Maestro has a DOW() function which, if given no argument, returns an integer representation of today’s day of the week. The return value ranges from 1 (Sunday) to 7 (Saturday) instead of 0 to 6, but we can work around that.

Let’s say the number of the target weekday is stored in the variable LocalTargetWeekday. Then if we’re looking for the next target weekday, we do this calculation,

(LocalTargetWeekday - DOW() + 7) MOD 7

and if we’re looking for the previous target weekday, we do this

(LocalTargetWeekday - DOW() - 7) MOD 7

The important thing to know here is that Keyboard Maestro’s MOD operator returns a positive number if the number before MOD is positive and a negative number if the number before MOD is negative. This is why I add 7 in the “next weekday” formula and subtract 7 in the “previous weekday” formula. By doing so, I force the value before MOD to be positive or negative as needed. And adding or subtracting multiples of 7 doesn’t change the remainder.

Let’s show how the calculation works. If it’s Thursday (5) and I want to know how many days until the next Monday (2), the calculation is

\[(2 - 5 + 7) \bmod 7 = 4 \bmod 7 = 4\]

The number of days until next Saturday (7) is

\[(7 - 5 + 7) \bmod 7 = 9 \bmod 7 = 2\]

Similarly, to get the number of days until the previous Monday, it’s

\[(2 - 5 - 7) \bmod 7 = -10 \bmod 7 = -3\]

And the number of days until the previous Saturday is

\[(7 - 5 - 7) \bmod 7 = -5 \bmod 7 = -5\]

All of which are the answers we expect, including the signs.

There is still one ambiguity left. Suppose we want the number of days until the next Thursday. The formula gives

\[(5 - 5 + 7) \bmod 7 = 7 \bmod 7 = 0\]

Is this what we want? Maybe, maybe not. I can certainly anticipate some situations in which you want next to mean strictly next, in which case the correct answer is 7, and others in which you want it to mean today or next, in which case 0 is correct. So the subroutine should handle both of those situations.

Here it is.

KM Days Until Weekday

Instead of building the subroutine yourself, you can just download it.

As you can see, the subroutine is called with three arguments:

As you can see, any word that starts with “b” will make the subroutine look for the previous weekday, and any word that starts with “n” will make the search not strict. Also, Keyboard Maestro’s way of matching strings with the “starts with” option is not case-sensitive, so you can use “B” and “N,” too.

The subroutine would be used in a macro like this:

KM Next Monday

For more on Keyboard Maestro’s new features, you can go to the link I gave above or listen to the most recent Automators podcast episode.

I used to end a lot of my posts with music videos in which the lyrics matched up with something I wrote about. I should go back to doing that.


A simple Shortcut for daily data entry

I’ve been measuring and recording my blood pressure more or less every day since early fall. I was in the “prehypertension” range at my last checkup, and my doctor wanted me to keep track of it. I’ve been adding each day’s reading to a table in Apple Notes, which has worked out well because Notes launches quickly on my phone, and it’s easy to add a new row with each reading.

Blood pressure note

To make entering the date faster, I made a Shortcut that puts today’s date on the clipboard. That shortcut sits in a widget on my iPhone’s home screen, and I run just before opening Notes. It’s just three steps:

StepActionComment
1 Today shortcut Step 01 Get today’s date.
2 Today shortcut Step 02 Format it.
3 Today shortcut Step 03 Put it on the clipboard.

I used the yyyy-MM-dd format because it’s easy to parse, and I thought someday I might want to pull the data out of Notes and do something with it.

That someday came yesterday when I decided to graph the data. For me, the easiest way to do that is to start with a CSV (comma-separated values) file and use a Python script to make the graph.

To make the CSV file, I opened the note on my Mac, copied the table, pasted it into BBEdit, and did some quick editing. The resulting file, named bp.csv, looked like this:

Date,Systolic,Diastolic
2021-09-17,125,71
2021-09-18,137,72
2021-09-19,138,73
2021-09-20,124,71
etc.

From here, it was relatively simple to write a short Python script that used Pandas and Matplotlib to read in the data and plot it. I’ve written plenty about making plots in Python, so I’m not going to say any more about that—at least not in this post. I want to focus on how I streamlined my system.

The plot showed me when I had been doing well and when I had been doing poorly in controlling my blood pressure, so it seemed like a good idea to set up a system that would make a new plot every week or two. But to do so, I couldn’t rely on the manual process I had just used to take the data from Notes and turn it into a CSV file. I needed to either automate that process or create a system that would update the CSV file directly with each day’s blood pressure reading. I opted for the latter with this shortcut:

StepActionComment
1 BP shortcut Step 01 Get today’s date.
2 BP shortcut Step 02 Format it in a form that Pandas likes.
3 BP shortcut Step 03 Prompt the user for blood pressure, which should be entered as either nnn/nn or nnn,nn.
4 BP shortcut Step 04 Separate systolic from diastolic by a comma instead of a slash.
5 BP shortcut Step 05 Assemble the CSV line for today.
6 BP shortcut Step 06 Append the line to the CSV file. The file is in the bp folder at the iCloud Drive root level.

With this in a widget on my home screen, I can tap the button to launch the shortcut and enter the reading I just took. The comma-separated date, systolic, and diastolic are appended directly to the CSV file. This new system is not only better at organizing the data for plotting, it’s also faster at entering it in the first place. No need to open Notes, no need to run a shortcut to get the date, just enter the BP directly and everything is taken care of.

Entry window

I don’t regret starting out using Notes. It was easy to set up, it synced across all my devices, and it kept the data in a format that I could export and transform. But it’s time to move on.

Update 02/15/2022 9:32 AM
A Mr. Richard Twitter of Ft. Lee, New Jersey asks1

Dear Dr. Drang,
Why don’t you put your blood pressure readings into the Health app? Couldn’t you just use the charts in Health? Or plot your blood pressure in Charty? And why don’t you buy a blood pressure cuff that will send its readings directly to Health?

Dear Mr. Twitter,
You ask a lot of questions with answers that should be obvious—even to someone from New Jersey.

The Health app is fine for tracking my walks and bike rides, but I don’t think much of it otherwise. I have basically no control over the charts it makes, and—most important—it lives exclusively on the iPhone. I want my data everywhere, and I don’t want to use workarounds to move it from one place to another. A CSV file on iCloud Drive allows me to do whatever I want.

As for Charty, it’s also more limited than I like. Plus, I use Python and Matplotlib so much, it takes almost no time for me to build a script that does just what I want.

Finally, I already had a cuff and didn’t see a need to buy a new one to get automatic connection to an app I wouldn’t use. And I’m cheap.


  1. Gen Xers may think they’ve taken over the cultural nostalgia thing after the Super Bowl halftime, but Boomers will never give up. 


Resurrecting the old Wordle for procrastinators

If you like to play Wordle, want to avoid the inevitable New York Times paywall, but forgot to download the original HTML and JavaScript files before the switchover, you can still get them from the Internet Archive. But there are a couple of tricks you’ll need to know if you want to get things running and preserve your playing history.

February 10 was the last day Wordle was still available at its old site. A URL to one of the Wayback Machine snapshots on that day is

https://web.archive.org/web/20220210031511/https://www.powerlanguage.co.uk/wordle/

But you don’t want to download the HTML and JavaScript you get from this link, because they will include additional code that the Internet Archive adds to make it work on their site. Modify the URL slightly by adding “id_” after the timestamp string,

https://web.archive.org/web/20220210031511id_/https://www.powerlanguage.co.uk/wordle/

and you’ll get the both the original HTML and JavaScript. This is a trick that works for every Wayback Machine page.

Wordle from Wayback Machine

The easiest way to get the HTML is choose Show Page Source in the Develop menu (you do have the Develop menu activated, don’t you?) and copy the code that appears in the lower center portion of the window.1 To get the JavaScript, copy the code from the main.e65ce0a5.js file in the Extra Scripts folder in the lower left part of the window.

Now you have the two files needed to run Wordle either locally or on a server you control. What about your history? Unfortunately, that will be reset if you start playing from a new location. But if you take screenshots of your history and your last game, you can get the information needed to restore your history.

Wordle uses what’s called local storage to save your most recent game and your history. The most recent game information is stored in gameState and the history in statistics. You can set these by making a simple web page that runs some JavaScript. Here’s an example:

xml:
 1:  <html>
 2:  <head>
 3:    <title>Setting history?</title>
 4:    <script type="text/javascript">
 5:      function n(e, a, s) {
 6:        return a in e ? Object.defineProperty(e, a, {
 7:          value: s,
 8:          enumerable: !0,
 9:          configurable: !0,
10:          writable: !0
11:        }) : e[a] = s, e
12:      }
13:      
14:      function sethistory() {
15:        var Ya = "statistics",
16:          Ja = "fail",
17:          Ua = {
18:            currentStreak: 10,
19:            maxStreak: 10,
20:            guesses: n({
21:              1: 0,
22:              2: 1,
23:              3: 3,
24:              4: 3,
25:              5: 2,
26:              6: 1
27:            }, Ja, 0),
28:            winPercentage: 100,
29:            gamesPlayed: 10,
30:            gamesWon: 10,
31:            averageGuesses: 3.9
32:          };
33:  
34:        window.localStorage.setItem(Ya, JSON.stringify(Ua));
35:  
36:        wa = "gameState",
37:        xa = {
38:          boardState: ["rites","gator","ultra","","",""],
39:          evaluations: [["present","absent","correct","absent","absent"],["absent","present","correct","absent","present"],["correct","correct","correct","correct","correct"],null,null,null],
40:          rowIndex: 3,
41:          solution: "ultra",
42:          gameStatus: "WIN",
43:          lastPlayedTs: 1644683109000,
44:          lastCompletedTs: 1644683109000,
45:          restoringFromLocalStorage: null,
46:          hardMode: !1
47:        };
48:        window.localStorage.setItem(wa, JSON.stringify(xa));
49:      }
50:    </script>
51:  </head>
52:  <body>
53:    <input type = "button" onclick = "sethistory()" value = "Set History">
54:  </body>
55:  </html>

Change Lines 18–31 to your history and Lines 38–42 to your most recent game. Lines 43–44 are a time stamp (in microseconds) for your most recent game. If it happens to be today, you can get that (more or less) from the date command:

date +%s

Because date returns seconds, and we need microseconds, just add three zeros to the end of the return value.

After you’ve made these changes, save the file as sethistory.html in the same directory as the Wordle HTML file. Then navigate to that page using the browser and device you like to play Wordle on, click the Set History button (which may be a tiny button in the upper left corner if you’re doing this on an iPhone), and your new Wordle should be set to carry on from your old one.


  1. I’m using the terminology from Safari. There are similar commands in other browsers. 


Still using NetNewsWire

Not much to say beyond the title. I just realized I’d never followed up on the post I wrote last year when I started using NetNewsWire again. The past seven months of RSS feed reading have been great. NetNewsWire looks good and works perfectly on iPhone, iPad, and Mac. Even better, I haven’t had any iCloud syncing problems.

I know there are people with very complex systems for collecting and reading stuff on the internet. For them, NetNewsWire probably wouldn’t work. Their loss.