What is a JSON feed? Learn more

JSON Feed Viewer

Browse through the showcased feeds, or enter a feed URL below.

Now supporting RSS and Atom feeds thanks to Andrew Chilton's feed2json.org service

CURRENT FEED

And now it’s all this

I just said what I said and it was wrong. Or was taken wrong.

JSON


TaskPaper actions for Drafts 5

Permalink - Posted on 2018-05-19 16:37

As I’ve written before, I use the TaskPaper plain-text format to keep track of my to-do lists. In particular, I have a main to-do list that I adjust every day, adding new projects and tasks as they come in and (I hope) marking older tasks as completed. At the beginning of every month, I make a new TaskPaper document, filling it with the uncompleted tasks from the previous month. In this way, the documents from the previous months are a record of what I was planning to do and what I finished.

On the Mac, I use Hog Bay Software’s TaskPaper app. Jesse Grosjean of Hog Bay created the TaskPaper format, and his app is still the best for creating and organizing TaskPaper documents. Unfortunately, Jesse stopped supporting his iOS TaskPaper app, and I haven’t thought much of replacements like Taskmator. As a result, I’ve been doing almost all of my TaskPaper tracking on the Mac, which isn’t the best way to keep on top of things, especially when I’m traveling.

But with a little bit of work, Drafts 5 can become a great TaskPaper app. Out of the box, it understands the project and task syntax and formats them nicely, including a strikeout for tasks marked with the @done tag.

TaskPaper list in Drafts

I created an action group that lets me do common manipulations of TaskPaper lists in Drafts. Be warned: by “common” I mean “common to me.” TaskPaper is a very flexible format, and I know a lot of people do things with it that I don’t. The actions in the group I made are tuned to my use. If you’re a TaskPaper user, some of them will be useful to you and some of them won’t. But I hope that even the actions that aren’t immediately useful to you will help you develop your own.

Let’s start with actions that upload and download my current monthly task list. I need this because I still edit this list on my Mac, so having it exclusively in Drafts isn’t an option. The current month’s task list is saved in a file named yyyy-mm.taskpaper in the Elements/tasks subfolder of my Dropbox folder,1 where yyyy and mm represent the current year and month.

The Upload Dated action (which appears as just Upload in the keyboard row) has just a single Dropbox action, reflecting the file name and folder choices:

Upload Dated action

The file name is set using template tags for the current date, and the action overwrites whatever is in that file with the contents of the current draft.

The Reload Dated action (which appears as Reload in the keyboard row) does the opposite, opening the appropriate file from Dropbox and replacing the current draft with its contents. Drafts doesn’t have a handy-dandy action for opening from Dropbox, but it does have a library of JavaScript functions for dealing with Dropbox files. Here’s the script step for Reload Dated:

javascript:
 1:  // Get TaskPaper file for current month and set the draft to it.
 2:  
 3:  // Dropbox folder where task file is.
 4:  var path = '/Elements/tasks/';
 5:  
 6:  // Assemble filename from today's date.
 7:  var today = new Date();
 8:  var yr = today.getFullYear().toString();
 9:  var mo = today.getMonth() + 1;
10:  mo = mo.toString().padStart(2, '0');
11:  var filename = path + yr + '-' + mo + '.taskpaper';
12:  
13:  // Get the file from Dropbox and set draft to it.
14:  var db = Dropbox.create();
15:  var content = db.read(filename);
16:  editor.setText(content);

I think this is mostly self-explanatory. To me, the most interesting part is handling the month number in Lines 9 and 10. The + 1 in Line 9 is there because JavaScript counts months starting from zero. Line 10 uses the padStart function to add a leading zero to single-digit months. I’m not sure when padStart was added to JavaScript, but it wasn’t there the last time I decided it worth buying a JavaScript reference book.

With the upload/download actions out of the way, let’s move on to the actions that manipulate the lists themselves.

We’ll start with New Item. When you’re adding a series of tasks to a project, Drafts helpfully inserts the tab-hyphen-space at the beginning of the line. but it doesn’t do that for the first task in a newly added project. The New Item action is a simple text insertion action that adds those three characters at the current insertion point. Nothing special, but it does help, especially when working with the software keyboard, which doesn’t have a Tab key.

Next is the Mark Done action, which adds a @done tag and the current date to the end of the current line or the end of all the selected lines if text is selected. Mark Done is smart enough to ignore project lines.

javascript:
 1:  // Mark current task or selected tasks as done today.
 2:  
 3:  // Set the done marker text with today's date.
 4:  var today = new Date();
 5:  var yr = today.getFullYear().toString();
 6:  var mo = today.getMonth() + 1;
 7:  mo = mo.toString().padStart(2, '0');
 8:  var da = today.getDate()
 9:  da = da.toString().padStart(2, '0');
10:  var marker = ' @done(' + yr + '-' + mo + '-' + da + ')';
11:  
12:  // Get all the currently selected lines.
13:  var lines = editor.getSelectedLineRange();
14:  var sel = editor.getTextInRange(lines[0], lines[1]).replace(/\n$/, '');
15:  
16:  // Loop through the lines, adding the done marker to tasks only.
17:  var selArray = sel.split('\n');
18:  var numLines = selArray.length;
19:  for (var i=0; i<numLines; i++) {
20:    if (selArray[i].startsWith('\t-')) {
21:      selArray[i] += marker;
22:    }
23:  }
24:  
25:  //Replace the selected lines with the marked lines.
26:  var doneText = selArray.join('\n');
27:  var doneLength = doneText.length;
28:  editor.setTextInRange(lines[0], lines[1], doneText + '\n');
29:  editor.setSelectedRange(lines[0] + doneLength, 0);

Lines 4–10 get today’s date and use it to format the marker to appear at the ends of the lines. It looks like this:

@done(yyyy-mm-dd)

The TaskPaper app for Mac has a setting for adding the date to @done tags, and I use it because I like to be able to look back to confirm when I finished something.

Lines 12–13 get the selected lines (which is just the current line if there is no selection) and put the text from those lines into the variable sel. The trailing newline is omitted.

Lines 17–23 split sel into an array of lines and loop through that array, adding the done marker to each task line. Task lines are distinguished from others by the tab-hyphen they start with.

Lines 26–29 clean up by replacing the originally selected lines with the altered text from the previous step. The cursor is then placed at the end of the last selected line.

The Marked Waiting action is basically the same as Mark Done except it puts a @waiting tag at the end of each selected task line.

Archive Done mimics another feature of the Mac TaskPaper app. It goes through the list, finds all the tasks marked @done and adds them to an Archive section at the bottom of the document. The archived tasks are marked with the name of the project they came from (in a @project tag at the end of the line), and they are placed at the top of the Archive section. If there isn’t already an Archive section, one is created.

Here’s how it works. Let’s go back to the task list I showed at the top of the post:

Before archiving

As you can see, there are a couple of done tasks in the Administration project. After running the Archive Done action, the draft looks like this:

After archiving done tasks

Here’s the script step of Archive Done:

javascript:
 1:  // Archive @done tasks
 2:  
 3:  // Set up variables.
 4:  var orig = draft.content;
 5:  var active = '';
 6:  var archive = '';
 7:  var project = '';
 8:  var archiveRE = /^Archive:$/m;
 9:  var projectRE = /^([^:]+):\s*$/;
