My new year started with a really strange bug. I use a DateFormatter to show the current month to the user. So when I opened the app on January 02, 2021 I expected to see “Saturday, Jan 2, 2021”. But what I got was “Saturday, Jan 2, 2020”. Somehow my DateFormatter did not get the message that 2020 was finally over.
let isoDateFormatter = ISO8601DateFormatter() let january2 = isoDateFormatter.date(from:"2021-01-02T12:00:00+0000")! let fmt = DateFormatter() fmt.dateFormat = "EEEE, MMM d, YYYY" fmt.locale = Locale(identifier: "en_GB") fmt.string(from: january2) // gives you "Saturday, Jan 2, 2020"
So what happened? It took me quite a while to get my head around this bug. We all know that timezones can be tricky, but my DateFormatter did get the weekday, day and month correctly but the year was wrong. So it was not a matter of timezone confusion.
After fiddeling around a bit I replaced “YYYY” with “yyyy” and the bug was fixed.
fmt.dateFormat = "EEEE, MMM d, yyyy" fmt2.string(from: january2) // gives you "Saturday, Jan 2, 2021"
But why? Turns out that “YYYY” uses a week-based calendar for its calculations.
So what is a week-based calendar???
A week-based calendar uses calendar weeks for its calculations. The concept of a calendar week is pretty straight forward. A year usually has 52 weeks so you can identify any week in a year by its number (e.g. week 17 is usually the last week in April).
But when does the first calendar week start? January 1st? That would be easy. Unfortunately it is more complicated than that. I found this definition:
In Europe, the first calendar week of the year is the first week that contains four days of the new year.
For 2021 this means that the first calendar week starts on Monday, Jan 4, because the first 3 days in January are in the previous week. And that week only contains 3 days of the new year and is therefor considered the last calendar week of 2020.
So, in a week-based calendar the first three days of 2021 are still part of 2020. Crazy.
But that’s not the end of the craziness. You might have wondered about the “In Europe” part of the definition”. The thing is: In Europe the first day of a week is Monday. In other countries (like the US and Canada) it is Sunday and in the Middle East it is Saturday. To add even more confusion: Some countries (like the US and Canada) choose to make things easier and start the first calendar week on January 1, no matter which day of the week this happens to be.
Here is a comparison between calendars in the USA and the UK (have a look at the week numbers and the first day of the week):
So the same day can be in different calendar weeks, depending on your locale:
let fmtGB = DateFormatter() fmtGB.dateFormat = "YYYY-ww-D" fmtGB.locale = Locale(identifier: "en_GB") let fmtUS = DateFormatter() fmtUS.dateFormat = "YYYY-ww-D" fmtUS.locale = Locale(identifier: "en_US") fmtGB.string(from: january2) // gives you "2020-53-2" (day 2 in week 53 of 2020) fmtUS.string(from: january2) // gives you "2021-01-2" (day 2 in week 1 of 2021)
So in the future I will be really careful NOT to use “YYYY” in a DateFormatter. The worst thing about this bug is that it only occurs in the first days of a new year (or the last days of the old year). The rest of the year everything is working well. Quite a sneaky bug that is 😉