Format partial date based on locale - ios

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.

Related

iOS NSDateFormatter needs NSLocale even it's UTC

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.

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

NSDateFormatter local date format issue

I have NSDate, and I need to convert it to string.
Usually I use this code:
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"MM/dd/yyyy"];
NSString *stringFromDate = [formatter stringFromDate:date];
return stringFromDate;
But I need to show data according to local date format. For instance, in USA the date usually starts from the month, but in Russia the date starts from day (#"dd/MM/yyyy"). And may be there is a different way of separators in different countries: '/', '.' ',' '-'
What is the best approach to do this?
Update (according to Jon Skeet answer):
if I specify NSDateFormatterMediumStyle the date is "4.9.2013", but for me it's better "04.09.2013". NSDateFormatterShortStyle doesn't help with this neither. Is there a way to add zeroes ?
See the date formatter guide which gives advice about this, suggesting that you set the style rather than calling setDateFormat:
NSDateFormatter makes it easy for you to format a date using the settings a user configured in the International preferences panel in System Preferences. The NSDateFormatter style constants—NSDateFormatterNoStyle, NSDateFormatterShortStyle, NSDateFormatterMediumStyle, NSDateFormatterLongStyle, and NSDateFormatterFullStyle—specify sets of attributes that determine how a date is displayed according to the user’s preferences.
It then gives a listing showing an example starting with this:
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
[dateFormatter setTimeStyle:NSDateFormatterNoStyle];
It sounds like you'd want NSDateFormatterShortStyle instead though.
setDateFormat is what you use for a custom format. More advice from the same document:
There are broadly speaking two situations in which you need to use custom formats:
For fixed format strings, like Internet dates.
For user-visible elements that don’t match any of the existing styles

remove year for date format in respect to the users locale

I want to display the day and the month in respect to the locale of the users device.
So that the date of today would be displayed as 5/18 with american settings and 18.5. with german setting.
As I want to handle every possible locale, I cant just simply use the [dateFormatter setDateFormat:] and [dateFormatter setDateStyle:NSDateFormatterShortStyle] will display the year.
Beside trying to identify the year value in the string and removeing it, is there a more elegant way?
I found the answer myself:
[self.dateFormatter setDateFormat:[NSDateFormatter dateFormatFromTemplate:#"MM d"
options:0
locale:[NSLocale currentLocale]]];
See more at apple documentation on dateFormatFromTemplate

NSLocale is mostly empty - where are the attributes?

I really just want to know whether the user has set the iPhone to 12 hour mode for time display.
The NSLocale class seemed to be describing (in vague unspecific terms without any examples or apparently useful methods) what I was after.
So I have created some NSLocale objects like so:
NSLocale *systemLocaleApparently = [NSLocale systemLocale];
NSLocale *currentLocaleWithLotsOfGoodies = [NSLocale autoupdatingCurrentLocale];
When I inspect these objects using the debugger or NSLog(), the best I can get is something along these lines:
<CFLocale 0x1278e0 [0x382084f8]>{type = system, identifier = ''}
The current locale has an ID string in it, but nothing else.
From the documentation, I can lookup values from the locale, and one of these is an NSCalendar object. So I get that back and have a look at it, and it tells me it is "gregorian".
All of which is apparently of use to somebody... but what I would really like is a nice big dictionary of attributes showing all of the actual system properties, mainly so that my NSDateFormatters don't keep blowing up when a user chooses 12 hour format even though I have forced the formatters to use en_US_POSIX locale and a fixed setDate format.
(I'm sure NSCalendarDate never used to have these problems...)
Hopefully I am missing something blatantly (and probably embarrassingly) obvious, but perhaps someone kind would share it with me. Please :)
Here is one way to tell if the user has set their time format to a 12- or 24-hour format:
NSDateFormatter* formatter = [[[NSDateFormatter alloc] init] autorelease];
[formatter setTimeStyle:NSDateFormatterMediumStyle];
// Look for H (24-hour format) vs. h (12-hour format) in the date format.
NSString* dateFormat = [formatter dateFormat];
BOOL using24HourClock = [dateFormat rangeOfString:#"h"].location == NSNotFound;

Resources