10:  var doneRE = /@done/;
11:  
12:  // Save the Archive section if there is one.
13:  var archStart = orig.search(archiveRE);
14:  if (archStart >= 0) {
15:     archive = orig.substring(archStart, orig.length);
16:     archive = archive.replace(/\s*$/, '\n');
17:     archive = archive.replace(/^Archive:\n/, '');
18:  } else {
19:     archStart = orig.length;
20:  }
21:  
22:  // Go through the unarchived tasks, line by line.
23:  // Keep track of the current project and collect @done items.
24:  orig = orig.substring(0, archStart).replace(/\s+$/, '')
25:  var lines =orig.split('\n');
26:  var len = lines.length;
27:  var i;
28:  for (i=0; i<len; i++) {
29:     var isProject = lines[i].match(projectRE);
30:     if (isProject) {
31:         project = isProject[1];
32:         active += lines[i] +  '\n';
33:     } else {
34:         if (lines[i].match(doneRE)) {
35:             // New archived lines go on top of existing archive
36:             // to match Mac TaskPaper behavior.
37:             archive = lines[i] + ' @project(' + project + ')\n' + archive;
38:         } else {
39:             active += lines[i] + '\n';
40:         }
41:     } 
42:  }
43:  
44:  // Replace the draft with the edited active and archive sections.
45:  editor.setText(active + '\nArchive:\n' + archive);
46:  editor.setSelectedRange(0, 0);

Lines 4–10 set up a bunch of variables for later use. Lines 13–20 find the Archive section and save all of its lines to the variable archive (which was initialized to an empty string). They also set the archStart variable to the character index of the beginning of the Archive section. This will be used later to determine the end of the active (unarchived) tasks. If there is no Archive section, archive remains blank and archStart is the end of the document.

Lines 24–42 are the heart of the script. They put the text of active section of the document into the active variable, turn it into an array of lines, and loop through the lines, looking for done tasks. Whenever a project header line is encountered (Lines 29–32), the project variable is updated to keep track of the current project. Done tasks (found in Line 34) are extracted and marked with the current project name and added to the front of the archive variable (Line 37)

When the looping is done, the active and archive sections are concatenated and the draft is replaced with the result. The cursor is put at the top of the draft.

One last action. As you can see in the screenshots, it’s my habit to have no space between the projects. I think the formatting is sufficient to make the list readable without extra whitespace.2 This does, however, make rearranging projects difficult in Drafts. Drafts has a rearranging mode, but it won’t work on a project-by-project basis unless there’s a blank line between the projects.

The Un/Block action addresses this problem by adding or deleting blank lines between the projects. It toggles between the tightly spaced format I like and the blank-line-separated format needed to use Drafts’s block rearrangement. When I want to move a project to the top of the list to make it more prominent, I run Un/Block to add blank lines between the projects, use Drafts block rearranging to drag the projects into the order I want, and then run Un/Block again to get rid of the extra blank lines. Un/Block is smart enough to maintain the blank line above the Archive section.

Here’s the script step for Un/Block:

javascript:
 1:  // Toggle the presence of blank lines above all projects.
 2:  
 3:  // Regexes for project headers.
 4:  var projREtight = /([^\n])(\n[^:\n]+:)/g;
 5:  var projREloose = /(\n\n)([^:\n]+:)/g;
 6:  var archiveRE = /^(Archive:)$/m;
 7:  
 8:  // Get the text of the current draft
 9:  var d = draft.content;
10:  
11:  // Make new text with blank lines above all project
12:  // headers that didn't have them.
13:  var e = d.replace(projREtight, '$1\n$2');
14:  
15:  // If there was no change, take the blank lines away.
16:  if (e == d) {
17:    e = d.replace(projREloose, '\n$2');
18:    e = e.replace(archiveRE, '\n$1');
19:   }
20:  
21:  // Replace the text of the current draft
22:  editor.setText(e);

Lines 4–6 define a set of regular expressions used to find project headers under different conditions. Line 13 replaces all “tight” project headers with “loose” ones. If that replacement didn’t do anything (Line 16), the spacing must already be loose, so Lines 17–18 replace the loose spacing with tight (except for the extra line above the Archive section).

I’ve been using these actions for over a month, and I’m pretty happy with the way they work. The functionality has remained the same for the past few weeks, with most of the effort going into simplifying the actions to make them easier to understand and maintain. As I say in the Drafts Action Directory, these actions are idiosyncratic, but I hope they can be used by others to make actions that fit their TaskPapering needs.


  1. This subfolder choice is historical rather than rational, and reflects my long-ago use of the iOS plain text editor, Elements, which required an Elements folder. I suppose I should reorganize things, but I’ve never bothered. 

  2. The Archive section is set off from the rest by a blank line. This is something the Mac TaskPaper app does, and I mimic that behavior in Drafts. 


[If the formatting looks odd in your feed reader, visit the original article]


Drafts and Dropbox

Permalink - Posted on 2018-05-06 19:06

Drafts has long had an easy way to save a draft to Dropbox: You use the first line of your draft as the file name and make a action with the Dropbox step set up something like this:

Simple Dropbox save step

Drafts knows, through its template tag system, that safe_title is the first line of the draft with troublesome characters removed (you can also use the title tag, which takes the first line as-is). The Dropbox path is the slash-separated list of subfolders to your main Dropbox folder. Here, I’ve shown it as a fixed, literal path, but you can also use tags to choose a subfolder by date, another line in the draft, or even the expansion of a TextExpander snippet.

But what if you don’t want the first line of your file to be the title? For example, when I write a blog post, the system of scripts that do the publishing expect the header lines of the Markdown source code to look like this,

Title: A line-numbering action for Drafts
Keywords: drafts
Summary: A script/action for numbering lines of code in a style that works on this blog.
Date: 2018-05-05 18:31:02
Slug: a-line-numbering-action-for-drafts

and they expect the source to be a file with the name <slug>.md in the yyyy/mm subfolder of my blog’s source directory, where the slug is taken from the Slug line and the year and month are taken from the Date line. To publish from my iPad, I need to save the draft to that spot with that file name.

I do this with a two-step action. The first step is a script that gets the required information from the draft:

javascript:
 1:  // Set template tags for the Dropbox folder and filename
 2:  // of a blog post.
 3:  
 4:  var d = draft.content;
 5:  
 6:  // Set up regexes for header info.
 7:  var dateRE = /^Date: (\d\d\d\d)-(\d\d)-\d\d \d\d:\d\d:\d\d$/m;
 8:  var slugRE = /^Slug: (.+)$/m;
 9:  
10:  // Get the year and month and set the path.
11:  var date = d.match(dateRE);
12:  var year = date[1];
13:  var month = date[2];
14:  var path = '/blog/all-this/source/' + year + '/' + month + '/';
15:  
16:  // Get the filename from the slug.
17:  slug = d.match(slugRE)[1];
18:  
19:  // Set tags for use in other action steps.
20:  draft.setTemplateTag('blog_path', path);
21:  draft.setTemplateTag('blog_slug', slug);

Line 4 pulls in the entire contents of the current draft. Lines 7 and 8 establish regular expressions for extracting the year, month, and slug from the header lines. Note the capturing parentheses in Line 7 collect the year and the month, and the capturing parentheses in Line 8 collect the slug. The m flag at the end of each regex stands for “multiline,” which means the ^ and $ represent the end of a line, not the end of the entire text string being searched.

Lines 11–14 run the date regex on the text, pull out the year and month, and construct the path to the subdirectory where the file will be saved. Line 17 extracts the slug.

Finally, Lines 20 and 21 create the blog_path and blog_slug template tags, which we’ll use in the next step of the action. The setTemplateTag function is described in the Drafts JavaScript documentation.

The second step in the action is similar to the simple Dropbox step shown above. This time, though, we’re using the tags defined in the script.

Blog post saving Dropbox step

Now I can write my blog posts with the same format I’ve used for years and can upload them to Dropbox in a single step when I’m ready to publish.

This action is very handy, but it’s specific to one use case. I wanted a more general solution for saving other types of files—LaTeX files, Python scripts, whatever—to Dropbox. In particular, I wanted flexibility in where the path and file name could be located within the file, and I wanted them to be easy to write and easy to identify when reading.

