As I’ve reported previously, some time in the last few weeks, it appears that Big Sur has changed how it formats time, causing strange bugs in several of my apps which work with time and the log. A couple of days ago, I released a set of updates to all six which could be affected, which detected whether that Mac was displaying time in 12- or 24-hour format, as problems only start when using 12-hour format.
Since then, I’ve had a chance to dig deeper, and believe that I understand the cause: macOS has recently started to format times differently. You’ll be unsurprised to learn that this is essentially undocumented, and as far as I can see doesn’t appear in any developer release notes for Big Sur. It could also affect other apps which had been working perfectly well prior to macOS 11.2.
My troubles with 12-hour clocks go back just over a year, when I started using a DatePicker to set times in Ulbow. As a result of extensive testing in Catalina, I ensured that the times read from Ulbow’s controls worked fine, regardless of whether that Mac was set to 12- or 24-hour format. As a user control, the DatePicker returns a Date, which includes the time; to format that for use in the
log show command which Ulbow and other apps use requires a DateFormatter.
For example, the required format for dates and times to be specified with the
log show command is expressed as
which might be rendered as
Each entry in the unified log bears a timestamp field, which uses a different format, expressed as
My first impressions from these bug reports were that the
log show command had started formatting the timestamp field in each log entry according to the setting in the Language & Region pane. When those timestamps were analysed by my apps, if the hours were set using a 12-hour clock, that would break the app. It turns out that I was wrong: inspecting JSON-format log extracts reveals no change in their contents.
The only alternative was that something had changed in the conversion from times set in the DatePicker control (and other places), code which I had worked on thoroughly in early 2020. For example, to convert a date read from the time setting, I set up a DateFormatter using a fixed format string, coupled with the correct time zone:
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
dateFormatter.timeZone = TimeZone.autoupdatingCurrent
then to get the date set as a string, I call
The next step was to observe what happened to this in 12- and 24-hour clock settings. That brought a surprise: when the clock is set to 24-hour format, a typical date string would be
as expected, but in 12-hour format that became
2021-04-05 8:03:15 pm
So macOS was ignoring my formatting, and doing something quite different: instead of the hour being set as two digits, it lost the leading zero, and the pm was being appended in spite of am/pm not being set in the format. No wonder those strings were breaking my apps!
Next I looked at controls over date and time formatting in the Language & Region pane, to see if those could help. When you set the Time format with 24-Hour Time unchecked, for 12-hour time, there are controls in the Advanced… settings of the pane.
Those aren’t at all helpful: as you can see, if the hour is set to the range 0-23, in a 12-hour clock that shows up as “7 am” not “7”. This pane also allows individual apps to use custom date and time settings, which appeared promising.
Only the choices for custom app settings don’t allow for control over time format as such, just the Region. I could therefore run an app in Chinese settings, but not using a 24-hour clock. I clearly needed to return to those formats used to establish how dates are converted into strings.
Formatting like yyyy-MM-dd HH:mm:ss isn’t defined by Apple, but conforms to Report TR35 from the Unicode Consortium, and is exhaustively defined here. That makes it clear that setting the hour using HH returns the hour in 24-hour format, ranging from 00 to 23.
Yet when a Mac is set to use 12-hour format, that rule is broken: the hour is returned in h format rather than HH, as unpadded digits between 0 and 11, or maybe even 1 and 12. Furthermore, the am/pm value is being added to the string.
Apple doesn’t explain this in its documentation of DateFormatter, but it drops a hint that my code may have been insufficient to stop macOS from meddling with the string formatting:
“When working with fixed format dates, such as RFC 3339, you set the dateFormat property to specify a format string. For most fixed formats, you should also set the locale property to a POSIX locale (“en_US_POSIX”), and set the timeZone property to UTC.”
Sure enough, adding the line
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
to my code restores respect for my formatting.
Why has this changed in Big Sur, and why hasn’t Apple documented it? The clue comes in an aside which I’ve seen in Apple’s documentation that iOS doesn’t respect the use of HH in time formats, and will override that if the clock setting uses 12- rather than 24-hours. That suggests that Apple was fully aware of this change to the behaviour of DateFormatters, but didn’t make it clear in its documentation.
Over the next week or so, I will be working through each of my apps which uses DateFormatters, adding the extra line which should make them compatible with both 12- and 24-hour clock settings. If macOS had continued to respect the Unicode Report TR35, none of this would have been necessary. And for Apple to change this without warning developers only compounds the problem it has created for everyone.
The root causes can be summarised thus:
- Although copious, Apple’s (albeit out-of-date) documentation claims compliance with TR35 but differs in unspecified ways.
- When macOS is set in 12-hour mode, DateFormatter silently rewrites format strings however it wants.
- Formatting for internal purposes rather than user presentation is controlled by user interface preferences, rather than formatting strings.
- Apple changed formatting behaviour without documenting or explaining that change.
Don’t be surprised if you see other software running into the same problems in macOS 11.2 and later.