Jake McCrary

Scheduling cron tasks in mixed time zones

Have you ever needed to schedule a repeating task on a Linux host? If so, you’ve probably reached for cron. cron is widely available and reliable; it is a great choice for scheduling tasks.

Sometimes you find yourself scheduling a task and, ideally, you’d be scheduling that task referencing a different time zone. This is a common need if your programs are interacting with systems hosted in different areas of the world. If one system you interact with starts up at 7 AM Europe/London and another at 8 AM America/New_York, it would be much better to schedule your program to run using times specified in those time zones.

Why is that preferred?

  • If you schedule in your host time zone, you have to convert from the other time zone to your own. This is error prone.
  • Different time zones have different Daylights savings shifts. Having to adjust your schedule when your host or target time zone shifts is error prone.

Luckily, you can do this with cron! At least, with the cronie implementation.

You do this by specifying the time zone in the crontab with the CRON_TZ variable. Any line after a CRON_TZ specification is scheduled in the specified time zone. This persists until the next CRON_TZ value is specified.

Below is a sample crontab that schedules four tasks. One is scheduled in the host time zone, two in America/New_York, and one in Europe/London.

1
2
3
4
5
6
7
8
0 7 * * * echo "run at 7 AM in the host time zone"

CRON_TZ=America/New_York
0 7 * * * echo "Run at 7 AM New York"
10 7 * * * echo "Run at 7:10 AM New York"

CRON_TZ=Europe/London
* 8 * * * echo "Run at 8 AM London"

The one gotcha with this is that cronie’s behavior is unspecified if the scheduled time ends up in the daylights savings shift of the host machine1. So make sure you don’t do that.

My team at work has been taking advantage of this feature since early 2023 for scheduling all of our processes start and end times. It has been working great. Prior to figuring2 this out, the fall and spring time shifts were sources of issues as various countries shifted on different days. That entire source of problems has been solved through scheduling tasks in the proper time zone.


  1. We have unit tests that confirm someone hasn’t configured a task to run within one of these periods.
  2. Figuring this out was a bit of a chore. Even the Linux experts I talked to weren’t aware of being able to do this. Digging through the source of cronie was how I figured this out. Hopefully this article makes it easier for the next person. Though, now that I know the CRON_TZ solution, it is pretty easy to search and find other folks talking about this.

Reading in 2023

At the beginning of every year (not so much the beginning this year), I take the time to update my records of what I’ve read the previous year and write up a summary.

Previous summaries: 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022.

I’ve continued to keep track of my reading using Goodreads. My profile has nearly the full list of the books I’ve read since 2010.

2023 Goals

Last year I wrote:

I’d like to do a better job of keeping track of my reading. This should be pretty easy to do.

I don’t feel too bad about the reduction in reading but I’d like to read more this year. Some of my reading time has been replaced with worthwhile endeavors but not always.

Reading rejuvenates me. I need to keep it a regular part of my life.

Well, I don’t remember how poorly I did in 2022 of keeping tack of reading but I don’t think I did a great job in 2023. Was it better than 2022? Perhaps.

I didn’t write many reviews for specific books nor did I send out any emails about what I was reading throughout the year. But I did do fewer corrections of the data I had in Goodreads.

I did read more than I did last year.

If I were grading myself on how well I achieved my goal, I’d give myself a B.

Highlights

Below are the highlights from 2023. Any title link will bring you to Goodreads.

Five-star books

Sea of Tranquility by Emily St. John Mandel

I started off the year with this book and really enjoyed it. It is a pandemic story that has characters spanning hundreds of years.

It is a little weird and beautiful. It did an excellent job of conveying feeling.

This is one of the few books that I did a mini-review of when I finished it. That review:

A beautiful novel. While reading, I found myself rereading parts. Not because the sentences were confusing but because they expressed such a clear feeling.

I highly recommend this book.

The First Bad Man by Miranda July

This book is real weird and fantastic. The main character is extremely interesting. I had a blast reading this.

Network Effect by Martha Wells

I really enjoy this series and thought this as an excellent addition to it.

Tomorrow, and Tomorrow, and Tomorrow by Gabrielle Zevin

This was an excellent book about friendship and creative, collaborative pursuits.

How to Live: 27 conflicting answers and one weird conclusion by Derek Sivers

A friend offered to send copies of this books to anyone who was up to reading it and I’m glad I spoke up and asked. Each chapter presents a philosophy. And the next chapter usually presents a different, conflicting philosophy. Every chapter is small, so it is easy to read a bit and take some time to reflect.

The Little Book of Talent: 52 Tips for Improving Your Skills by Daniel Coyle

A bunch of distilled ideas around improvement. No fluff, just tips.

Wool Omnibus by Hugh Howey

I have mixed feelings about including this book on the list. I reread this series when I was debating watching the television show. Even on a second read, I still enjoyed this story.

Is it complex and view shattering science fiction? Nahh, not really.

Is it entertaining? Yep.

Other highlights

Chain-Gang All-Stars by Nana Kwame Adjei-Brenyah

This is a really well done book that explores a modern gladiator system of punishment for criminals. It is dark and creative and a solid commentary on modern society.

Maybe this should have been five-stars.

The Sparrow by Mary Doria Russell

I knew nothing about it going in and thought this was a interesting take on first contact with aliens.

Stats

I read 53 books in 2023.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| Year | # of Pages | # of Books |
|------+------------+------------|
| 2023 |      14956 |         53 |
| 2022 |      10127 |         35 |
| 2021 |      19564 |         57 |
| 2020 |      12093 |         43 |
| 2019 |      15994 |         42 |
| 2018 |      13538 |         36 |
| 2017 |      18317 |         48 |
| 2016 |      22790 |         59 |
| 2015 |      21689 |         51 |
| 2014 |      24340 |         71 |
| 2013 |      19815 |         60 |
| 2012 |      14208 |         44 |
| 2011 |       9179 |         19 |
| 2010 |      14667 |         46 |