I decided to steal the idea of modelines from Vim and adapt it to file names and paths. Somewhere in the draft, I put a line—which I call a fileline—that contains

dbox:/path/to/subfolder/filename.txt

and that provides both the path and the file name for where the draft should be saved. Typically, these will be inside comments, so the draft of a Python file will start with

#!/usr/bin/env python
# dbox:/path/to/subfolder/filename.py

and a LaTeX file will have

% dbox:/path/to/subfolder/filename.tex

somewhere in the header.

Like my blog post saving action, the action that uses these “filelines” to determine where to save the draft consists of two steps. First, the script step that creates the template tags,

javascript:
 1:  // Set template tags for Dropbox path and file name
 2:  // from dbox: file line in draft.
 3:  
 4:  var d = draft.content;
 5:  
 6:  // Regex for fileline.
 7:  var fileRE = /dbox:(.+)\/(.+)/m;
 8:  
 9:  // Get the year and month and set the path.
10:  var fileline = d.match(fileRE);
11:  var path = fileline[1] + '/';
12:  var filename = fileline[2];
13:  draft.setTemplateTag('dbox_path', path);
14:  draft.setTemplateTag('dbox_filename', filename);   

and then the Dropbox step that uses those tags for saving,

Fileline Dropbox step

The script follows the same outline as the blog post saver, but the regex in Line 7 is worth an extra look. It takes advantage of the fact that the + quantifier is “greedy.” So the dbox:(.+)\/ part of the regex captures every character after “dbox:” and before the last slash on the fileline. So no matter how many subdirectories deep I want to save this draft, they’re all captured in fileline[1]. Only the file name is captured in fileline[2].

This action is available to download and install from the Drafts Action Directory.

There are ways to do this without using scripting. You could, for example, make a header with the file name in the top line, the directory in the second line, and a blank line separating the header from the rest of the contents. With the proper template tags, you could set up a single-step action that saves only the remainder of the draft, not its entire contents.

Header Save Dropbox step

I’ve put this in the Drafts Action Directory, too, calling it Header Save. Although it’s simple and easy to understand, because the header isn’t saved to Dropbox, there’s a difference between the contents of the draft and the contents of the saved file. I use the fileline solution because I prefer a system in which the draft and the saved file are the same.


[If the formatting looks odd in your feed reader, visit the original article]


A line-numbering action for Drafts

Permalink - Posted on 2018-05-05 23:31

Among my many deficiencies as a human is my failure to write a review of Drafts 5. It will come eventually, but in the meantime, I’ll put out a few posts on how I’m scripting and configuring Drafts to be my primary text editor on iOS for both short snippets (which is how I’ve used it for years) and longer pieces like reports and blog posts. If you want to see a real review of Drafts 5, I’ll suggest

Because so many of my blog posts include source code with line numbers, I need my text editor to have a line-numbering command. Drafts 5 has built-in a way of displaying line numbers,1 but I need the numbers to part of the actual text. The action I made for doing this is called, with the great imagination I’m known for, Number Lines, and you can get it from the Drafts Action Directory.

The action consists of a single step, which is this script:

javascript:
 1:  // Number the selected lines (or all lines) with colon and
 2:  // two spaces after each line number.
 3:  
 4:  
 5:  // Function for right-justifying text str in width n.
 6:  function rjust(str, n) {
 7:    var strLen = str.length;
 8:    if (n > strLen) {
 9:      var prefix = ' '.repeat(n - strLen)
10:      return prefix + str;
11:    } else {
12:      return str;
13:    }
14:  }
15:  
16:  // Get either the current selection or the entire draft.
17:  var sel = editor.getSelectedText();
18:  if (!sel || sel.length==0) {
19:    editor.setSelectedRange(0, editor.getText().length);
20:    sel = editor.getSelectedText();
21:    }
22:  
23:  // Break the text into lines and number them.
24:  // Right-justify the numbers and put a colon and
25:  // two spaces after the line number.
26:  var lines = sel.split('\n');
27:  var numLines = lines.length;
28:  var width = Math.floor(Math.log(numLines)*Math.LOG10E) + 1;
29:  var numbered = [];
30:  var lNum;
31:  for (var i=0; i<numLines; i++) {
32:    lNum = i + 1;
33:    numbered.push(rjust(lNum.toString(), width) + ':  ' + lines[i]);
34:  }
35:  
36:  // Replace the original text with the line-numbered text.
37:  editor.setSelectedText(numbered.join('\n'));

This is longer than I think it should be, mainly because JavaScript doesn’t have Python’s (or Perl’s or Ruby’s) text-handling capabilities. It doesn’t even have a C-style sprintf. So the rjust function in Lines 6–14 is there just to right justify the line numbers.

Update May 5, 2018 9:56 PM
This tweet from Tim Tepaße let me know that recent versions of JavaScript include a padStart method that will handle the right justification of strings. So the rjust method wasn’t necessary, and line in which it’s used can be changed to

javascript:
numbered.push(lNum.toString().padStart(width) + ':  ' + lines[i]);

I’ve left the original code here in the blog post, but the Drafts Action Directory has been updated.

This tells me a couple of things:

  1. Code in other actions I’ve written that zero-pads numbers to ensure two digits for month and day numbers can be simplified with padStart (it has an optional parameter for the padding character).
  2. I need to update the JavaScript references I use. The websites that Google steers me to don’t know about padStart, nor does my copy of David Flanagan’s JavaScript: The Definitive Guide. To be fair, I just noticed my copy of Flanagan was published in 2006.

Lines 17–21 get either the selected text or—if no text is selected—the entire draft. This is the text that will have line numbers added to it.

Line 26 splits the text into an array of lines so we can loop through and add line numbers to each one. Line 27 gets the length of the array, numLines, which we’ll use as a limit in the for loop that starts on Line 31.

Before we start the loop, though, we need to know how wide (in characters) the line numbers will be. I could do this by turning numLines into a string and getting its length, but there’s no mathematical fun in that. Instead, in Line 28, I took the integer portion of the base-10 logarithm of numLines and added 1 to it. And because JavaScript doesn’t have a base-10 log function, I had to use the natural log of the numLines and convert it to base 10 by multiplying it by the natural log of 10. Who says junior high math doesn’t come in handy?

Inside the loop, we prefix each line with the right-justified line number, a colon, and two spaces (we’ll get to the colon and the spaces in a minute), and push the resulting string onto the end of the numbered array, which we initialized as empty in Line 29.

With the loop finished, we join up the elements of numbered with a newline character and replace the selection in the draft with the numbered lines.

So why the colon and the two spaces after the line number? Well, partly because I think it looks nice in a plain text file, but mostly because I have a JavaScript/jQuery function here on the blog that takes line-numbered source code formatted that way and styles it so the line numbers are visible but less intrusive.2 The setup is described in this post from way back in 2010.

I’ve been using Drafts 5 for almost two months now (I came in late to the beta), and I’ve been slowing building up and refining a set of actions to help me use it for nearly all of my iOS writing. Some of them are so customized for my way of working that they won’t be immediately useful to anyone else. But I’ll still post them, as they give a sense of what Drafts 5 is capable of, and they can be the starting point for your own scripts.


  1. Strictly speaking, it’s paragraph numbering, but for source code that’s the same as line numbering. 

  2. If you’re reading the RSS feed of this post, you’ll see the line numbers and colons without any styling. You’ll have to go to the actual post to see what I’m talking about. 


[If the formatting looks odd in your feed reader, visit the original article]


A small Apple surprise

Permalink - Posted on 2018-05-02 01:53

It’s been a while since I’ve operated this here blog, so let’s get back into the swing of things with a post that nearly writes itself.

