2

A couple of times a day, our PHP REST API logs an error causing by an invalidly-formatted ISO8601 date, coming from a GET request sent by our iOS app. The interesting thing is that most of the calls are fine (eg. 2015-07-07T00:00:00+10:00), but every so often we get a strange one (eg. 2015-07-07T12:00:00 am+10:00).

The code I believe is causing this is as follows:

NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZZZZZ"];

NSString *iso8601StringStart = [dateFormatter stringFromDate:self.searchStartTime];

Is there any circumstance in which NSDateFormatter could somehow (incorrectly) get am/pm from "yyyy-MM-dd'T'HH:mm:ssZZZZZ", when it's clearly the unintended behaviour? Are there certain kinds of NSDate that cause different behaviour? I'm stumped. The date given is always created via dateFromComponents.

shellco
  • 536
  • 1
  • 6
  • 25
  • try this "yyyy-MM-dd'T'HH:mm:ss a ZZZZZ" to show am/pm @Sadurnias – Dharmesh Dhorajiya Jul 07 '15 at 06:42
  • @DharmeshDhorajiya I don't think I was clear enough - I'm not trying to display am/pm, I'm trying to work out how it could possibly display am/pm without having the 'a' in the format string. I've edited the question to clarify a bit. – shellco Jul 07 '15 at 06:45

2 Answers2

1

I do not believe that that format string could ever generate the date with the am/pm annotations which you show. If I were you, my first course would be to double check that those dates are really being generated by those lines of code.

However, if you're sure this is happening, the only issue I can see is that it might be incorrect that you are not explicitly setting the locale and the calendar of the date formatter object. The date format syntax is defined by the unicode consortium, and the governing spec does say in section 4.5 that "If locales are not listed, dayPeriods fallback to AM/PM". I don't understand the whole document, but it suggests that being very explicit is the safest path.

If your only requirement is ISO8601, then you could use RFC3339 in UTC time zone, since this is a profile of ISO8601. This creates a correct formatter for that format:

let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z"
formatter.timeZone = NSTimeZone(forSecondsFromGMT: 0)
formatter.calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierISO8601)!
formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
algal
  • 27,584
  • 13
  • 78
  • 80
  • Thank you! This is exactly what I needed to get started. After some more research, setting the locale to en_US_POSIX has fixed it for me :) – shellco Jul 08 '15 at 00:37
0

My final solution (towards which I was nudged by algal's answer):

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

The Unicode spec was helpful (thanks algal), as was this Apple Technical QA, which suggested the en_US_POSIX as a specific solution.

"On iOS, the user can override the default AM/PM versus 24-hour time setting (via Settings > General > Date & Time > 24-Hour Time), which causes NSDateFormatter to rewrite the format string you set, which can cause your time parsing to fail."

Most helpfully, I found this explanation of the behaviour by huyz, although a little old:

When iPhone users change their region format between, say, “United States” and “France”, the users’ “24-Hour Time” setting is automatically switched to the mode that is most prevalent in that region. In France, that would set 24-Hour Time to “ON”, and in the U.S., that would set it to “OFF”. The users can then manually override that setting and that’s where trouble starts.

The problem comes from NSDateFormatter somehow “getting stuck” in the 12 or 24-hour time mode that the user has manually selected. So if a French user manually selects 12-hour mode, and the application requested NSDateFormatter to output time with the 24-hour format “HHmm”, it would actually receive time in a 12-hour format, e.g. “01:00 PM”, as if the application had instead requested “hhmm aa”. The reverse would happen if a US user manually selected 24-hour mode: outputting time with the 12-hour format “hhmm aa” would actually get you time in the 24-hour format instead, e.g. “17:00″.

Community
  • 1
  • 1
shellco
  • 536
  • 1
  • 6
  • 25
  • 1
    I think the explanation on that blog is probably irrelevant since it concerns a bug in an iOS 3 beta SDK, which has probably been fixed in intervening five years. The real issue is that if you don't specify locale, then it uses the user's default, and the resulting locale setting can _override the format string_. Yikes! Had no idea. – algal Jul 08 '15 at 00:51