There are definitely some spiky months in the data.

Book and pages count by month

Unsurprisingly, electronic books are still the dominate format.

1
2
3
4
5
6
|           | 2023 | 2022 | 2021 | 2020 | 2019 | 2018 | 2017 | 2016 | 2015 |
|-----------+------+------+------+------+------+------+------+------+------|
| audiobook |    0 |    0 |    0 |    1 |    0 |    0 |    0 |    0 |    0 |
| ebook     |   51 |   34 |   56 |   41 |   43 |   37 |   37 |   56 |   47 |
| hardcover |    0 |    1 |    0 |    0 |    1 |    1 |    7 |    0 |    1 |
| paperback |    2 |    0 |    1 |    1 |    7 |    5 |    5 |    3 |    3 |

Fiction continued to dominate the book count this year. If I did this by page count I think it would tell a different story, as I read quite a few short stories published as Kindle books and this skewed my fiction book count high.

1
2
3
4
5
|                           |  2023 | 2022 |   2021 |   2020 |   2019 |   2018 |
|---------------------------+-------+------+--------+--------+--------+--------|
| fiction                   |    47 |   28 |     46 |     26 |     28 |     29 |
| non-fiction               |     6 |    7 |     11 |     17 |     23 |     14 |
| fiction:non-fiction ratio | 7.8:1 |  4:1 | 4.18:1 | 1.53:1 | 1.22:1 | 2.07:1 |

Here is the star rating distribution.

1
2
3
4
|             | 2 stars | 3 stars | 4 stars | 5 stars |
|-------------+---------+---------+---------+---------|
| fiction     |       0 |      21 |      22 |       4 |
| non-fiction |       0 |       1 |       3 |       2 |

2024 goals

I’d like to maintain a regular reading practice. I think this means having a similar number of pages read this year.

I have a massive stack of books at home that I haven’t read through yet, I’d like to whittle that down some.

Reading in 2022

At the beginning of every year, I look back at my records and reflect on the books I read the previous year.

Previous years: 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021.

I’ve continued to keep track of my reading using Goodreads. My profile has nearly the full list of the books I’ve read since 2010.

This year I did a poor job of keeping Goodreads updated. I had somewhat stopped updating Goodreads, or at least caring if I did it accurately, because I thought they were killing the ability to export your data. Luckily, that feature hasn’t been removed so I’m going to continue using the service.

2022 goals

Last year I wrote:

I used to be pretty good at capturing some thoughts upon completion of a book. I haven’t been doing a great job of that. I’d like to do better this year.

We’ll see what that entails but it might take the form of having more discipline around sending out some thoughts in the newsletter.

I accomplished absolutely nothing related to the goals I wrote at the beginning of 2022.

I didn’t write down thoughts on books closer to when I finished them. I didn’t keep my Goodreads data updated. I didn’t send out updates to my newsletter.

Highlights

Below are some highlights from 2022. The titles link to Goodreads.

I didn’t write many reviews on Goodreads this year and did not write detailed reviews in this article. I’d encourage you to click the links and read reviews on Goodreads.

Five-star books

Liberation Day: Stories by George Saunders

I love George Saunders' writing and especially enjoy his short stories. This collection delivered.

The Lathe of Heaven by Ursula K. Le Guin

A science fiction classic that deserves to be read.

Four Thousand Weeks: Time Management for Mortals by Oliver Burkeman

Gist of the book: Life is short and you won’t be able to accomplish everything you desire.

I enjoyed reading this and would benefit from reading it (or at least the Goodread’s reviews) again. This review on Goodreads is excellent.

Breath: The New Science of a Lost Art by James Nestor

I found this book fascinating and it made me interested in breathing. It was a nice mix of self-experimentation and reporting on studies. The history of the shape of our faces and how it affects breathing really hooked me into this book.

I enjoyed the book enough that I also listened to a significant portion of it while on a road trip so my partner could also consume it.

Other Highlights

Mount Chicago: A Novel by Adam Levin

This probably could have been rated five stars. It is a ridiculous and long novel that rewards the reader for paying attention.

Probably the highlights for me were the chapters from the perspective of a pet.

I thought a couple sections dragged a bit and are what held me back from five stars.

Novelist as a Vocation by Haruki Murakami

To say I like Murakami’s writing would be an understatement, so reading a collection of essays by him about his writing was a pleasure. I learned a bunch about Murakami and his journey to becoming an internationally renowned author. At least as presented by Murakami, aspects of his life seem as surreal as some of his books.

This didn’t earn five stars because I found myself just not caring about some of the topics. But overall, solid book, especially for someone that enjoys Murakami and enjoys reading about writing.

Octavia Butler’s Patternmaster series.

Octavia Butler is one of my favorite authors and I devoured the first three books in this series. I enjoyed the first book, Wild Seed, the best.

I didn’t realize until writing this up that there is a fourth book in the series and I’ve immediately queued up reading it.

Stats

Compared to my usual number of books and pages read, this was a low reading year. I read 35 books in 2022.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| Year | # of Pages | # of Books |
|------+------------+------------|
| 2022 |      10127 |         35 |
| 2021 |      19564 |         57 |
| 2020 |      12093 |         43 |
| 2019 |      15994 |         42 |
| 2018 |      13538 |         36 |
| 2017 |      18317 |         48 |
| 2016 |      22790 |         59 |
| 2015 |      21689 |         51 |
| 2014 |      24340 |         71 |
| 2013 |      19815 |         60 |
| 2012 |      14208 |         44 |
| 2011 |       9179 |         19 |
| 2010 |      14667 |         46 |

Here is a breakdown of books finished by month. There were even a couple months where I allegedly didn’t finish a single book, though I wonder if that is actually true or of it is a data issue.

Book and pages count by month

Electronic books continue to be the dominant format. Audio book could be 0.5 for how much I listened to Breath while on a road trip.