Apple announced its quarterly sales today. You can get loads of charts on all the data from the usual sources. Revenue was up significantly, but I’ve always been more interested in unit sales because it’s the number of Macs and iOS devices out there that provide the market for third-party developers who make the software I need to use these machines.

Here are the unit sales for the three products Apple reports separately. As always, the dots show the actual quarterly sales and the line is the four-quarter moving average. Also, I plot real dates along the horizontal axis, not Apple’s goofy fiscal quarters.

Apple sales

This doesn’t show the old iPhone sales growth, but it’s steady. Certainly not the precipitous dropoff that was being predicted some weeks ago from “supply channel sources.”

Because the iPhone dominates unit sales, the chart above isn’t particularly good at showing what’s going on with the iPad and the Mac. Here’s the iPad by itself:

iPad sales

The iPad has managed to pull off an entire year of growth after bottoming out this time last year. As with the iPhone, it’s not stellar growth, but it’s welcome after the previous three years. It would be nice to see it always over the 10 million threshold again.

(You’ll note that since becoming an iPad user, I’ve stopped being snarky about its no-longer-world-beating figures and have started rooting for it. That’s what happens when you have a dog in the fight.)

Here’s the Mac:

Mac sales

Bleh. While iMacs seem to be in good shape, the MacBook line, which has usually driven sales, has little to recommend it. The MacBook Air has wheezy old specs that make many prospective buyers fear they’d be getting instant obsolescence. The MacBook Pro has a keyboard with serious reliability problems and a feature, the TouchBar, that neither Apple nor third-party developers have done much with. The unsuffixed MacBook is, I think, a nice machine, but I’m not sure many users are willing to pay a premium for an extra-light notebook (that’s somewhat underpowered) when they can get an iPad.

The real news of Apple’s growth is coming from services and the elusive “other” category of products, and that’s been the case for a while. These categories don’t have unit sales figures, so I’ve kept away from them. Maybe I should rethink that.


[If the formatting looks odd in your feed reader, visit the original article]


The sunrise plot

Permalink - Posted on 2018-03-13 04:09

Since I almost never make a graph without showing the code for it, here’s how the sunrise/sunset plots in yesterday’s post were made.

Chicago sunrise and sunset

I started with the US Naval Observatory’s 2018 sunrise/sunset data for Chicago, which is a plain text table (i.e, monospaced font using space characters to align columns) that looks like this:

USNO data for Chicago

I copied the table, pasted it into BBEdit, and did some editing to get it into this form:

2018-01-01  0718 1631
2018-01-02  0718 1632
2018-01-03  0718 1632
2018-01-04  0718 1633
2018-01-05  0718 1634
2018-01-06  0718 1635
2018-01-07  0718 1636
2018-01-08  0718 1637
2018-01-09  0718 1638
2018-01-10  0718 1639
2018-01-11  0717 1640
2018-01-12  0717 1642
2018-01-13  0717 1643
2018-01-14  0716 1644
2018-01-15  0716 1645
2018-01-16  0715 1646
2018-01-17  0715 1647
2018-01-18  0714 1649
2018-01-19  0714 1650
2018-01-20  0713 1651
        etc.

Most of the editing consisted of selecting columns for February through December and pasting them under the January data. Then I prepended the year and month (with hyphens) in front of the the days. That left me with a file, called chicago-riseset.txt, with 365 lines and three columns. If I were going to do this sort of thing on a regular basis, I’d write a script to handle this editing, but for a one-off I just did it “by hand.”

The script that parsed the data and created the graphs is this:

python:
 1:  #!/usr/bin/env python
 2:  
 3:  from fileinput import input
 4:  from dateutil.parser import parse
 5:  from datetime import datetime
 6:  import numpy as np
 7:  from matplotlib import pyplot as plt
 8:  import matplotlib.dates as mdates
 9:  from matplotlib.ticker import MultipleLocator, FormatStrFormatter
10:  
11:  # Read in the sunrise and sunset data in CST
12:  # and convert to floating point hours
13:  days = []
14:  rises = []
15:  sets = []
16:  for line in input():
17:    d, r, s = line.split()
18:    days.append(parse(d))
19:    hr, min = int(r[:2]), int(r[-2:])
20:    rises.append(hr + min/60)
21:    hr, min = int(s[:2]), int(s[-2:])
22:    sets.append(hr + min/60)
23:  
24:  # Daylight lengths
25:  lengths = np.array(sets) - np.array(rises)
26:  
27:  # Get the portion of the year that uses CDT
28:  cdtStart = days.index(datetime(2018, 3, 11))
29:  cstStart = days.index(datetime(2018, 11, 4))
30:  cdtdays = days[cdtStart:cstStart]
31:  cstrises = rises[cdtStart:cstStart]
32:  cdtrises = [ x + 1 for x in cstrises ]
33:  cstsets = sets[cdtStart:cstStart]
34:  cdtsets = [ x + 1 for x in cstsets ]
35:  
36:  # Plot the data
37:  fig, ax =plt.subplots(figsize=(10,6))
38:  plt.fill_between(days, rises, sets, facecolor='yellow', alpha=.5)
39:  plt.fill_between(days, 0, rises, facecolor='black', alpha=.25)
40:  plt.fill_between(days, sets, 24, facecolor='black', alpha=.25)
41:  plt.fill_between(cdtdays, cstsets, cdtsets, facecolor='yellow', alpha=.5)
42:  plt.fill_between(cdtdays, cdtrises, cstrises, facecolor='black', alpha=.1)
43:  plt.plot(days, rises, color='k')
44:  plt.plot(days, sets, color='k')
45:  plt.plot(cdtdays, cdtrises, color='k')
46:  plt.plot(cdtdays, cdtsets, color='k')
47:  plt.plot(days, lengths, color='#aa00aa', linestyle='--', lw=2)
48:  
49:  # Add annotations
50:  ax.text(datetime(2018,8,16), 4.25, 'Sunrise', fontsize=12, color='black', ha='center', rotation=9)
51:  ax.text(datetime(2018,8,16), 18, 'Sunset', fontsize=12, color='black', ha='center', rotation=-10)
52:  ax.text(datetime(2018,3,16), 13, 'Daylight', fontsize=12, color='#aa00aa', ha='center', rotation=22)
53:  
54:  # Background grids
55:  ax.grid(linewidth=1, which='major', color='#cccccc', linestyle='-', lw=.5)
56:  ax.grid(linewidth=1, which='minor', color='#cccccc', linestyle=':', lw=.5)
57:  
58:  # Horizontal axis
59:  ax.tick_params(axis='both', which='major', labelsize=12)
60:  plt.xlim(datetime(2018, 1, 1), datetime(2018, 12, 31))
61:  m = mdates.MonthLocator(bymonthday=1)
62:  mfmt = mdates.DateFormatter('              %b')
63:  ax.xaxis.set_major_locator(m)
64:  ax.xaxis.set_major_formatter(mfmt)
65:  
66:  # Vertical axis
67:  plt.ylim(0, 24)
68:  ymajor = MultipleLocator(4)
69:  yminor = MultipleLocator(1)
70:  tfmt = FormatStrFormatter('%d:00')
71:  ax.yaxis.set_major_locator(ymajor)
72:  ax.yaxis.set_minor_locator(yminor)
73:  ax.yaxis.set_major_formatter(tfmt)
74:  
75:  # Tighten up the white border and save
76:  fig.set_tight_layout({'pad': 1.5})
77:  plt.savefig('riseset.png', format='png', dpi=150)

After all the imports in Lines 3–9, the script begins by using the fileinput module to read and parse the lines of the data file, one by one. Each line is split into date, rise time, and set time in Line 17. Line 18 parses the date using the dateutil library, returning a datetime object. Lines 19–20 then split the sunrise time into the hour and minute parts and convert them into a single floating point number. Lines 21–22 do the same thing to the sunset time. The dates and times are accumulated into the days, rises, and sets lists.

