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!
Related
Simply, I have a positive integer [9, 393, 3, 993], and I would like to localize it to a certain language [٩, ٣٩٣ ,٣ ,٩٣٩].
If I use NSNumberFormatter, it will localize the number according to the user's locale. However, I want to override that and choose any locale to translate the number to.
I tried the following, did not work:
// user locale is #"en"
NSNumberFormatter* formatter = [NSNumberFormatter new];
[formatter setNumberStyle:NSNumberFormatterNoStyle];
[formatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:#"ar"]];
[formatter setMinimumIntegerDigits:padding];
return [formatter stringFromNumber:#(num)];
The returned string is in English.
Please note that I have a very similar code snippet for NSDateFormatter, but it works as expected. The NSDateFormatter object respects the set locale.
It seems I came across a very special case where the locale of the app would just freak out.
I am changing the default locale of the app by using something like this:
[[NSUserDefaults standardUserDefaults] setObject:#[#"ar"] forKey:#"AppleLanguages"];
Then, I was trying to get the preferred language and create a locale object from it using:
NSString* langPrefix = [NSLocale preferredLanguages][0];
Finally, create a new NSLocale object from the returned object. When testing the code, I would change the language from within the app, then close the app through Xcode. I am assuming that the NSUserDefaults would not synchronize, but even if I called the synchronize method, it would still screw up.
Bottom line is, testing localization should be done by deploying the app, and after the device has been disconnected from Xcode, so the app would run through all the life-cycle stages properly.
So I am using an array of dates as Section Headers in my UITableView. The dates of my data in the arrays is as follows
07/12/2012
07/13/2012
07/14/2012
But when I run the app the section headers are all moved back one day so they are as follows:
07/11/2012
07/12/2012
07/13/2012
What gives? The data I am pulling from the server is specific to timeZone. We know when our app will be used and on what day (think traveling circus).
I am sure this has to do with NSTimeZone, so I tried the following, which did not work.
self.sectionDateFormatter = [[NSDateFormatter alloc] init];
[self.sectionDateFormatter setDateStyle:NSDateFormatterLongStyle];
[self.sectionDateFormatter setTimeStyle:NSDateFormatterNoStyle];
[self.sectionDateFormatter setTimeZone:[NSTimeZone localTimeZone]];
Anyone know have a solution?
Set the dates to UTC and it fixed the issue.
I have a String which looks like this
NSString *string = #"2012-04-30T23:59:00+10:00";
Right now, I am trying to convert that into NSDate format (So I can store the data in a database at a later stage). Right now, I can't seem to get a date format that works with my current date. I am using
NSDateFormatter *assignmentDateFormat = [[NSDateFormatter alloc] init];
[assignmentDateFormat setDateFormat:#""];
to parse my date but I just cant seem to get the combination right (e.g. YYYY-MM-dd etc.).
Any help would be greatly appreciated and if someone knows how to do the timezone part (+10:00) that would be amazing. Thanks!
The full specification is here: http://unicode.org/reports/tr35/tr35-10.html#Date_Format_Patterns
(Kinda tricky to find in Apple documentation... bookmark it!)
Edit: OK, try this one: yyyy-MM-dd'T'HH:mm:ssZ
There are a simple tutorial here: http://www.picksourcecode.com/ps/ct/161110.php
Use this:
NSDateFormatter *assignmentDateFormat = [[NSDateFormatter alloc] init];
[assignmentDateFormat setDateFormat:#"yyyy-MM-dd'T'HH:mm:ss+10:00"];
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…)
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.