1
2
3
4
5
6
|           | 2022 | 2021 | 2020 | 2019 | 2018 | 2017 | 2016 | 2015 |
|-----------+------+------+------+------+------+------+------+------|
| audiobook |    0 |    0 |    1 |    0 |    0 |    0 |    0 |    0 |
| ebook     |   34 |   56 |   41 |   43 |   37 |   37 |   56 |   47 |
| hardcover |    1 |    0 |    0 |    1 |    1 |    7 |    0 |    1 |
| paperback |    0 |    1 |    1 |    7 |    5 |    5 |    3 |    3 |

Fiction dominated this year.

1
2
3
4
5
|                           | 2022 |   2021 |   2020 |   2019 |   2018 |
|---------------------------+------+--------+--------+--------+--------|
| fiction                   |   28 |     46 |     26 |     28 |     29 |
| non-fiction               |    7 |     11 |     17 |     23 |     14 |
| fiction:non-fiction ratio |  4:1 | 4.18:1 | 1.53:1 | 1.22:1 | 2.07:1 |

Here is the star rating distribution.

1
2
3
4
|             | 2 stars | 3 stars | 4 stars | 5 stars |
|-------------+---------+---------+---------+---------|
| fiction     |       2 |      10 |      14 |       2 |
| non-fiction |       0 |       0 |       5 |       2 |

2023 goals

I’d like to do a better job of keeping track of my reading. This should be pretty easy to do.

I don’t feel too bad about the reduction in reading but I’d like to read more this year. Some of my reading time has been replaced with worthwhile endeavors but not always.

Reading rejuvenates me. I need to keep it a regular part of my life.

Have any book recommendations? Please shoot me an email or leave a comment.

Bookmarklets on mobile are useful

Bookmarklets, little snippets of JavaScript that you keep around as a bookmark, are useful. They let you execute some JavaScript to perform almost any action you want on a website.

Some bookmarklets I use on my desktop browser include:

  1. A collection of bookmarklets that let you change the playback speed of most embedded videos.
  2. A bookmarklet to manipulate the URL of the page you’re visiting.
  3. A bookmarklet to save the current page’s URL to pinboard.in.

For years, I thought I was restricted to only using bookmarklets in my desktop web browser. I hadn’t effectively used mobile bookmarks before and thought that clicking them would be a huge pain.

It turns out, I was wrong! I recently learned that if you start typing a bookmark’s title into your mobile browser’s location bar, it will let you select the bookmark. This means you can easily execute a bookmarklet just by starting to type its name and clicking it when it appears. This “search for bookmark in location bar” technique works with at least Google Chrome and Brave running in Android.

Below are the two bookmarklets I use regularly on my phone. They exist to bypass paywalls.

This one prepends http://archive.is/ to the current URL:

1
javascript:(function() {window.location="http://archive.is/"+window.location.toString();}())

This one changes theatlantic.com to theatlantic.com. (though it no longer gets around their paywall):

1
javascript:(function() {window.location=window.location.href.replace(/theatlantic.com/, 'theatlantic.com.');}())

To get them onto my phone, I added them a bookmarks on my laptop’s Chrome and synced them to my mobile phone. Once in my mobile Chrome, I edited the bookmark in mobile Chrome, copied the code, and pasted it into a bookmark in Brave.

I type three characters into my mobile browser’s location bar before I can select either of these bookmarklets. That is quicker than editing the URLs by hand and has improved the experience of reading articles on my phone.

Reading in 2021

At the beginning of every year, I reflect on books I’ve read in the previous year. I take a look at my records, fix errors, and think about reading goals for the upcoming year.

Here are links to my previous end-of-year reflections: 2013, 2014, 2015, 2016, 2017, 2018, 2019, and 2020.

I’ve continued to keep track of my reading using Goodreads. My profile has nearly the full list of the books I’ve read since 2010. Here is my 2021.

2021 Goals

Last year I wrote:

I have quite a few unread books sitting on my virtual and physical bookshelf. This feels like setting a really low-bar but this year I’d like to read some of these unread-but-owned books.

I’m also planning on reading at least one book on writing and one book on climbing. This goal is almost a subset of the above goal as I have books on both these topics sitting on my shelf.

Did I achieve those goals? No.

Looking through my list of read books, I think only one of those a book I owned at the beginning of 2021. I did not read some already owned books; I read a single already owned book.

I did read a book on writing, George Saunders' A Swim in a Pond in the Rain, and multiple climbing books, Rock Climbing Technique by John Kettle and Rock Climbing in Kentucky’s Red River Gorge by James Maples.

Early in 2020, at the request of some readers of this site, I started a mailing list. During 2020 I used this newsletter as a way to notify subscribers of new articles posted to this website and write up short blurbs on books I had finished. Except for a single email, I also didn’t send out any updates to the newsletter.

This is partially because I didn’t write many articles last year. I generally try to write about one article a month but I did not do that in 2021. We’ll see if I pick back up this habit in 2022.

Highlights

Below are some highlights from 2021. The titles link to Goodreads.

I didn’t write many reviews on Goodreads this year and did not write detailed reviews in this article. I’d encourage you to click the links and read reviews on Goodreads.

Five-star books

Dune by Frank Herbert

This was my third time reading Dune. I read it in preparation for seeing the 2021 film.

I’m a huge fan of Dune but parts of it definitely haven’t aged well. I think the story manages to be complex and have plenty of movement but somehow isn’t overwhelming. I thought the film did a pretty good job of capturing that.

The first time I read Dune, I also made the very first Kindle Dune Dictionary. If you are reading on a Kindle and haven’t read the book before, I’d recommend purchasing the dictionary. I didn’t find it useful on my third time through the book but it made my first read through better.

Memory by Lois McMaster Bujold

In August and September, I pretty much devoured the entire Vorkosigan Saga. I read all these books pretty much back to back and can barely distinguish them.

I’d recommend the series. It was a fun series and there are quite a few books in it. For some reason, this book stood out and is the only one I gave five stars.