The duration of daylight is calculated by subtracting the rise time from the set time in Line 25. I could have done this within the loop of Lines 16–22, but chose to do it through NumPy array arithmetic instead.

Lines 28–34 handle the daylight saving time stuff. The USNO data are given in standard time. To convert to DST, I needed to create new date and time lists that extend only over the duration of DST—from March 11 through November 3—and add an hour to the sunrise and sunset times. Lines 28–29 get the indices necessary to slice the lists, Line 30 slices the list of dates, Lines 31 and 33 slice the lists of rise and set times, and Lines 32 and 34 add the DST hour to the rise and set times. It sounds more complicated than it is.

Then we start using Matplotlib to make our graph. Lines 37–47 create the plot and add all of the various lines and areas. The fill_between function creates the areas, and the plot function draws the lines. I use the alpha parameter in the fill_between calls to get the shading I wanted and to allow the gridlines to show through the filled-in areas. There was a bit of trial and error to get alphas that made the two DST zones look about the same.

The parts of the plot were labeled in Lines 50–52. The text function puts the given text (third parameter) at the given x- and y-coordinates (first and second parameters). The neat thing about this function is that the coordinates are given in the same units as the data, which is why you see the x-coordinate given as a datetime. The ha parameter is short for “horizontal alignment,” which allowed me to specify the center of the text so it would fall between the vertical gridlines. The rotation values were chosen through trial and error to get the text tilted to match (by eye) their curves.

Lines 55–56 display the gridlines. I set the linewidth to .5 points, but thin lines like that are better for PDF output than PNG.

After setting the font size for all the tick labels in Line 59, the horizontal axis is formatted in Lines 60–64. The horizontal limits were set to run the length of the year, and the tick marks delineate the month boundaries. The date format in Line 62 uses the standard strftime system, and has a bunch of space characters at the beginning to get the month labels to be (more or less) centered in the middle of each month instead of centered under the tick mark at the beginning of the month. There should be a better way to do this, but I haven’t found it.

The vertical axis is formatted in Lines 67–73. Lines 68 and 69 set the major and minor tick marks to be 4 hours and 1 hour apart, respectively. Recall that the rise and set times are just numbers—we can’t use the strftime system Line 70 formats the time labels, which are just numbers, to look like like hours.

Finally, Line 76 gets rid of a lot of the whitespace border that would otherwise surround the plot, and Line 77 saves it to a PNG file. The 10″ by 6″ plot size set back in Line 37, combined with the dpi=150 setting in Line 77, gives us an image that’s 1500×900 pixels.

I did make one small change to the script. As I mentioned in yesterday’s post, I thought the curve and tick labels were too small for the size of the graph as it appears in the blog. I bumped the font size up from 10 to 12 to make the text more legible. Not a huge difference, but a definite improvement.


[If the formatting looks odd in your feed reader, visit the original article]


One table following another

Permalink - Posted on 2018-03-12 03:39

Justin Grieser of the Washington Post had a story about Daylight Saving Time a couple of days ago, and unlike most such stories, it was generally favorable. I’ve said what I think about DST and don’t intend to revisit the topic here, but I do want to talk about how the WaPo article presented the sunrise and sunset data.

The gist of the article was similar to any argument in favor of DST: without it, we have lots of summer sunlight in the early morning, when it’s wasted for most of us. The argument was encapsulated in this table, which presents sunrise and sunset times in Washington, DC, using standard time throughout the year.

Rise and set table from WaPo

There are a few odd things about this table. Let’s start at the top. The headings for sunrise and sunset are given in emoji rather than words. I’m sure this seemed like a cute idea at the time, but it wasn’t very helpful to those of us who read the article on our phones, where the sun emoji, less than 2 mm across, looked more like a burnt orange smear than a sun. (Also, the emoji is officially named “Sunrise,” so it probably shouldn’t be used for sunset, even with the down arrow.) More distinct glyphs would have been the regular sun, ☀️, or even the sun with a face, 🌞, although the latter might be confused with one of the regular face emojis.

The next oddity is the distribution of dates. There’s no rule that says the dates have to be uniformly distributed throughout the year, but there’s no good reason for the haphazard scattering in this table. It starts out on the first of every month, then jumps to the 21st of June (to get the summer solstice), skips July entirely (because July 1 is too close to June 21?), jumps from October 1 to November 15 (to get back into standard time, I guess), and finishes up with December 21 (the winter solstice, but awfully close to January 1, where we started). A better choice would have been to use the 15th of every month. That would give a better sense of how sunrises and sunsets change throughout the year and would be close enough to the solstices. Hitting the solstices isn’t that important, anyway, as the sunrise and sunset extremes don’t occur on the solstices.

The weirdest thing, though, is why the article used a table at all. You can show a whole year’s worth of data in a graph, and it would give readers a better overall view of when the sunrise is too damned early. Graphs usually aren’t as good as tables for providing exact figures, but exact figures aren’t important here.

I took the US Naval Observatory’s sunrise and sunset data for Chicago in standard time and plotted them. This would be the Chicago equivalent of WaPo’s DC table.

Chicago rise and set in standard time

I didn’t label the axes because I thought they were obvious. The USNO uses a 24-hour clock in its data set,1 so I stuck with that for the vertical axis.

The graph is better than any table at showing the sinusoidal flow of rise and set times over the course of a year. The labels are little smaller than I intended, but unlike the WaPo app, you can zoom in to see details.

Another way to present the data would be to include the DST shift:

Chicago rise and set with DST

Here you can see how DST saves us from the earliest sunrise times, but at the expense of fairly late sunrise times at the two ends of DST. We didn’t get such late sunrises back when DST covered a shorter fraction of the year.2

I’ve also added the duration of daylight, the time between sunrise and sunset, as the dashed purple line. This is cheating, as I’m using the vertical axis for both time and duration, which are not the same; but the units work out, and I don’t think it’s confusing.

I’m not sure why Justin Grieser used tables instead of graphs in his article, but I have a guess. Most graphing programs have standard facilities for handing times and dates along the horizontal axis because timelines are so common. Plotting time along the vertical axis isn’t as common, and I suspect the software WaPo uses doesn’t make it easy to build the plots I did. If that’s the case, it’s another example of something I’ve complained about in the past: graphs being made—or in this case, not made—to accommodate the limitations of the software rather than the needs of the data.


  1. There is no 24:00 time—it’s 0:00 of the following day—but I included the 24:00 label because I thought the graph looked stupid without it. 

  2. I’ve often wondered how we can call something “standard time” when it’s in effect for only about four months. 


[If the formatting looks odd in your feed reader, visit the original article]


Travel, devices, and cables

Permalink - Posted on 2018-03-02 03:16

I was on a business trip in Philadelphia this week. I arrived Monday evening and while I was taking out my contact lenses for the night, I tore one of them. No problem. I always have a spare set of contacts in my shaving kit because I’m a careful planner and forward thinker.

Then I started to remove my Apple Watch, and my opinion of myself took a sudden turn. I realized I hadn’t brought the watch’s charger with me because I’m a terrible planner and worthless thinker.

In my defense, this was my first trip after buying the watch a couple of weeks ago, and I haven’t yet established good habits for traveling with it. I have, however, ordered a spare charging cable, so this won’t happen again.

Over the years, I’ve developed a set of charging accessories for travel that tries to strike a balance between simplicity and comprehensiveness. One compartment of my backpack contains

  1. A 5W Apple charger (the little cube that came with one of my iPhones).
  2. A 10W Apple charger (the bigger one with the flip-out prongs that came with my iPad Pro).
  3. Lightning cables in 4-inch, 3-foot, and 6-foot lengths.
  4. A Jackery 6000mAH battery.
  5. A 21-inch micro-USB cable that came with some device I don’t remember.

