iOS NSDateFormatter needs NSLocale even it's UTC - ios

I have a doubt that I cannot understand why is the way it is and I appeal to the Gods of this site :)
I have a date coming like this:
"1982-01-01T00:00:00Z"
As I'm displaying whatever the server sends (I know, customer requirement, not good practice...), I'm forcing the device to have that TimeZone with the following method, simplified without error checking, not optimized, and all that kind of things:
+ (NSString *) yearStringFromDate: (NSDate *) date
{
NSDateFormatter *formatter = [NSDateFormatter new];
[formatter setDateFormat:#"YYYY"];
[formatter setTimeZone:[self timezoneForSIHF]];
return [formatter stringFromDate:date];
}
This should be UTC, BUT if I don't set the locale I'm getting, and JUST sometimes the incorrect year. So by adding this I get the year correct for all cases:
[formatter setLocale:[NSLocale localeWithLocaleIdentifier:#"en_US_POSIX"]];
For further info, I'm testing in a real device and simulator with different languages (en, de, es).
Why is this?
Why does the locale affect the date even though the timeZone is correct?
Why sometimes is working with some dates and sometimes it's not?
For example, 1982 is returning without setting the locale, 1981 and if I set it 1982. This doesn't happen with 1980, returning in both cases 1980 (or 1987, or ...)
Thanks in advance for all your replies :D
Cheers!

When converting ISO 8601/RFC 3339 date string to NSDate object, one uses the en_US_POSIX locale in case the user is not using a Gregorian calendar. See Technical Q&A 1480.
In your case, though, you are trying to get year string representation from date object. In that case you might not need en_US_POSIX. That's only necessary if you need to use Gregorian calendar regardless of what sort of calendar the device might currently be using.
As noted by others, though, you should be using yyyy and not YYYY. The former returns the calendar year. The latter returns the year, in "Week of Year" based calendars, which may not always be the same value as calendar year.
See the date formatting patterns for a discussion contrasting y and Y.
By the way, if you really need the year component of the date, you can also use the NSDateComponents related methods of NSCalendar.

Use yyyy instead of YYYY.
Reference.

Related

Conversion: NSString to NSDate

This is a really common question, but mine might be unique since I have long decimal places for the seconds.
NSString *timestamp = #"2015-11-06 15:27:34.0000000";
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyy-MM-dd'T'HH:mm:ss zzz"];
NSDate *capturedStartDate = [dateFormatter dateFromString: timestamp];
My capturedStartDate is null.
For the date format, I've tried replacing zzz with Z, a, and removing it completely. I've also tried with and without the 'T'. Does someone know the correct format to retrieve this date from the string?
Your date string doesn't have a time zone in it, so you should remove that Z. Also, you have fractions of a second, too. And there's no T in the date string. So you want
yyyy-MM-dd HH:mm:ss.SSSSSS
This does beg the question as to what time zone that string represents. If it is UTC, you'll want to set the time zone of the formatter, accordingly.
Likewise, you might want to be careful about users with non-Gregorian calendars. See Apple Technical Q&A 1480 regarding setting the locale to en_US_POSIX.

Format partial date based on locale

I'm trying to display the month and day of a given date, like so:
"November 5"
but allow it to change the order based on locale.
"5 November" (or whatever other people do)
I am aware that I can simply hard code the formats like so:
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"MMMM d"];
But, this doesn't update for locale, and I don't want to have to hard code a list of locales that use one style or the other.
Generally when I want to display a date for a given local, I know that I can use setDateType on the formatter, and pick from a number of pre-existing formats that will nicely account for the current locale. Unfortunately, none of the existing NSDateFormatterStyle will display the way I need them to.
The one solution I have been able to think of is to set the date style to long style, and then read through the dateFormat string, and see if I hit a 'd' or 'M' first. Then format it accordingly. This would be fairly easy to do, but it seems really hacky. Surely there is a better way to do that.
Any suggestions?
Apparently NSDateFormatter dateFormatFromTemplate is a thing, and it solves my problem.
NSString *format = [NSDateFormatter dateFormatFromTemplate:#"MMMM d" options:0 locale:[NSLocale currentLocale]];
This will format the date correctly following your template according to current locale.

iOS app occasionally sends an invalidly-formatted ISO8601 date to REST API

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.
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")
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″.

Date conversion from string to NSDate in ios

I have searched over internet for a long time to get this but I can't find the solution. I have received a date string from web services as "22 May 2014", I have to convert into NSDate format for check it with current date. And I have to find out the date from web service is in future or in past time.
The actual problem is that when I convert this using
NSDate *date;
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"dd MMMM YYYY"];
date = [dateFormatter dateFromString:dateString];
But I get an entirely Different Date, Sample Input dateString:22 June 2014 and Output I get is 2013-12-21 18:30:00+0000
Please suggest any solutions.
Thanks in advance. :)
You're using YYYY, which doesn't mean what you think it means. From the TR35-31 documentation, Y is the symbol for "year in week-of-year calendars".
You want dd MMMM yyyy instead as your format string. Mixing week-of-year-based fields and regular day/month/year fields is a recipe for odd problems.
Additionally, you may well want to set the time zone in your formatter - if you're just parsing a date, then you should consider using UTC, and make sure that all your calculations and formatting/parsing use UTC.
(I suspect the issue here is that week-of-year hasn't been set, so is assumed to be 1... and the week-year 2014 started on December 30th. Then the day-of-month is set to 22 by the dd part, and then your time zone offset of UTC+05:30 is taken into account.)

NSDate and NSDateformatter wrong output

So, i'm trying to create a NSDate object for sunrise and sunset. I get the date based on NSDatePicker, i get coordinates from a Map, and i get the timezone from the GPS from the map.
I use this code to get the NSDate object: https://github.com/MosheBerman/KosherCocoa-legacy
This one to get the coordinates: https://github.com/digdog/MapKitDragAndDrop
And this one to get the the timezone based on coordinates: https://github.com/Alterplay/APTimeZones.
Right now my physical location is in Los Angeles, and the sunrise and sunset i'm using to testing is back home in Denmark.
-(NSString *)sunriseDate{
//Create the GeoLocation based on 'latitude' and 'longitude' (getting the from MapKitDragAndDrop) and 'location.timeZone' (getting that from APTimeZones).
GeoLocation *position = [[GeoLocation alloc] initWithName:#"position" andLatitude:latitude andLongitude:longitude andTimeZone:location.timeZone];
AstronomicalCalendar *astronomicalCalender = [[AstronomicalCalendar alloc] initWithLocation:position];
//daysBetween is the value from NSDatePicker
astronomicalCalender.workingDate = [NSDate dateWithTimeIntervalSinceNow:kSecondsInADay*[self daysBetween]];
NSDate *sunriseDate = [astronomicalCalender sunrise];
NSLog(#"Sunrise time: %#", sunriseDate);
//This spits out: Sunrise time: 2014-03-05 06:09:53 AM +0000 which is the right time.
NSDateFormatter *sunriseTime = [[NSDateFormatter alloc] init];
[sunriseTime setDateFormat:#"HH:mm:ss"];
NSString *sunriseString = [sunriseTime stringFromDate:sunriseDate];
NSLog(#"Sunrisestring: %#", sunriseString);
//This spits out: 10:09:53 PM.
return sunriseString;
}
Why does this happen and can anyone maybe give me a solution to this?
To anyone who might stumble into the same thing.
I found a library on github https://github.com/Alterplay/APTimeZones that helped me determine the timezone based on the coordinates.
Then i used
[sunriseTime setTimeZone:location.timeZone];
This put out the right time for the timezone.
Hope this helps anyone!
You need to match the input format correctly.
You may only be interested in the time but the NSDateFormatter doesn't care. NSDate is never JUST a time. It is a point in time and so includes the date too. It doesn't work without the date and time sections.
Also, this is probably one of THE MOST ASKED questions on Stack Overflow. Any other NSDate to NSString (or vice versa) question will answer this.
Your date format should be...
#"YYYY-MM-dd hh:mm:ss a EEEE"
I believe. Something like that anyway.
This spits out: 10:09:53 PM. This is correct local time for your time zone, which differs by 8 hours from Greenwich time Sunrise time: 2014-03-05 06:09:53 AM +0000. That's all. You have evening when a german man wakes up.
As Fogmeister said, you should include the timezone when creating a NSDateFormatter. Take a loot at Apple's Docs, Data Formatting Guide:
Fixed Formats
To specify a custom fixed format for a date formatter, you use setDateFormat:. The format string uses the format patterns from the Unicode Technical Standard #35.
The Unicode official site:
http://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Date_Format_Patterns
You may try using this: [sunriseTime setDateFormat:#"yyyy-MM-dd hh:mm:ss a EEEE"];
instead of [sunriseTime setDateFormat:#"HH:mm:ss"];

Resources