A Memory Called Empire and A Desolation Called Peace by Arkady Martine

These are two incredible books. Arkady Martine built a great world. Politics, aliens, and poetry all show up in these books and it is great.

Go read some reviews (perhaps this and this) and then go start this series.

The Overstory by Richard Powers

The book starts with what feels like a collection of short stories and grows into a story of struggle, triumph, and failure. I can’t point to what made this book stand out to me but a I really enjoyed it. It might have had some moments that dragged a bit but I still loved it.

This Is How You Lose the Time War by Amal El-Mohtar

I went into this book knowing nothing about it and found it beautiful. The writing is lyrical and the way the story is told worked really well.

The book isn’t that long and I absolutely devoured it.

Zikora by Chimamanda Ngozi Adichie

This is a 35 page short story. Go spend the short time it takes to read it. And then go read the rest of Adichie’s writing.

A Swim in a Pond in the Rain: In Which Four Russians Give a Master Class on Writing, Reading, and Life by George Saunders

This book is fantastic. Generally, the format of the book is that you read a short story and then read George Saunders' thoughts about that story.

This book slightly changed how I think about stories.

Dept. of Speculation by Jenny Offill

This is a relatively short, beautiful book. I experienced a full range of emotions while reading it.

Think Again: The Power of Knowing What You Don’t Know by Adam M. Grant

A solid book on the benefits of rethinking your positions. A very short summary is that it is good to update your beliefs and be curious. There is little benefit to being wrong longer. Learn how to rejoice in correcting your beliefs and embrace updating your viewpoints.

There is a some overlap of concepts in this book and the book Moral Tribes. If you are also well-versed in cognitive biases, parts of this book will be a repeat.

The DevOps Handbook: How to Create World-Class Agility, Reliability, and Security in Technology Organizations by Gene Kim

I think a second edition came out immediately after I finished reading the first edition. Hopefully the second edition has high quality updates as the original edition of this book is pretty solid. I’ve lived the vision promoted by this book and it is a good place to be.

Rock Climbing Technique: The Practical Guide to Movement Mastery by John Kettle

A short book full of specific drills that are intended to improve your skill in climbing. This book isn’t about improving your strength, flexibility, or endurance. It is all about getting better at movement and paying attention to your movement patterns.

I’ve taken some of these drills and incorporated them into my climbing practice. I plan on digging back into this book and incorporating more of them.

Drug Use for Grown-Ups: Chasing Liberty in the Land of Fear by Carl L. Hart

This book is a mix of the author’s personal experience, policy, and science and makes the argument that drugs should be legal. Probably worth reading if you have any sort of reaction to that last sentence.

Reading some reviews on Goodreads gives a fairly balanced view of what this book is about. Even if I personally gave this book five stars, I find myself agreeing with a wide range of reviews by others.

Other Highlights

A Psalm for the Wild-Built by Becky Chambers

A couple other reviews (one, two) described this book as comforting science fiction. I think that is a great description.

The world feels cozy. It is full of generally nice folks going about their lives and interacting with each other over tea. This main character in this book is non-binary and you get to hang out with them as they live their life. It feels like a nice place to be with reasonable folks and respect between humans, nature, and robots.

It is a relaxing read that feels like a gentle fable that muses on life and what it means to exist.

Rock Climbing in Kentucky’s Red River Gorge by James Maples

This book has a very narrow audience. If you have heard about the Red River Gorge, rock climb, and are interested in the history of the area you should read this book.

I learned a lot about one of my favorite places to rock climb.

Crossroads by Jonathan Franzen

There is a really good chance I should have given this book five stars. It was great. This book tells the story of the Hildebrandt family through interweaving perspectives of the family’s members. The characters are complex and the perspectives are interesting. I hope Franzen can continue to deliver this level of story in the sequels.

This goodreads review does a great job of selling the book.

The Art of Gathering by Priya Parker

This book does a wonderful job of describing what makes a gathering great. It was a bit hopeful to read this book early in 2021.

All Systems Red by Martha Wells

A fun novella told from the perspective of an android.

Klara and the Sun by Kazuo Ishiguro

This book was beautiful. It manages to feel slightly off and this is completely appropriate given the narrator. This leads to some really amusing bits of writing.

Stats

I thought I had read less this year than I had in recent years but I was wrong. I read 57 books and 19,564 pages in 2021.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
| Year | # of Pages | # of Books |
|------+------------+------------|
| 2021 |      19564 |         57 |
| 2020 |      12093 |         43 |
| 2019 |      15994 |         42 |
| 2018 |      13538 |         36 |
| 2017 |      18317 |         48 |
| 2016 |      22790 |         59 |
| 2015 |      21689 |         51 |
| 2014 |      24340 |         71 |
| 2013 |      19815 |         60 |
| 2012 |      14208 |         44 |
| 2011 |       9179 |         19 |
| 2010 |      14667 |         46 |

Here is a breakdown of books finished by month. I can tell from looking at August and September that I started and finished the Vorkosigan series during those months.

Book and pages count by month

Electronic books continue to be the dominant format.

1
2
3
4
5
6
|           | 2021 | 2020 | 2019 | 2018 | 2017 | 2016 | 2015 |
|-----------+------+------+------+------+------+------+------|
| audiobook |    0 |    1 |    0 |    0 |    0 |    0 |    0 |
| ebook     |   56 |   41 |   43 |   37 |   37 |   56 |   47 |
| hardcover |    0 |    0 |    1 |    1 |    7 |    0 |    1 |
| paperback |    1 |    1 |    7 |    5 |    5 |    3 |    3 |

Fiction dominated this year.

1
2
3
4
5
|                           |   2021 |   2020 |   2019 |   2018 |
|---------------------------+--------+--------+--------+--------|
| fiction                   |     46 |     26 |     28 |     29 |
| non-fiction               |     11 |     17 |     23 |     14 |
| fiction:non-fiction ratio | 4.18:1 | 1.53:1 | 1.22:1 | 2.07:1 |