All of the cables are USB A at the other end. I’m in the lucky position of not having to support both USB A and USB C.

I bought the Jackery 3–4 years ago, and although it doesn’t have as much oomph as it used to, it still gets me through long days stuck in an airport when my iPhone use—either directly or as my iPad’s tethering connection—goes way up. The 4-inch Lightning cable was bought mainly to use with the Jackery; it’s a convenient length when the phone is charging in my jacket pocket or backpack.

I bought a 10-foot Lightning cable a couple of years ago, thinking it would be handy to reach those out-of-the-way hotel outlets. But it wasn’t one of my better purchases. It takes up a lot of space and is so thick—both the cable itself and the Lightning end fitting—that it’s too stiff and clumsy to use comfortably with the phone. This surprised me, as the 3- and 6-foot cables (top and middle in the photo below) are much closer in thickness.

Amazon 3-ft, 6-ft, and 10-ft lightning cables

Maybe a braided cable would be a better choice. In any event, the 10-footer has been relegated to occasional use at home to charge the iPad when it’s being used as a laptop proxy.

The micro-USB cable is there to recharge the Jackery and my Kindle, although I can’t remember ever needing to charge the Kindle during a trip.

I buy only white Lightning cables to make them easy to distinguish from the micro-USB, which is black. I decided to go this way a few years ago after buying a black Lightning cable and getting it confused with the micro-USB almost every time I tried to pull it out of the pouch. I suppose it would be even smarter to color code the different Lightning cable lengths, but I haven’t gone that far.

Adding an Apple Watch to the mix means it’s time to switch from individual wall warts to a multiport charger. That, too, was part of the order when I returned from Philadelphia.

By the way, my watch made it through the trip without dying, from about 6:30 AM on Monday to 9:30 PM on Wednesday without a charge and without switching to Power Reserve. I can’t say it got heavy use during this trip, as I was under the weather and did no formal exercising those three days (breaking my streak of completed rings). Still, that’s 63 hours, which is much better than I expected.


[If the formatting looks odd in your feed reader, visit the original article]


Chart consistency

Permalink - Posted on 2018-02-25 17:49

This is another in my irregular series on graphs that could be improved with a little work. It’s me being a grammar Nazi but with charts.

The article of interest is “How much better are today’s Winter Olympians than the first? The answer, in eight charts.” Words and graphics by Jacob Borage, published in the Washington Post a couple of days ago. The title tells you what to expect: a comparison over several events of this year’s results (or sometimes the 2014 results if the 2018 results weren’t available yet) to the first Winter Olympics results of 1924. The comparison is limited to sports in which the scoring is determined by a tape measure or a stopwatch.

The article starts with ski jumping:

Ski jumping

I like how Borage uses light and dark versions of the same basic color to do his comparison. We’ll see shortly that he uses a different color for each sport, which is also helpful. What I don’t understand about this graph is putting the two bars end-to-end. It suggests there’s some total amount being divvied up between 1924 and 2018, which certainly isn’t the case. A better chart would be a standard bar chart, with each bar starting at the same spot and the 2018 bar extending over twice as far. Such a chart is not only standard for this type of comparison, it would evoke the event itself—starting at the same point and heading off for whatever distance is achieved.

The 500-meter speed skating chart, which comes next in the article, is just what the doctor ordered:

500 meter speed skating

Note the change in color for a new event, but still with 1924 being the lighter version and 2018 the darker. Strictly speaking, this isn’t necessary because the bars are labeled, but its the kind of thing that helps the reader and shows some actual design thought. I do think that putting the city names in the chart is unnecessary clutter—the emphasis in the article is improvement with time, not venue—but otherwise I like this chart a lot.

The next chart is still in speed skating, but has shifted to the 1500-meter race:

1500 meter speed skating

What the hell is this? Why the switch to a donut chart within the same category? And why a donut chart at all? This, even more than the ski jumping chart, suggests a whole amount that’s being shared between two items. That’s not an apt comparison. I get that a donut chart looks sort of like a clock, and we are comparing times, but that’s not sufficient reason to use one here.

There are a couple of other speed skating comparisons, both of which use donut charts and continue the blue theme. I want to show the 10,000-meter chart:

10 kilometer speed skating

Do you see what’s wrong with this? The light and dark have been flipped, breaking the pleasing consistency established in the preceding graphs.

The next set of graphs cover cross-country skiing. Here’s a chart that covers the 18 km (from 1924) and 15 km (from 2018) races. Because the distances have changed, a direct comparison of the event times can’t be made, so Borage has plotted the average time per kilometer.

Cross country skiing average

We’re back to a standard bar chart, which I like, but we have the light and dark colors flipped, which I don’t. Then comes the 50 km event,

50 kilometer cross country skiing

where the colors have been flipped back to what they should be1 (and the modern time is from 2014 because the 50 km race hadn’t been skied when the article was published).

Finally, we have the four-man bobsled time:

Four man bobsled

Sigh. We’re back to the donut and we’ve switched the light/dark signalling again.

In the end, all of these graphs make the comparisons they’re supposed to, but the lack of consistency and the poor choice of graph type undercut the meaning. The decision to use different color themes for the different category events was a great idea made less great by flipping the light/dark representation. And using three different chart types for essentially the same type of comparison is just weird.

Consistency of design in graphing helps the reader as much as consistency of tense (to choose something I struggle with) in writing. Yes, the reader can figure it out, but you’ve lost some of the power and elegance of your message.


  1. By “should be,” I mean consistent with most of the other charts. There’s no particular reason the old events should be light and the new events dark, but once you choose a system, stick with it. 


[If the formatting looks odd in your feed reader, visit the original article]


Not loving JXA

Permalink - Posted on 2018-02-24 04:08

A few days ago, Brett Terpstra presented us TaskPaper users with a couple of Keyboard Maestro macros, one for natural language dates (e.g., “tomorrow” or “next Monday” in @due or @start tags) and one for incrementing or decrementing numerical values in tags like @priority(n).

As it happens, I don’t use TaskPaper in as formal a way as Brett does—experience shows that the more structure I put in my task management system, the less I use it—so I suspect I’ll never take advantage of Brett’s work. But tucked away in a footnote is a comment that rang true for me:

I’m still clumsy with JavaScript for Automation. As annoying as I’ve found AppleScript over the years, I’m just not loving JSA much more.

This is a perfect distillation of my experience with both AppleScript and JavaScript for Automation.1

The main problem with AppleScript is the tremendous variation in quality from one application’s dictionary to another’s. There are so many idiosyncrasies, it’s hard to remember which gimmicks apply to which app. JXA doesn’t—and can’t—solve that problem because it rests on the same Apple Events foundation as AppleScript. Worse, it adds a new problem: very little outsourced documentation.

By “outsourced documentation” I mean the vast collection of websites in which AppleScript programmers have written about the little tricks they’ve discovered to get their scripts to do what they want. For me, programming in AppleScript consists largely of working out in my head the overall structure of the script and Googling to find the right magical incantations for each individual part. Then revising the structure when I find that certain parts can’t be done the way I thought they could.

(There are, I’m sure, AppleScripters who don’t need to Google for syntax. I can’t imagine Doug Adams, for example, needing to search for help with the iTunes dictionary. But I’ve never scripted any single application long enough to master it, and even if I had, mastery of one app doesn’t necessarily translate to another.)

JXA doesn’t have that quarter-century of folk wisdom. So programming in JXA is just like programming in AppleScript, but with the extra step of translating someone’s AppleScript trick into JavaScript—a very different language.

