What year it it? (A tale of ISO week dates)

Happy (Gregorian) new year!

As I type, it’s 9am UTC, and 2021 has arrived in most of the world. Timezones mean the new year doesn’t arrive all at once: it starts in small Pacific islands like Samoa and Kiritimati, works its way from east to west, and eventually finishes back in the Pacific, on Baker Island. For about 24 hours, “what year is it?” depends on where you are.

This year, my computer decided to join in the fun. I have a text expansion macro that prints the current date, and when I ran it shortly after midnight, this was the output:

2020-01-01

Uh oh. Computer bug, or a terrifying 2020-esque Groundhog Day?

I run the macro with Keyboard Maestro, which types the following snippet:

%ICUDateTime%Y-MM-dd%

Some of you can already see the issue, but it was a mystery to me – if you’re still puzzled, keep reading. Those % symbols mean this is a Keyboard Maestro token: rather than typing the literal text, it types the date in the ICU format Y-MM-dd.

I tried updating to the latest version of Keyboard Maestro, and the output was the same. Unsurprising, but good to check.

I almost never work with ICU date formatting; I’m more used to the format used by strftime(3) and strptime(3) (which are in turn used by the Python datetime library). The format specifier looked correct to me, but clearly something was wrong.

The Keyboard Maestro docs link to the ICU date format instructions, and here I discovered my error. Although uppercase Y seems to work, it’s actually one of two symbols for the year:

A table with two rows. The first has symbol 'y' and is the 'year'; the second had symbol 'Y' and is the 'year of “Week of Year”'

The first year is the calendar year, and what I thought I was using.

The second, “Week of Year” refers to the ISO week date, a system that divides the year into numbered weeks. Each week starts on a Monday and runs for seven days, so a week is always in a single year and has a predictable length. For certain fiscal processes, those properties are more useful than the variable-length months of the calendar year.

Here’s how the ISO weeks look around December 2020:

A table showing a small December calendar and the week numbers down the left. Week Mon Tues Weds Thurs Fri Sat Sun 2020 W49 30 1 2 3 4 5 6 2020 W50 7 8 9 10 11 12 13 2020 W51 14 15 16 17 18 19 20 2020 W52 21 22 23 24 25 26 27 2020 W53 28 29 30 31 1 2 3 2021 W1 4 5 6 7 8 9 10

Notice that January 1, 2021 is in Week 53 of 2020, so the ISO week-calendar year is still 2020, and will be until next Monday. This is where my problem lies: I was using the wrong type of year in my ICU format specifier.

For almost all the year, the calendar year and the ISO week-calendar year are the same – it’s only in the first few days of the calendar year when they differ. I can’t remember when I wrote this macro, and it’s possible it’s been broken for multiple years. The code seemed to be working until today, even if it was by coincidence rather than because it was actually correct.

If this bug was in some sort of business software that was only used on workdays, it could last a very long time without being detected. The ISO week starts on a Monday, so it’s quite possible the first week of the new ISO year would always start before anybody ran the broken code.

This is the ICU format specifier for the calendar date:

yyyy-MM-dd => "2021-01-01"

This is the ICU format specifier for the ISO week-calendar date:

Y-w-e => "2020-53-5"

Both are valid and useful formats, but mixing the two year types is a mistake waiting to happen.

A datetime bug seems like some sort of omen for 2021. I hope it’s an auspicious one, and I hope this new year finds you all well and safe.

This post started as a thread on Twitter.