Here is the star rating distribution.

1
2
3
4
|             | 2 stars | 3 stars | 4 stars | 5 stars |
|-------------+---------+---------+---------+---------|
| fiction     |       2 |      17 |      19 |       8 |
| non-fiction |       1 |       2 |       3 |       5 |

2022 goals

I used to be pretty good at capturing some thoughts upon completion of a book. I haven’t been doing a great job of that. I’d like to do better this year.

We’ll see what that entails but it might take the form of having more discipline around sending out some thoughts in the newsletter.

Have any book recommendations? Please shoot me an email or leave a comment.

Tests are living documentation

Tests can serve many purposes.

You might write tests as a way of driving the design of your software. Other tests might be written in response to a discovered bug and, if written first, those tests you know when you’ve fixed the bug and act as guardrails preventing the reintroduction of that bug. Tests can also be used to confirm you haven’t changed behavior while refactoring.

Tests can also be used as documentation. Unlike non-executable documentation, tests will always match the implementation’s behavior.

An example in a comment or other documentation deserves to be in a test. Take the following sketch of a Clojure function:

1
2
3
4
5
6
7
8
9
(defn confobulate
  "Takes a string and transforms it to the confobulated form. Examples:
  - \"alice\" -> \"EcilA\"
  - \"//yolo1\" -> \"//oneOloY\"
  "
  [s]
  (-> s
      ;; insert some work here, not going to implement this
      ))

The docstring has examples in it to aid humans in understanding its behavior. These examples are useful! But they stop being useful and start being dangerous when they stop being accurate.

We can use unit tests to keep examples like this correct. You can write comments near the assertions letting future readers know about the documentation that needs to be updated if behavior changes.

1
2
3
4
5
6
7
(deftest confobulate-should-ignore-slashes
  ;; If this assertion changes the docstring needs to be updated
  (is (= "//oneOloY" (confobulate "//yolo1"))))

(deftest confobulate-reverses-and-capitalizes
  ;; If this assertion changes the docstring needs to be updated
  (is (= "alice" (confobulate "EcilA"))))

Any example in a comment or other non-executable documentation should be an assertion in a unit test. You’ve already taken the time to document the behavior; take the time to figure out how to document it in a way that will fail if the behavior changes.

Improve your tests by picking better constants

The constants you use in unit tests matter. Like test and variable names, they can improve the readability of your code and make it easier to understand test failures.

Imagine the following.

A new developer joins your team and asks a question about how the code resolves config values. You are unsure of the details so you pair up with the new teammate to dig into the code.

You know the codebase uses a relatively simple key-value pair concept for configuration. It reads keys and values from a known files and, based on some rules, either ignores or overrides values when keys are duplicated across files.

config-value is the function that looks up the value for a particular configuration key, represented as a string. This function takes three arguments: an in-memory representation of the configuration files, the key to lookup, and the mode to operate in. You know the mode is important in influencing how config resolution works but you don’t remember the details.

Luckily for you and your pair, the codebase has plenty of unit tests. The two of you dive in and look at some tests, hoping to understand how config resolution works.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(def config {"scratch.conf" {"a" "1"}

             "development.conf" {"a" "2"
                                 "b" "2"}

             "application.conf" {"a" "3"
                                 "b" "3"
                                 "c" "3"}})

(deftest handles-overrides-in-dev-mode
  (is (= "1" (config-value config "a" :dev)))
  (is (= "2" (config-value config "b" :dev)))
  (is (= "3" (config-value config "c" :dev))))

(deftest handles-overrides-in-prod-mode
  (is (= "3" (config-value config "a" :prod)))
  (is (= "3" (config-value config "b" :prod)))
  (is (= "3" (config-value config "c" :prod))))

It is great that these tests exist but they could be clearer. They aren’t terrible but you have to work a bit understand what is happening.

When reading (= "2" (config-value config "b" :dev)), what does "2" represent? What does "b" mean? You have to either keep the value of config in your brain or keep glancing up in the file to recall what it is.

This isn’t great. This adds cognitive overhead that doesn’t need to be there.

There are a few ways these tests could be improved One way is through using better constants. Let’s do a quick rewrite.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(def config {"scratch.conf" {"in dev+app+scratch" "from scratch"}

             "development.conf" {"in dev+app+scratch" "from development"
                                 "in dev+app" "from development"}

             "application.conf" {"in dev+app+scratch" "from application"
                                 "in dev+app" "from application"
                                 "in app" "from application"}})

(deftest handles-overrides-in-dev-mode
  (is (= "from scratch" (config-value config "in dev+app+scratch" :dev)))
  (is (= "from development" (config-value config "in dev+app" :dev)))
  (is (= "from application" (config-value config "in app" :dev))))

(deftest handles-overrides-in-prod-mode
  (is (= "from application" (config-value config "in dev+app+scratch" :prod)))
  (is (= "from application" (config-value config "in dev+app" :prod)))
  (is (= "from application" (config-value config "in app" :prod))))

These are the same tests but with different constants. Those constants make a huge difference. This change has made the tests more legible. You no longer need to remember the value of config or keep glancing up at it to understand the assertions in a test.

You can read (= "from development" (config-value config "in dev+app" :dev)) and have a pretty solid idea that you are looking up a key found in both development.conf and application.conf and while in :dev mode expect the value from development.conf.

The new constants provide clues about what the test expects. You can read and understand the assertions without keeping much state in your head.

This increases the legibility of the tests and is useful when a test fails. Which of the following is clearer?

1
2
3
4
5
FAIL in (handles-overrides-in-dev-mode)
expected: "2"
  actual: "3"
    diff: - "2"
          + "3"
1
2
3
4
5
FAIL in (handles-overrides-in-dev-mode)
expected: "from development"
  actual: "from application"
    diff: - "from development"
          + "from application"