When JXA was released, I really thought it would be superior to AppleScript. I imagined myself rewriting my old AppleScripts in this new, more normal language. But it hasn’t turned out that way. Like Brett, I am still clumsy with JXA and doubt I’ll ever get coordinated.


  1. And like Brett, I think JavaScript for Automation should be abbreviated JSA and usually write it that way. But Apple says it’s JXA (in documentation that still refers to macOS as OS X, but I’m not here to rant about how Apple’s enormous workforce can’t muster enough people to update the documentation for a line of products so small it could fit “on the table you’re sitting at”). 


[If the formatting looks odd in your feed reader, visit the original article]


Timers, reminders, alarms—oh, my!

Permalink - Posted on 2018-02-18 04:19

I was shocked—shocked!—to see people disagree with my last post. I was even more shocked to learn about bizarre omission in the HomePod software. I decided to dig into the many ways you can set timed alerts on your Apple devices and how the alert systems vary from device to device. It is, you will not be surprised to learn, a mess.

Let’s start with the summary. In the table below, I’m comparing the features of the three alert types on iOS: Timers, Alarms, and Reminders. Included in the comparison is how certain features work (or don’t work) on the iPhone, iPad, Watch, Mac,1 and HomePod. Most of the entries for the HomePod are empty because I don’t have one to test, but I’ve included it because it was the device that got me started down this path. Also, there’s that software omission I want to talk about.

  Timer Alarm Reminder
Number 1 ∞︎ ∞︎
Name/Description No Yes Yes
Autodelete Yes No Yes
Shared
    iPhone Yes Yes Yes
    iPad No No Yes
    Watch Yes Yes Yes
    Mac No No Yes
    HomePod ? ? No
Time left
    iPhone Yes No No
    iPad Yes No No
    Watch Yes No No
    Mac No No No
    HomePod ? ? ?
Time of
    iPhone No Yes Yes
    iPad No Yes Yes
    Watch No Yes Yes
    Mac No No Yes
    HomePod ? ? ?

Many of the entries in this table have caveats, so let’s go through it.

The number of alerts that can be set was the starting point for the last post. People want multiple timers in their HomePods. That’s great, but Apple’s never had multiple timers in any iOS device, which is why I’ve always used reminders instead. “Reminders aren’t a substitute for timers!” I’ve been told by several people. I admire your steadfast adherence to your principles, but I need a solution, not a manifesto. (We’ll get to the deficiencies of using reminders as a substitute for timers later in the post.)

Since there’s only one timer, there’s no need for it to have a name or description. So when the timer on your phone/watch/table/speaker goes off, you might have to think a bit before you remember what it’s for. Alarms and reminders don’t have this problem.

I didn’t mention alarms in my last post, but Kirk McElhearn reminded2 me of them. If you’ve only used Clock app’s UI to set an alarm, you may think you have to use a specific time (like 8:55 PM) instead of a relative time (in 20 minutes). But Siri offers another way:

Hey Siri, set a casserole3 alarm for 20 minutes.

One problem with using alarms as your alert system is that they don’t delete themselves when you dismiss them; they just sit there, inactive, taking up space in your list of alarms until you undertake a second action to remove them from the list. Timers delete themselves upon dismissal, which is certainly more convenient. Reminders almost delete themselves—when you mark a reminder as complete, it gets hidden in the Completed list. I take this as close enough to deletion that I gave Reminders a Yes on the Autodelete line.

One of the biggest advantages to using reminders is that they’re shared via iCloud, which also syncs them to your Mac. This is very convenient if you use reminders during the workday and allow notifications from the Reminders app, which I do. Timers and alarms are not shared; the timer you set on your phone doesn’t appear in the Clock app on your iPad or on your watch. But the watch is special because of its intimate relationship with the phone. Your watch will alert you of a timer or alarm set on your phone, even though it doesn’t appear in the watch’s Timer or Alarms app. The Mac is ignorant of all timers and alarms.

Here’s where we get to the HomePod’s software omission. Even if you set up your HomePod to access your reminders—which, I admit, you may be reluctant to do in some households—the HomePod will not alert you when a reminder comes due. I was first informed of this stunning fact by Holger Eilhard, and it’s been confirmed by others. So I guess you can create a reminder through your HomePod but not be alerted by one. For whatever that’s worth. Because I don’t think it’s worth much, I decided to put a No in the Reminder column for sharing on the HomePod.

A feature many people find essential is getting the time remaining before an alert goes off. I would like to tell these people to chill out, take a Zen approach, that “a watched pot never boils,” but that would only anger folks who seem to be a little on edge already. My blithe assertion that timed reminders is the solution to the lack of multiple timers was based too much on my own use. In the 4+ years I’ve been using reminders for timed alerts, I have never wanted to know how much time was left, but I guess the rest of the world doesn’t slavishly model itself after me.

So if you need to know the time left on an alert, the timer is your only friend. Neither alarms or reminders will give you that. Alarms and timers will give you the time an alert will go off (like 8:55 PM), but you’ll have to do the subtraction yourself, which isn’t convenient.

By the way, although I put a Yes in the “Time of” section for the Watch, my watch has never actually been able to tell me the time a reminder is due when I ask it via Siri. It definitely understands me, and it acts like it’s going to retrieve that information, but it’s never finished the job. I can, of course, see the due time of a reminder using the watch’s Reminders app.

And there are also a couple of problems with asking Siri for the time of a reminder on the phone:

Siri reminder times

The obvious problem is that the time Siri says is wrong. And it’s been wrong every time I’ve tried this over the past two days.4 For this example, the reminder was set for 3:50 PM, but Siri told me a time six hours earlier. Now, I happen to live six hours away from UTC, so my first thought was that Siri was programmed (stupidly) to respond in universal time. But then I realized the six hour difference was in the wrong direction. 3:50 PM US/Central is 9:50 PM UTC, not 9:50 AM UTC. So Siri’s answer is so bad it isn’t even wrong in an understandable way.

The less obvious problem is Siri’s characterization of my casserole reminder as the “next reminder.” Inexplicably, she uses that phrase even if the reminder you ask about isn’t the next one. Sigh.

After going through this exercise, I will continue to use timed reminders because

  • they work across all my devices, including the Mac;
  • their deficiencies regarding the time remaining don’t affect me; and
  • they don’t require a second action to get them out of the way when completed, unlike alarms.

I’ve said on Twitter that I think Apple intends timed reminders to be the substitute for multiple timers. I still think that, but I’m less certain now than I was a few days ago.

Update Feb 18, 2018 9:22 AM
There’s always more.

First, something I had scribbled in a note but forgot to put in the post: a timer may not sound an alert. If you like to fall asleep listening to music, you may have the Timer’s When Timer Ends setting assigned to Stop Playing.

Timer setting

If that’s the case, the next time you use Siri to set a timer, it won’t make a sound, which probably isn’t what you want.

Second, reader Thomas Shannon has emailed me that alarms go off only at minute markers. So if it’s 9:55:45 and you tell Siri to set an alarm for one minute, it will go off 15 seconds later. I was annoyed to hear this because I looked into this four years ago with regard to reminders and found that their alert times are not restricted to whole minutes. If you tell Siri at 9:55:45 to remind you of something in one minute, the alert goes off at 9:56:45.

I used to tell people the advantage of using Apple products was their consistency across devices and applications. I don’t do that anymore.


  1. You’re right, the Mac isn’t an iOS device, but it does work with Reminders, which can be very handy, so I’m including it. 

  2. Hah! I slay me. 

  3. I’m using casseroles in the examples because I’m a homespun Midwesterner (and not from Minnesota). 

  4. As I said above, I’ve never asked about the time of a reminder. Good thing, too. 


[If the formatting looks odd in your feed reader, visit the original article]


Friendly reminders

Permalink - Posted on 2018-02-16 01:02

