DateFormatter bug


(Ronaldo Faria Lima) #1

Hi, all.

During a discussion in CocoaHeads Brazil, one developer came with a very odd situation involving Foundation’s DateFormatter class. Here is the code:

import Foundation

let fmt = DateFormatter()
fmt.dateFormat = "yyyy-MM-dd"
let aDate = fmt.date(from: "2017-10-15”)

The variable aDate is becoming nil. In order to reproduce it, you will need to change the time zone to Americas/Sao_Paulo in your system. On other time zones, it works perfectly.

There is another important fact: 15 of October is when day light savings is effective in Brazil.

Debugging this, I have found that setting the calendar property, it works perfectly, which means that it possibly be a bug on Core Foundation (I found that DateFormatter, in fact, calls Core Foundation functions under the hood).

I’d like to know if you guys can reproduce it on other time zones that have a day light saving, for the day that it become effective.

Cheers!

Ronaldo Faria Lima

Nineteen
ronaldo@nineteen.com.br
+55 16 98136 5287


(Quinn “The Eskimo!”) #2

Which means there’s no midnight on that day, right?

Last time I encountered this it was caused by an oddity in how NSDateFormatter handles dates that aren’t fully specified (in your case the date has year, month and day but not era, hour, minute and second). It does this by getting the missing components from the `defaultDate` property. This defaults to nil, yielding default components of 0, leading to a time of 2017-10-15 00:00:00, which doesn’t exist in the specified time zone.

The solution is to apply a default date with a time component. For example:

1 import Foundation
2
3 let fmt = DateFormatter()
4 fmt.timeZone = TimeZone(identifier: "America/Sao_Paulo")!
5 fmt.dateFormat = "yyyy-MM-dd"
6 fmt.defaultDate = Date(timeIntervalSinceReferenceDate: 12 * 60 * 60)
7 let aDate = fmt.date(from: "2017-10-15")
8 print(aDate) // -> Optional(2017-10-15 12:00:00 +0000)

If you remove line 6, it prints nil.

The date I’ve chosen in line 6 (that is 2001-01-01 12:00:00 GMT) is fine in this case because it maps to a 10:00:00 in the specified time zone. It could cause problems if your time zone is GMT-12, which can happen, so if you have to deal with arbitrary time zones then you need to provide a default date whose time components always make sense in local time.

Finally, I should stress that the above code is most definitely broken (try setting your default calendar to the Buddhist calendar and see what you get). If you’re working with fixed-format dates you must set the locale to `en_US_POSIX`, as described in QA1480 “NSDateFormatter and Internet Dates”.

<https://developer.apple.com/library/ios/#qa/qa1480/_index.html>

                   * * *

All of the above means that `DateFormatter` may not be the right option for you. In many cases it’s better to parse a fixed-format date string into a set of date components and then build a date from those components.

Share and Enjoy

···

On 16 May 2017, at 00:53, Ronaldo Faria Lima via swift-users <swift-users@swift.org> wrote:

There is another important fact: 15 of October is when day light savings is effective in Brazil.

--
Quinn "The Eskimo!" <http://www.apple.com/developer/>
Apple Developer Relations, Developer Technical Support, Core OS/Hardware