The second one is clearer. You can read it and form a hypothesis about what might be broken.

Well chosen constants reduce the state a person needs to keep in their head. This makes tests easier to understand. Good constants also make test failures easier to understand. Just like good variable names, good constants increase the readability of our tests.

It is well worth placing some extra thought into the constants found in your tests.

Reading in 2020

At the beginning of every year I reflect on my reading from the previous year. I take a look at my records, fix errors, and think about reading goals for the upcoming year.

Here are links to my previous end-of-year reflections: 2013, 2014, 2015, 2016, 2017, 2018, and 2019.

I’ve continued to keep track of my reading using Goodreads. My profile has nearly the full list of the books I’ve read since 2010. Here is my 2020.

2020 Goals

Last year I wrote:

I was encouraged by how many non-fiction books I read this year and how many of them ended up earning a five star rating. I’d like to continue that trend of reading high-quality non-fiction books.

I’ve also been reading a lot of books but I haven’t always been the best at trying to consciously apply the lessons from those books. I’m going to try to improve that this year.

Those are pretty fuzzy goals but I’m alright with that.

I’ll come back at the end of this article and reflect on if I hit it or not.

Highlights

Here are my five star books from 2020. The titles are affiliate links to Amazon. If you click one and make a purchase I get a small kickback.

If I wrote a review on Goodreads then the my review link will take you there. In the last couple of years, I’ve been writing fewer reviews on Goodreads than in the past so many books do not have a review there.

If you’re missing these reviews, I have started sending out an email every month or two and it frequently includes small reviews of what I’ve read since the previous email. You can subscribe to that here.

Here are the 2020 five star books:

The Hard Truth: Simple Ways to Become a Better Climber by Kris Hampton

This is an excellent dose of wisdom about climbing and improving your performance. It does this through suggestions of how to change your mental relationship with climbing. Improving is about putting in the work, reflecting, and trying hard.

The Body Keeps the Score: Brain, Mind, and Body in the Healing of Trauma by Bessel A. van der Kolk

I really enjoyed this book and made hundreds of highlights while reading it on my Kindle. I’d suggest reading reviews on Goodreads and seeing if it is something that would be interesting for you.

Come as You Are: The Surprising New Science that Will Transform Your Sex Life by Emily Nagoski

This was a good book that, unlike what the subtitle claims, did not transform my sex life. But I didn’t go into it expecting that. I’m not the main audience for this book but still got some value from it. I particularly enjoyed the parts that talked about stress, responses to stress, and emotional systems.

How to Change Your Mind by Michael Pollan

This book is about psychedelics, such as LSD and psilocybin. It combines the history of these substances, old and new research being done with them, and sort of a travelogue of Michael Pollan’s growing experience with these substances.

Why I’m No Longer Talking to White People About Race by Reni Eddo-Lodge

I’ll just link to a friend’s review of this book.

The author Chimamanda Ngozi Adichie

I devoured all of her writing this year, both fiction and non-fiction, and the highlights are above. None of her writing earned less than four stars.

Between starting and finishing writing this article, I learned she published a new short story, Zikora, and immediately read it. It was pretty great.

One of the reasons I enjoy reading fiction is that it provides a window into the experiences of others. Chimamanda Ngozi Adichie’s writing does exactly this and does it with beautiful prose and compelling stories.

The Diamond Age by Neal Stephenson

This was a reread of the first Neal Stephenson book I read. I wanted to reread this book as I had been recommending it as a relatively short introduction to Neal Stephenson’s writing but I was second guessing how much I enjoyed it.

I was wrong to second guess that. This story was still great the second time through. This book covers so much and feels prescient despite being read 25 years after it was originally published (February 1995).

Parable of the Sower and Parable of the Talents by Octavia E. Butler

Octavia Butler builds a new religion in this series and, honestly, that religion is tempting. These are fantastic science fiction reads that explore human connections and what we could be as a species.

Other notable reads

These are books that for some reason I didn’t give five stars but I still think they are worth recommending. All links below are to Goodreads.

Piranesi by Susanna Clark

This book was weird and I enjoyed it. You follow a character that lives in a weird, infinite building made of corridors lined with statues.

Why We’re Polarized by Ezra Klein

This covers the American political system and how we got to our modern form with deeply polarized parties. I thought it was a pretty interesting read.

The Will to Change: Men, Masculinity, and Love by bell hooks

I read this at the very beginning of 2020 and think that everyone should read it. I highlighted a ton of passages and plan on going back and reviewing those passages.

You’re Not Listening: What You’re Missing and Why It Matters by Kate Murphy

I read this at the very beginning of 2020 and highlighted a ton of passages. The book is about listening and how we do a bad job at it. It includes suggestions about how to get better.

Pair this book with I hear you, a book I read last year, and you’ll have the tools to become a better listener.

Diaspora by Greg Egan

This was a stupendous science fiction read. It takes you on a wild journey into a far future where sentient beings can exist in software.

This was very close to receiving five stars but I kept getting bogged down in some of the explanations. I know this is why some folks enjoy hard science fiction but that isn’t why I’m reading these stories. This book delivers an interesting, complex, and very speculative far future. If the blurb sounds interesting to you and you’re willing to put up some with advanced theoretical (real? fake? I don’t know) physics then pick this book up.

Stats

I read 43 books and 12,093 pages in 2020. The data also doesn’t capture three books that I’ve started but have yet to finish.

1
2
3
4
5
6
7
8
9
10
11
12
13
| Year | # of Pages | # of Books |
|------+------------+------------|
| 2020 |      12093 |         43 |
| 2019 |      15994 |         42 |
| 2018 |      13538 |         36 |
| 2017 |      18317 |         48 |
| 2016 |      22790 |         59 |
| 2015 |      21689 |         51 |
| 2014 |      24340 |         71 |
| 2013 |      19815 |         60 |
| 2012 |      14208 |         44 |
| 2011 |       9179 |         19 |
| 2010 |      14667 |         46 |