My vision of myself as a powerful thinkfluencer in the Apple world took a real beating this week. It seemed as if everyone who got a HomePod was complaining that it couldn’t set multiple timers. This is something I’ve written about a couple of times, going back four years. And I’ve explained the solution. Is this thing on?

Of course, four years ago, I wasn’t talking about the HomePod, I was talking about the iPhone, but the principle is the same. In iOS, the timer function is in the Clock app, and there’s only one. There’s no way to have two timers running simultaneously and no way to give your timer a name that lets you know what it’s for.

But you do have Reminders. They have names and can be set to alarm not only at an absolute time, but also at a relative time:

“Hey Siri, remind me to check the casserole in 20 minutes.”

Casserole reminder

This works on my iPhone, iPad, and Watch, and I assume—based on this article—that it would work on my HomePod if I had one. This is clearly Apple’s preferred solution to setting mulitple timers, each with a distinct name.

So I was frustrated to hear John Gruber and Paul Kafasis in the latest episode of The Talk Show complain about the multiple timer problem. They should both know how to use Reminders to solve this problem. So should Myke Hurley, who made the same complaint in the most recent Upgrade.

I understand where they’re coming from. If you’re an Amazon Echo user, you’re probably in the habit of saying something like

“Alexa, set a 20-minute timer for the casserole.”

Habits like that are hard to break, especially as you get older.1 But Apple users should be used to the idea that Apple has strong opinions about the right way to use its products and you’re usually better off not bucking the system.

You don’t like cluttering up your Reminders with hundreds of “check the casserole” and “check the tea” items? Even though you typically don’t see completed reminders? There is a solution.

In the past couple of days, the HomePod complaint industry has moved on from multiple timers to white rings. Cheaply made leather circles are already coming onto the market, but I’m going to suggest that high end furniture protection should come from lace doilies with tatting that complements the HomePod’s fabric pattern.


  1. Myke is 30 now, so his brain has lost much of its former plasticity. 


[If the formatting looks odd in your feed reader, visit the original article]


LaTeX contact info through Workflow

Permalink - Posted on 2018-02-10 21:00

I’ve been writing more on my iPad recently; not just blog posts, but reports for work, too. Because I have a lot of helper scripts and macros built up over many years of working on a Mac, writing on the iPad is still slower. But I’m gradually building up a set of iOS tools and techniques to make the process go faster. Today’s post is about a Workflow I built yesterday with advice from iOS automation experts conveyed over Twitter.

For several years, I wrote reports for work using a Markdown→LaTeX→PDF workflow. For most of those years, it was rare for me to have to edit the LaTeX before turning it into a PDF. Recently, though, that rarity has disappeared, mainly because my reports have more tables and figures of varying size that need to be carefully positioned, something that can’t be done in Markdown. A few months ago I decided it would be more efficient to just write in LaTeX from the start. This wasn’t as big a change as you might think. I used to write in LaTeX directly, and the combination of TextExpander and a few old scripts I resurrected got me back up to speed relatively quickly—on the Mac, anyway.

On iOS, most of the TextExpander snippets I built for writing in LaTeX work fine, but the helper scripts, which tend to rely on AppleScript, don’t. One of the scripts I definitely wanted an iOS counterpart for was one that extracted the contact information from a client in a particular format. In my reports, the title page usually includes section for the name, company, and address of the client. This is added in the LaTeX source code by this:

tex:
\client{John Cheatham\\
Dewey, Cheatham \& Howe\\
1515 Loquitor Lane\\
Amicus OH 44100}

where \client is a LaTeX command I created long ago, and its argument needs the usual LaTeX double backslashes to designate line breaks. Also, ampersands, which are special characters in LaTeX, need to be escaped.

I thought I could whip something up in Workflow, but my limited understanding of Workflow isn’t conducive to whipping. When I first tried to put something together a couple of weeks ago, it looked to me as if I was going to have to painstakingly extract every piece of information from the selected contact, create variables to store them in, and then put those variables together into a new string of text. So I gave up.

Yesterday I decided to ask for help.

I would like to extract from a selected contact a standard name/address block as plain text:

Full Name
Company
Street Address
City, ST Zip

I don’t think Contacts or Interact do this. Does anything?
  — Dr. Drang (@drdrang) Fri Feb 9 2018 9:37 PM

As you can see, I asked for something a bit simpler than what I really wanted, and I was kind of expecting suggestions for an app that would do the trick. But I soon got a response from Ari Weinstein with a Workflow solution:

Sample workflow from Ari

Since Ari is a co-developer of Workflow, I kind of figured he knew what he was talking about. But I didn’t, and it’s because I didn’t appreciate Workflow’s magic variables. I’ve always thought of Workflow as being almost like a functional language, where each action transforms the data passed to in and sends it along to the next action in turn. That, at least, is what I thought happened when the actions are connected by lines.

Which is why I didn’t understand Ari’s workflow at first. I figured that if it was extracting the Street Address in the second step, there’d be no way for it to get ahold of the Name and Company in the fourth step. What I didn’t appreciate was that there can be side effects the usual view of a workflow doesn’t show you. In this case, the Contact that’s selected in the first step is saved to a magic variable (called “Contact”) that remains available for use in later steps. So the third and fourth steps have access to all the Contact information even after the extraction of the Street Address in the second step.

Ari’s sample is a standard workflow that would have to be run from within Workflow itself or from a launcher app like Launch Center Pro. I was thinking about how to turn it into an Action Extension that could be called from within Contacts when I noticed I had a Twitter reply from Federico Viticci:

Sample workflow from Federico

His suggestion is set up as an Action Extension that accepts only Contacts and extracts the info from the Workflow Input magic variable. Just what I was going to do.

“My” final workflow, called LaTeX Address, combines what I learned from Ari and Federico and adds some search-and-replace stuff to handle the LaTeX-specific parts:

LaTeX address workflow

The first two steps create a text variable named Ret that consists of a single line break. We’ll see why I needed it in a bit.

Steps 3–5 are the Ari/Federico mashup. I couldn’t use Federico’s suggestion to just add Workflow input:Street Address to the end of the block because my contacts usually include the country, even though the country is almost always the US, and I didn’t want that at the end of the block. At some point, I’ll improve this by writing up a filter that deletes the country line only if it’s the US, but this will do until I get another job with a non-US client.

Step 6 escapes the ampersands, and Step 7 adds the double backslashes to the ends of each line. You need four backslashes to get two in the output because regexes need two to produce one. I thought I could use \n at the end of the replacement string to get a line break, but I couldn’t get that to work. Thus, the Ret variable defined at the beginning of the workflow.

Finally, Step 8 puts the text on the clipboard, ready for pasting into a LaTeX document.

My plan is to use this extension in Split View, with my text editor, currently Textastic, on one side and Contacts on the other. When I need to insert the client info, I find it in Contacts, tap Share Contact to bring up the Sharing Sheet, and select the Run Workflow action.

Textastic and Contacts

This brings up the list of Workflow Action Extensions that can accept Contacts. I choose LaTeX Address from the list, switch focus back to Textastic, and paste the text block where it belongs. Boom.

Pasting workflow result-

I’ll try to remember to look for magic variables the next time I make a workflow. There is a trick to making them visible. When you’re editing a workflow and can insert a variable (magic or otherwise), a button with a magic wand will appear in the special keyboard row.

Button to see magic variables

Tapping it will give you a new view of your workflow, with the magic variables appearing where the workflow creates them.

Seeing the magic variables

You don’t need to do this, as all of these variables should appear in the special keyboard row if you keep scrolling it to the right. But I find it easier to understand what they are and where they come from in this view.

Thanks to everyone who had suggestions for me, especially Ari and Federico.


[If the formatting looks odd in your feed reader, visit the original article]