NSDateFormatter crash. How come? - objective-c

NSDateFormatter *timeFormatter = [[[NSDateFormatter alloc] init] autorelease];
[timeFormatter setDateFormat:#"h:mm a"];
NSString *fullTime = [timeFormatter stringFromDate:someDateHere];
NSArray *timeParts = [fullTime componentsSeparatedByString:#" "];
timeLabel.text = [timeParts objectAtIndex:0];
ampmLabel.text = [timeParts objectAtIndex:1];
The LAST line crashes with
NSRangeException*** -[__NSArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]
How is this possible? There is a nil check on the date that returns just before this code.

From the Data Formatting Guide documentation (section Date Formatters > Use Format Strings to Specify Custom Formats > Fixed Formats):
Although in principle a format string specifies a fixed format, by default NSDateFormater still takes the user’s preferences (including the locale setting) into account.
...
In iOS, the user can override the default AM/PM versus 24-hour time setting. This may cause NSDateFormatter to rewrite the format string you set.
In other words, on an iOS device that's set for 24-hour time setting, you won't get "6:02 PM", you'll get "18:02", even though you specified "h:mm a". So when you separate that by spaces, you get back a single value, "18:02", not two values.

There's a caveat in the documentation for NSDateFormatter that says:
Note that although setting a format string (setDateFormat:) in principle specifies an exact format, in practice it may nevertheless also be overridden by a user’s preferences—see Data Formatting Guide for more details.
Could this apply in your case to produce a string without any spaces in it? (That would lead to a length 1 array when split by spaces, giving the exception you see in the place you see it.) Check this by logging the formatted date or attaching a debugger.
Note that the end of the page on date formats does recommend using plain old strftime_l when dealing with unlocalized dates/times. That might be more suitable for you. (Also, you want an AM/PM indicator in data that's bound for a computer? Seriously? The 24-hour clock is way easier to work with usually…)

Related

NSDateFormatter string is invalid

SITUATION
The app I'm maintaining has a rare but reoccurring issue parsing NSStrings into NSDates. I have been unable to replicate it locally, but our logs show that it is indeed happening for our users.
I found a lot of NSDateFormatter questions on StackOverflow, but none seem to be directly relevant so here I am.
WHAT'S HAPPENING
The error message on our logs is “2016-04-19T17:30:00-0400” is invalid. I used the getObjectValue:forString:range:error: function to grab the error description. That is not the only date that is failing; there are multiple ones.
The date format being used is yyyy-MM-dd'T'HH:mm:ssZZZZ.
If it helps, the project uses Mantle and the initial conversion that failed happened through the NSValueTransformer. The logging of the error is done separately from the NSValueTransformer.
WHAT I'VE TRIED
Tried a fallback solution where it attempted to parse the string once more into a date, but according to the logs the fallback hasn't had any effect
Tried parsing the failed strings in a separate test project...and they all work
Tried messing with timezone in the separate test project, but it doesn't seem to have any effect, especially since the timezone is already in the date string itself via ZZZZ. I looped through possible timezones via [NSTimeZone knownTimeZoneNames]
Tried messing with the NSLocale to no effect. I can't get it to fail in the separate test project. I looped through possible locales via [NSLocale availableLocaleIdentifiers]
Tried messing with the NSCalendar to no effect. I manually used the calendars provided at https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSCalendar_Class/#//apple_ref/doc/constant_group/Calendar_Identifiers
Tried seeing if it was a threading issue, but couldn't find any evidence that it's an issue. Given how the fallback solution is implemented though, it's very unlikely to be a threading issue.
CODE
Fallback code that doesn't work. Element is a Mantle object. rawWhenValue is a string.
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = kNSDateFormatterWhenJSONFormat; //yyyy-MM-dd'T'HH:mm:ssZZZZ
NSDate *when = [dateFormatter dateFromString:element.rawWhenValue];
element.when = when;
Code used to grab the NSDateFormatterError.
NSDate *dateFallback;
NSError *err;
NSRange range = NSMakeRange(0, element.rawWhenValue.length);
BOOL parsed = [dateFormatter getObjectValue:&dateFallback forString:element.rawWhenValue range:&range error:&err];
I'm at my wits end. Even a direction to look in would be appreciated. Many thanks!

NSLocale - invalid identifiers

If I am trying to initialize a NSLocale with a certain locale identifier, how can I tell if it is a legit identifier? No matter what identifier I pass in, even a garbage one, NSLocale still gets initialized to something.
For example, if you pass in "ar_NO", a locale with language arabic and a (invalid) country code of NO, the NSLocale object that I get seems legitimate. For example, if I call -[NSCalendar firstWeekday] from a calendar with that locale, it will return Monday. My question, is where is that coming from? Does iOS fall back to another locale in case it cant use the given identifier? I would think it would fall back to the base "ar", but "ar" firstWeekday is Saturday, so that is not the case.
Note: I know that the identifiers are specificed by ISO BCP 47 specifications and I know about the [NSLocale availableLocaleIdentifiers] function, but that doesnt really help me because if I use an identifier not in that list, I still get a NSLocale.
Anyone have any thoughts? Thanks in advance
I think it will fall back to local default settings if given NSLocale is invalid. Try changing local device settings and see what happens! After settings your invalid locale, print [NSLocale currentLocale]; and see what it returns.

Objective-C - How to Get Date Formatter to Add Colon in Time Zone Offset

I'm trying to format a date to match the format expected on the server side.
Wanted: 1985-01-24T00:00:00-07:00
Got: 1985-01-24T00:00:00-0700
Using: yyyy-MM-dd'T'HH:mm:ssZZZ
Is there a date format trick I can use to get that colon in there?
Here is my code. _birthdate is the date supplied by the birthdate selector:
NSDate *birthdate = (NSDate *)resultObject;
[_birthdate setNewTitle:[IRDate mmddyyFromNSDate:birthdate]];
//Set server-ready birthdate format
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"yyyy-MM-dd'T'HH:mm:ssZZZ"];
serverFormattedBirthDate = [formatter stringFromDate:birthdate];
NSLog(#"Birthdate: %#", serverFormattedBirthDate);
According to the Date Format Specifiers documentation, it looks like you'll need 5 Z's. That will get you things like "-08:00".
Aha, I see what you're getting at. If you run the formatting on OS X 10.8, you'll get the string you're expecting. However, if you run the formatting on iOS 5.1, you'll get the extra "GMT" in the string.
I'm guessing that the underlying data has changed in recent versions of the CLDR. In that case, I'm not sure what the correct answer is.

NSDateFormatter dateFromString returns incorrect date [duplicate]

This question already has answers here:
Get NSDate from NSDate adjusted with timezone
(2 answers)
Closed 9 years ago.
I am trying to use NSDateFormatter in my app which takes a date string and formats it to an NSDate so that I can do Date Comparisons, however I am finding when I use dateFromString and format it the date is losing one day.
NSString *dateString = #"02-06-2012";
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"dd-MM-yyyy"];
NSDate *dateFromString = [[NSDate alloc] init];
dateFromString = [dateFormatter dateFromString:dateString];
NSLog(#"My Date = %#", dateFromString);
[dateFormatter release];
This outputs to the console:
My Date = 2012-06-01 23:00:00 +0000
Try adding this lines to your code,
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"GMT+0:00"]];
or
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:#"GMT"]];
SWIFT update :
Code from quetion,
let dateString = "02-06-2012"
var dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "dd-MM-yyyy"
var dateFromString : NSDate = dateFormatter.dateFromString(dateString)!
println("My Date \(dateFromString)")
And Solution ,
dateFormatter.timeZone = NSTimeZone(name: "GMT")
OR
dateFormatter.timeZone = NSTimeZone(abbreviation: "GMT+0:00")
I don't believe that Dhruv's answer is correct. In fact, it's not clear there's any problem at all. You just seem to have an incorrect expectation of what should happen and/or interpretation of what's happening.
NSDate represents a moment in time. This moment does not have one unique name. It will be known by different names in different places and under different naming systems (time zones, calendars). NSDate doesn't deal with any of this, except lamely in its -description method, where it has to produce a string representation of that moment.
Second, a string like "02-06-2012" doesn't specify a precise moment in time. First of all, it's just a date with no time information, so NSDateFormatter just defaults to the first moment for that date. Second, it doesn't specify the time zone. The first moment of the calendar day is a different moment in each time zone. Unless you specify a time zone with -setTimeZone: or the string itself carries time zone information, NSDateFormatter assumes that any date strings you ask it to parse are in the current time zone.
So, your dateFromString object represents the first moment of the specified date, 02-06-2012, in your time zone. I expect this is what you wanted. However, you then got confused by the way that NSDate describes itself when logged. As I said, NSDate has to pick some "name" (string representation) for the moment it represents and which name it picks is fairly arbitrary. These days it is picking the name that the moment is known by in UTC. I gather from the log output shown in your question that you are located at UTC+0100. So, the date may look like it's one day earlier but it really is the same moment you specified. In other words, "2012-06-01 23:00:00 +0000" and "2012-06-02 00:00:00 +0100" are two equivalent names for exactly the same moment in time. You just aren't used to seeing the first one and misinterpreted it.
The lesson is that you have to stop relying on NSDate's self-description to be in any particular time zone. Really, you have to not rely on anything about it, since it's not documented. In fact, the docs for -[NSDate description] state, "The representation is not guaranteed to remain constant across different releases of the operating system."
Dhruv's solution seems to help merely because it causes NSDateFormatter and -[NSDate description] to agree on the time zone. But that's unreliable. It wouldn't work on Snow Leopard, for example, because -[NSDate description] used the local time zone instead of UTC in that version of the frameworks.
More importantly, though, it alters the actual moment represented by the NSDate object you get from NSDateFormatter's interpretation of your date string. I suspect you really want that to have a specific meaning – you want the string to be interpreted as being in the local time zone – and his solution thwarts your intent.
tl;dr: you were getting the date you wanted all along; don't rely on -[NSDate description]; don't use Dhruv's solution

nil result from NSDateFormatter with 0's in format string

I'm getting a date from a webservice back in the form MM00yyyy -- it is just the two-digit month, followed by two 0's, and then the four-digit year. When I do this:
NSString *expDate = #"12001975";
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"MM00yyyy"];
NSDate postDate = [dateFormat dateFromString:expDate];
[dateFormat dateFromString] returns nil for some reason. I have also tried MMddyyyy, and MM'0''0'yyyy, with no success either way. I am converting a similar date, except the 0's are actually the day with no problem using the same method.
To get this working, I would just use the following pattern MMHHyyyy. Since you need only the date and not neccessarily the hour, the HH will use the 00 to set the time as zeroth hour and hence you will get the date that you are looking for. Again this is just a hack and a workaround only to solve your current problem.
Have a look at the Date Formatting Guide from Apple. The section "Use Format Strings to Specify Custom Formats" lists all the different standards the are supported by various iOS versions for specifying a format string. I would say that "00" is not allowed, so that is the reason why "MM00yyyy" is failing. Similarly, "MMddyyyy" is also failing because no day can be "00".
I don't know if you can have more luck with UNIX functions, as the Apple doc suggests:
For date and times in a fixed, unlocalized format, that are always guaranteed to use the same calendar, it may sometimes be easier and more efficient to use the standard C library functions strptime_l and strftime_l.
Be aware that the C library also has the idea of a current locale. To guarantee a fixed date format, you should pass NULL as the loc parameter of these routines. This causes them to use the POSIX locale (also known as the C locale), which is equivalent to Cocoa's en_US_POSIX locale, as illustrated in this example.
struct tm sometime;
const char *formatString = "%Y-%m-%d %H:%M:%S %z";
(void) strptime_l("2005-07-01 12:00:00 -0700", formatString, &sometime, NULL);
NSLog(#"NSDate is %#", [NSDate dateWithTimeIntervalSince1970: mktime(&sometime)]);
// Output: NSDate is 2005-07-01 12:00:00 -0700
Getting the format strings right seems much more like art than science. I suggest you make a new string without the 00 in it and then have your DateFromatter process that with "MMyyyy".
While this might not be the "correct" way to do it, it should solve your problem pretty quickly.
The zeros are unsupported symbols. Apple supports the following characters for date formatting: http://unicode.org/reports/tr35/tr35-10.html#Date_Format_Patterns See the day section.