Last year marks a decade of me tracking my reading and it was the second lowest page count in that decade. For many reasons 2020 was an unforgettable year and one where I spent a lot of time at home.

I would have thought that would have lead to a large number of pages read but I think much of my time ended up being taken up by non-book reading activities. For better or worse (probably worse), a lot of my time was spent reading articles about the on-going global pandemic, the USA election, and the other non-stop news cycle of 2020. Between that and the increase in newsletters and podcasts I’m consuming, I’m not that surprised my book reading has taken a hit.

Here is a breakdown of books finished by month.

Book and pages count by month

This graph tells a slightly different story than the one I presented above. I did not finish many pages in January through March, the pre-pandemic time period in the United States.

Those months I was extremely dedicated to training for climbing and was starting a new relationship. I’m very happy both of those took up my non-working hours during those months.

I was still commuting to an office from January till mid-March and would have expected more pages finished on the train. I’ll blame podcasts for that as this year I did start listening to those while commuting, since I can enjoy those while walking to and from the train as well.

The number of books read in February is high because I read a short story collection where each story was published individually on Amazon.

Unsurprisingly, electronic books continue to be the dominant format.

1
2
3
4
5
6
|           | 2020 | 2019 | 2018 | 2017 | 2016 | 2015 |
|-----------+------+------+------+------+------+------|
| audiobook |    1 |    0 |    0 |    0 |    0 |    0 |
| ebook     |   41 |   43 |   37 |   37 |   56 |   47 |
| hardcover |    0 |    1 |    1 |    7 |    0 |    1 |
| paperback |    1 |    7 |    5 |    5 |    3 |    3 |

Below is the breakdown of fiction vs non-fiction books. Fiction started to regain its dominance after having non-fiction catch up in 2019.

1
2
3
4
5
|                           |   2020 |   2019 |   2018 |
|---------------------------+--------+--------+--------|
| fiction                   |     26 |     28 |     29 |
| non-fiction               |     17 |     23 |     14 |
| fiction:non-fiction ratio | 1.53:1 | 1.22:1 | 2.07:1 |

Here is the star rating distribution.

1
2
3
4
|             | 2 stars | 3 stars | 4 stars | 5 stars |
|-------------+---------+---------+---------+---------|
| fiction     |       1 |       8 |      12 |       5 |
| non-fiction |       0 |       4 |       7 |       6 |

Did I hit my 2020 goals?

I succeeded in reading a solid number of non-fiction books that earned a high rating. I read fewer non-fiction books than fiction but managed to have more 5 star ratings. I’m going to count this as successfully hitting the non-fiction part of my 2020 goal.

Did I get better at applying the lessons from books? Not at all and I barely even tried to do so. Definite failure here.

2021 goals

I have quite a few unread books sitting on my virtual and physical bookshelf. This feels like setting a really low-bar but this year I’d like to read some of these unread-but-owned books.

I’m also planning on reading at least one book on writing and one book on climbing. This goal is almost a subset of the above goal as I have books on both these topics sitting on my shelf.

It is interesting to have been collecting this data for a decade now. I haven’t done much in the way around looking at multi-year trends but I think it might be interesting to do so.

If you have a book recommendation, feel free to reach out and contact me.

Speeding up Magit with the native-comp branch of Emacs

In my last article, Speeding up Magit, I showed how removing elements from Magit’s status buffer drastically reduces the time it takes to refresh this buffer when working in a large repository (from 4 seconds to around 0.348 seconds). In a comment on r/emacs, someone wondered if the native-comp feature of Emacs might improve the Magit status refresh time.

This reddit thread was the first time I had heard of the native-comp feature. This feature lives on the feature/native-comp branch of the Emacs repository and it compiles Elisp code into native code. Many users have reported noticeable speed improvements using it. The official development log and Emacs Wiki have more information about it.

I’ll provide more information about getting native-comp working on macOS after I answer the Magit speed question.

How did it change refresh times of the Magit status buffer?

The quick answer is that running Emacs with native-comp improved the refresh times of the Magit status buffer. Below is a table of the various times.

1
2
3
4
5
| Experiment                              | magit-status refresh time |
|-----------------------------------------+---------------------------|
| full magit-status with native-comp      | 3.152 seconds             |
| full magit-status without native-comp   | 4.003 seconds             |
| magit-status with many sections removed | 0.348 seconds             |

Using native-comp, we’ve cut off about 0.85 seconds. That is a pretty solid improvement. Even still, that isn’t fast enough for how often I use Magit so I’ll be sticking with my Magit setup with many sections removed.

As a caveat, the timing with native-comp also includes upgrading Emacs from 26.3 to 28.0.50 (so I could have native-comp) and Magit from 20201111.1436 to 20201212.929. As a result, the comparison to full magit-status without native-comp isn’t entirely fair as multiple variables have changed. The comparison to time with sections removed is fair as I’m still using that setup (but with native-comp) and the timing is pretty much the same.

Getting native-comp on macOS

To enable native-comp you need to build Emacs from source. I’ve done this before on Linux systems but this was the first time I’ve done this on macOS.

When browsing reddit, I found the build-emacs-for-macos project which has some helpful instructions for doing this. I followed the instructions from the readme and picked the latest known good commit from this issue (at the time I did this be907b0ba82c2a65e0468d50653cae8a7cf5f16b). I then updated my init.el based on instructions from in the build-emacs-for-macos project.

I haven’t had any issues since switching to this very new Emacs. I don’t have numbers to back this up but it does feel faster.

Recommendation

I’d recommend giving the native-comp feature of Emacs a shot. It wasn’t terribly challenging to get setup and it is nice to get a glimpse of what the future of Emacs might be. That future is a bit snappier.

Speeding up magit

Magit is a great Emacs tool and by far my favorite way of interacting with git repositories. I use Magit nearly every day.

