5

Having some problems parsing date. I have an array of supported formats and once I receive the date (string) from API, I try to parse it iterating through the formats until I get a valid NSDate object.

A snippet from Xcode Playground --

let dateString = "02/06/1987" // --> want to parse into this Feb 6, not Jun 2
let dateFormatIncorrect = "dd.MM.yyyy"
let dateFormatCorrect = "MM/dd/yyyy"
let dateFormatter = NSDateFormatter()

dateFormatter.dateFormat = dateFormatIncorrect
let date = dateFormatter.dateFromString(dateString)! // "Jun 2, 1987, 12:00 AM"

dateFormatter.dateFormat = dateFormatCorrect
let date2 = dateFormatter.dateFromString(dateString)! // "Feb 6, 1987, 12:00 AM"

Why does it parse the date even though the format is clearly incorrect for a given string? Could not find anything in the docs regarding date formatter ignoring separators.

I realise the proper solution would be to have a fixed format returned from API but was wondering what is happening here?

Thanks.

Mayank Jain
  • 5,663
  • 7
  • 32
  • 65
sharoni
  • 158
  • 9
  • @TheParamagneticCroissant it isn't flipping the day/month. The two formatters have them specified in a different order. dMy and Mdy. – Fogmeister Nov 10 '14 at 09:36
  • MM stand for the month, and dd stand for the day of the month. so the dd.MM.yyyy is corret format – Sunny Shah Nov 10 '14 at 09:36
  • @Fogmeister Oh, yes, that's correct. – The Paramagnetic Croissant Nov 10 '14 at 09:36
  • The month and day positions are different intentionally. I'm curious of why it gets parsed with the first (maybe incorrect is not the right word) format even though the separators don't match (/ and .) – sharoni Nov 10 '14 at 09:44
  • That's interesting. Even with `dateFormatIncorrect = "'aaa'dd'bbb'MM'ccc'yyyy'ddd'"` the date formatter converts it. – Martin R Nov 10 '14 at 09:55
  • Have you tried setting lenient = NO? – pbasdf Nov 10 '14 at 10:13
  • @pbasdf: `lenient = false` is the default. I also tried to set it explicitly with no difference. Setting the locale to "en_US_POSIX" makes also no difference. – Martin R Nov 10 '14 at 10:14

3 Answers3

5

It seems that NSDateFormatter is extremely lenient when parsing a date string. Unfortunately, I could not find a reference for this, but even with

dateFormatIncorrect = "'aaa'dd'bbb'MM'ccc'yyyy'ddd'"

the date string "02/06/1987" is successfully parsed. There is a lenient property, but that is false by default, and setting it explicitly makes no difference.

As a workaround, you could convert the parsed date back to a string, and only if the result is equal to the original string, the date is accepted:

extension NSDateFormatter {

    func checkedDateFromString(string : String) -> NSDate? {
        if let date = self.dateFromString(string) {
            if self.stringFromDate(date) == string {
                return date
            }
        }
        return nil
    }
}

Using this custom extension,

dateFormatter.checkedDateFromString(dateString)

returns nil for the incorrect date format.


Generally, if you work with fixed date formats, you should also set the locale to "en_US_POSIX"

dateFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")

(see What is the best way to deal with the NSDateFormatter locale "feechur"?). However, this makes no difference for this particular problem.


Update for Swift 3:

extension DateFormatter {

    func checkedDate(from: String) -> Date? {
        if let date = date(from: from), string(from: date) == from {
            return date
        }
        return nil
    }
}
Community
  • 1
  • 1
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
1

This could be related to the fact that NSDateFormatter will anyways respects the users settings when using fixed formats

Although in principle a format string specifies a fixed format, by default NSDateFormatter still takes the user’s preferences (including the locale setting) into account

So may be the locale defined in your preference uses '/' for separator and satisfies the 'incorrect format'. Even if that is not the case, apple noted in several places that NSDateFormatter might not act consistently. So try setting a fixed locale as below and see if that helps

NSLocale *locale = [[NSLocale alloc] 
    initWithLocaleIdentifier:@"en_US_POSIX"];
[dateFormatter setLocale:locale];

See these links for detail: apple tech note . Note directly related to separators, but that could be related.

Antenehs
  • 336
  • 1
  • 7
  • Did you try that? That was also my first thought, but setting the locale to "en_US_POSIX" did not solve the problem in my test case. – Martin R Nov 10 '14 at 10:24
  • That was one of the first things I've tried but unfortunately with no luck. – sharoni Nov 10 '14 at 10:28
1

Had a similar issue:

NSDateFormatter returns date object from invalid input

Filed a bug report at Apple. Result: Will not be fixed, as the change could break working code, in addition it is more error tolerant and thus provides some kind of convenience.

Please know that our engineering team has determined that this issue behaves as intended based on the information provided.

It appears that ICU’s udat_parseCalendar() is very lenient and still is able to parse even if the string doesn’t exactly match the format. We understand preferring that the formatter return nil in these cases but (1) there’s no easy way for us to know that the input string doesn’t match the format since ICU allows it and doesn’t throw an error and (2) suddenly returning nil in these cases would almost certainly be a bincompat issue.

In my case I had the option to either modify the unit tests and be more tolerant in case of invalid input or have an additional checkup (based on the recommended approach, which is the accepted answer for the post) whether the resulting NSDate's string fits to the input string.

Community
  • 1
  • 1
Lepidopteron
  • 6,056
  • 5
  • 41
  • 53