Last year, the first working day after Christmas was 29 December 2020. I began the day by merging a pull request. Automated tests checked the code change did not break anything, but to my surprise one of the tests failed.
The test creates a new API key and makes a couple of checks on it. It failed because the creation date displayed for the API key was a year later than the actual date. It should have been 2020 but it was 2021.
I created a new API key myself and looked at its creation date. It was a year ahead. The test had identified a real bug in our live application.
The merged pull request had nothing to do with API keys. It's unusual for working code to suddenly break.
Date and time formatting patterns
We were in the last few days of the year and the application already thought it was next year. This sounded familiar. I remembered a similar bug where Twitter thought it was already 2015 at the end of 2014.
The Twitter bug was caused by a date formatting mistake. Maybe the bug I'd just discovered was too.
Many programming languages support formatting patterns for dates and times. For example, yyyy-MM-dd HH:mm:ss
might produce 2020-12-29 10:00:00.
In this pattern, yyyy
means the year. In some languages, such as Java, the symbols are case-sensitive. While a lower-case yyyy
is the year, an upper-case YYYY
is the week year.
The current week year is usually the same as the current calendar year. But it may not be around New Year's Day. The new week year does not always start on 1 January. It starts at the beginning of the first week of the new year.
The international standard that deals with dates and times, ISO 8601, specifies how weeks work. ISO 8601 states that the first day of the week is Monday. The first week of the year is the week containing the first Thursday in January. Why Thursday? Because that week is the first to contain the majority of its days (at least 4) from January. The new week year starts at the beginning of that week.
I tracked down the problematic date formatting to some Java code:
DateTimeFormatter.ofPattern("dd MMM YYYY -
HH:mm").format(zonedDateTime)
Fixing it would be easy: just change the upper-case YYYY
to a lower-case yyyy
.
Localisation
But something did not make sense. 1 January 2021 was a Friday. According to ISO 8601, 29 December 2020 was not in the first week of 2021 but the last week of 2020.
Confused, I did some testing with JShell, a read-evaluate-print-loop (REPL) for Java.
jshell> import java.time.*;
jshell> import java.time.format.*;
jshell> var zonedDateTime =
ZonedDateTime.parse("2020-12-29T10:00Z");
zonedDateTime ==> 2020-12-29T10:00Z
jshell> DateTimeFormatter.ofPattern("dd MMM YYYY -
HH:mm").format(zonedDateTime);
$1 ==> "29 Dec 2020 - 10:00"
Now I was really confused. On my computer, formatting 29 December 2020 using the pattern resulted in the week year 2020. But in our live application, the exact same date and pattern resulted in the week year 2021.
I checked the documentation. The Javadoc for DateTimeFormatterBuilder defines the upper-case Y symbol: "append special localized WeekFields element for numeric week-based-year."
The word 'localized' is important. In Java, date and time formatters are locale-sensitive. For example, DateTimeFormatter.ofPattern("EEEE, d. MMMM", Locale.GERMAN)
might produce Dienstag, 29. Dezember, using the German words for the days of the week and the names of the months.
If a locale is not specified, the formatter will use the default locale.
So I went back to JShell to check the default locale of my computer.
jshell> Locale.getDefault();
$2 ==> en_GB
It was en_GB, which means English and the United Kingdom.
Our live application is deployed inside a Docker container. I checked the default locale there. It was en_US: English and the United States.
I began to wonder if the UK and the US have different conventions for week years. So I looked online and found the excellent timeanddate.com.
The timeanddate.com 2021 UK calendar says: "Week numbers: ISO 8601 (week starts Monday) - week 1 is the first week with Thursday."
The timeanddate.com 2021 US calendar says: "Week numbers: Week starts Sunday - week 1 is the one with January 1."
In the UK, week year 2021 started on Monday 4 January 2021. In the US, week year 2021 started on Sunday 27 December 2020. 29 December 2020 fell into a different week year in the 2 countries.
As we'd already fixed the issue by changing YYYY
(locale-specific) to yyyy
(same in every locale) as outlined earlier in this post, we didn't need to do anything else. But it was good to finally have sorted out what was an annoying bug.
What we learned
Dates and times are surprisingly complex. It's important to check what the different date and time formatting pattern symbols mean in your programming language. Be careful when performing a locale-sensitive operation using the default locale. It might not be what you expect.
You can see the pull request that fixed the bug here. Have you ever dealt with something similar? Let us know in the comments below.