Unfortunately, refreshing the magit-status buffer is sluggish when you are working in a large repository.

A few months ago, I became sick of waiting and investigated how to speed up refreshing the status buffer. After doing some research, I learned about the magit-refresh-verbose variable.

Setting magit-refresh-verbose to true causes Magit to print some very useful output to your *Messages* buffer. This output shows how many seconds each step of magit-status takes.

Here is the output for the large repo that caused me to look into this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Refreshing buffer ‘magit: example-repo’...
  magit-insert-error-header                          1e-06
  magit-insert-diff-filter-header                    2.3e-05
  magit-insert-head-branch-header                    0.026227
  magit-insert-upstream-branch-header                0.014285
  magit-insert-push-branch-header                    0.005662
  magit-insert-tags-header                           1.7119309999999999
  magit-insert-status-headers                        1.767466
  magit-insert-merge-log                             0.005947
  magit-insert-rebase-sequence                       0.000115
  magit-insert-am-sequence                           5.1e-05
  magit-insert-sequencer-sequence                    0.000105
  magit-insert-bisect-output                         5.3e-05
  magit-insert-bisect-rest                           1.1e-05
  magit-insert-bisect-log                            1e-05
  magit-insert-untracked-files                       0.259485
  magit-insert-unstaged-changes                      0.031528
  magit-insert-staged-changes                        0.017763
  magit-insert-stashes                               0.028514
  magit-insert-unpushed-to-pushremote                0.911193
  magit-insert-unpushed-to-upstream-or-recent        0.497709
  magit-insert-unpulled-from-pushremote              7.2e-05
  magit-insert-unpulled-from-upstream                0.446168
Refreshing buffer ‘magit: example-repo’...done (4.003s)

The total time is found in the last line and we can see it took four seconds. Four seconds is an incredibly long time to wait before interacting with Magit.

You can change how much work Magit does by removing functions from the magit-status-sections-hook with remove-hook. I looked at the timings and and tried removing anything I decided was slow and something I didn’t think I’d miss. For me, that list includes magit-insert-tags-header, magit-insert-status-headers, magit-insert-unpushed-to-pushremote, magit-insert-unpushed-to-upstream-or-recent, and magit-insert-unpulled-from-upstream. I also removed magit-insert-unpulled-from-pushremote.

You remove a function from a hook by adding elisp similar to (remove-hook 'magit-status-sections-hook 'magit-insert-tags-header) to your Emacs configuration.

I use use-package to configure mine and below is what my magit section looks like.

Lines 20-25 remove the hooks. I also hard-code magit-git-executable to be the full path of the git executable on line 5 because folks said this made a difference on macOS.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
(use-package magit
  :ensure t
  :bind ("C-c g" . magit-status)
  :custom
  (magit-git-executable "/usr/local/bin/git")
  :init
  (use-package with-editor :ensure t)

  ;; Have magit-status go full screen and quit to previous
  ;; configuration.  Taken from
  ;; http://whattheemacsd.com/setup-magit.el-01.html#comment-748135498
  ;; and http://irreal.org/blog/?p=2253
  (defadvice magit-status (around magit-fullscreen activate)
    (window-configuration-to-register :magit-fullscreen)
    ad-do-it
    (delete-other-windows))
  (defadvice magit-quit-window (after magit-restore-screen activate)
    (jump-to-register :magit-fullscreen))
  :config
  (remove-hook 'magit-status-sections-hook 'magit-insert-tags-header)
  (remove-hook 'magit-status-sections-hook 'magit-insert-status-headers)
  (remove-hook 'magit-status-sections-hook 'magit-insert-unpushed-to-pushremote)
  (remove-hook 'magit-status-sections-hook 'magit-insert-unpulled-from-pushremote)
  (remove-hook 'magit-status-sections-hook 'magit-insert-unpulled-from-upstream)
  (remove-hook 'magit-status-sections-hook 'magit-insert-unpushed-to-upstream-or-recent))

After this change, my magit-status buffer refreshes in under half a second.

1
2
3
4
5
6
7
8
9
10
11
12
13
Refreshing buffer magit: example-repo...
  magit-insert-merge-log                             0.005771
  magit-insert-rebase-sequence                       0.000118
  magit-insert-am-sequence                           5.3e-05
  magit-insert-sequencer-sequence                    0.0001
  magit-insert-bisect-output                         5.5e-05
  magit-insert-bisect-rest                           1.1e-05
  magit-insert-bisect-log                            1.1e-05
  magit-insert-untracked-files                       0.247723
  magit-insert-unstaged-changes                      0.024989
  magit-insert-staged-changes                        0.018397
  magit-insert-stashes                               0.026055
Refreshing buffer magit: example-repo...done (0.348s)

What did I lose from the magit-status buffer as a result of these changes? Here is screenshot of the original buffer.

Buffer before changes

And here is the buffer after.

Buffer after changes

The difference is drastic1. And so is the speed difference.

The increased speed is worth losing the additional information. I interact with git very often and much prefer using Magit to do so. Before these changes, I found myself regressing to using git at the command line and I don’t find that to be nearly as enjoyable. Since I’ve made these changes, I’m back to doing 99% of my git interactions through Magit.

Don’t settle for slow interactions with your computer. Aggressively shorten your feedback cycles and you’ll change how you interact with the machine.

Versions used when writing this article

This post was written with Magit version 20201111.1436 and Emacs 26.3 on macOS 10.15.7. I’ve been using these changes for a few months but do not remember or have a record of what Magit version I was using at the time I originally made these changes.

edit on 2020/12/15: I recently upgraded Emacs to tryout the native-comp work and can report this still works with with Emacs 28.0.50, Magit 20201212.929, and Git 2.29.2 running in macOS 11.0.1.

Warning: This reduces the information Magit shows you. The status buffer will be blank if you have no changes. I find this tradeoff to be worth it.


  1. The before image is even missing some sections that would have gone missing in the after shot since I didn’t want to put the effort.