I have a shortage of screen real estate for my time labels in my iPhone app.
My solution is to have the time e.g. 12:00 on one line and then if the users current locale specifies that an AM-PM is used, have these in a second label below it.
Since AM-PM also have localized variants I can't just look for the letters "AM" or "PM", then I thought about stripping the last two letters, but by checking I found out some languages uses a format like this: "F.M." "E.M". My next thought was to strip everything after the first 5 digits(12:34), but for hour intervals below 10 that is no good either.
Is there a "locale safe" way of always removing the localized suffix and move it to a new string, regardless of the users settings?
Thank you in advance:)
There is no locale safe way of doing that.
Use NSDateFormatter to generate two strings.
NSDateFormatter *timeOfDayFormatter = [[[NSDateFormatter alloc] init] retain];
[timeOfDayFormatter setDateFormat:#"hh:mm"];
NSDateFormatter *amPmFormatter = [[[NSDateFormatter alloc] init] retain];
[amPmFormatter setDateFormat:#"aa"];
NSLog(#"Time is: %# %#",
[timeOfDayFormatter stringFromDate:theDate],
[amPmFormatter stringFromDate:theDate]);
Now you can layout your user interface with the two strings.
The 24-hour format is standard where I live. I'd "force" that format on the user if screen real estate is a real problem.
I ended up solving it like this and then manually test it with 15+ locale settings:
NSArray *timeParts = [NSArray arrayWithArray:[[timeFormatter stringFromDate:myDate] componentsSeparatedByString:#" "]];
Then I test the timeParts array:
if ([timeParts count] > 1) {...}
If the count is 1 it is a locale without the suffix and I don't set the "AM/PM" label.
Else, I set both labels, the timeLable with [timeParts objectAtIndex:0] and the localeLabel with [timeParts objectAtIndex:1]
This seems to be a stable solution for all locales.
Related
Our iOS iPhone app contains this code which produced a valid NSDate object below named resultDate in iOS 5:
static NSDateFormatter *invariantFmt = nil;
if (!invariantFmt) {
invariantFmt = [[NSDateFormatter alloc] init];
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US_POSIX"];
[invariantFmt setLocale:locale];
[locale release];
[invariantFmt setDateStyle:NSDateFormatterShortStyle];
[invariantFmt setTimeStyle:NSDateFormatterMediumStyle];
}
NSDate *resultDate = [invariantFmt dateFromString:#"08/04/2010 10:43:39 AM"];
After upgrading to XCode 4.6 and iOS 6.1, that code now gives a nil for resultDate, so something has changed with what they use to parse. The release notes says nothing about NSDateFormatter changing. Internet research has turned up only that they might have changed to use a newer Unicode UTS Locale parsing standard. Obviously they changed something. After twiddling with code and taking a known valid NSDate object and applying the same NSDateFormatter settings to get a NSString, I find that iOS 6.1 likes this string instead: #"08/04/2010, 10:43:39 AM"
The only difference being that extra comma after the date portion. Using that in iOS 6.1 gives back a valid date with the same above code. Anyone seeing this and understand why that's different or if that's an ok Unicode change or a Apple bug?
The format styles should only be used to convert NSDate objects to text to display to the user. When parsing a date string in a known format, you must use a specific format, never the styles. The use of the en_US_POSIX locale is used to ensure that the format you specify isn't tweaked by the OS based on user preferences such as the 24-hour time setting.
So, as you suspected, you need to remove the two calls to set the date and time styles and replace them with a call to set a specific format that matches your known date/time string you need to parse.
Generally you should not output a formatted Date to a file, and later want to parse it back.
A Date should be stored as long value UTC (with or without additional TimeZone offset).
Only in the last moment, before visualizing a Date to UI it should be formatted and local time applied.
This not only i smy experience, it it is also stated in Apple DateFormatting Doku.
Here is how I am localising days:
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
NSString *dayFormat = [NSDateFormatter dateFormatFromTemplate:#"EEEE" options:0 locale:[NSLocale currentLocale]];
[dateFormatter setDateFormat:dayFormat];
[dateFormatter setLocale:[NSLocale currentLocale]];
NSString *day = [dateFormatter stringFromDate:date];
And yet it seems to be returning English days of the week (Monday, Tuesday etc) rather than the device language (which has been set to German in the simulator).
Any idea where I'm going wrong?
Update after doing some research on device I've realised that its actually the region setting, not the language setting which changes the date language. Odd, but I guess its done for a reason.
Thanks
The language of the date is set by the region not the language. This has to be a bug. If I'm in Germany, but an English speaker I don't want to have my dates in German, surely?
Anyway, this is why. You have to change language and region.
I've had the same issue. It wouldn't work on the simulator, but it would on a device. Can you try it ? I did not however solve it, I did not even look more into it as it was working perfectly on the device, which is truly the main target of your app.
Edit:
This comes from Apple's doc:
currentLocale
Returns the logical locale for the current user.
+ (id)currentLocale
Return Value
The logical locale for the current user. The locale is formed from the settings for the current user’s chosen system locale overlaid with any custom settings the user has specified in System Preferences.
Discussion
Settings you get from this locale do not change as a user’s
Preferences are changed so that your operations are consistent.
Typically you perform some operations on the returned object and then
allow it to be disposed of. Moreover, since the returned object may be
cached, you do not need to hold on to it indefinitely. Contrast with
autoupdatingCurrentLocale.
Maybe you can try using:
preferredLanguages
Returns the user's language preference order as an array of strings.
+ (NSArray *)preferredLanguages
Return Value
The user's language preference order as an array of NSString objects, each of which is a canonicalized IETF BCP 47 language identifier.
So I seem to be getting burned by the iPhone's calendar support again. I've come across an issue with the way core data compares dates. Core data appears to be storing dates using the Gregorian calendar as they come out in a 20XX-MM-dd format. But when I build an NSPredicate this way
[nsRequest setPredicate:[NSPredicate predicateWithFormat:#"publish <= %# AND expires >= %#", [NSDate date], [NSDate date]]]
if the user's device isn't set to a Gregorian calendar then no items will appear, as core data doesn't seem to localizing it's NSDates before comparing against the input dates.
So it would appear that if the date were today '2011-05-31' in Japanese calendar mode core data is using '0023-05-31' since it's the 23rd year of the current emperor, placing the input date about 2000 years before what it's being compared against.
I've found I can work around this like so
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:[[NSLocale preferredLanguages] objectAtIndex:0]] autorelease]];
[dateFormat setDateFormat:#"yyyy-MM-dd HH:mm:ss ZZZ"];
NSString *todayGregorian = [[dateFormat stringFromDate:[NSDate date]] retain];
[dateFormat release];
NSDateFormatter *df = [[NSDateFormatter alloc] init];
[df setDateFormat:#"yyyy-MM-dd HH:mm:ss ZZZ"];
[nsRequest setPredicate:[NSPredicate predicateWithFormat:#"publish <= %# AND expires >= %#", [df dateFromString:todayGregorian], [df dateFromString:todayGregorian]]];
but I'm hoping that maybe I'm being an idiot and there's a better way. Any suggestions?
Please note:
Asking my Japanese users to change
the date format they use isn't an
option.
Also, I still need to store
the core data dates in a Gregorian
format, as these users do sometimes
change their calendars back and
forth.
Real problem
My solution above is wrong. TechZen's answer kicked my head in to gear and I wish I could up vote him 100 times, I ended up solving the issue.
My problem is data going in to Core Data is being sent by the server in GMT and the formmater parsing that data might not necessarily be in GMT so I need to adjust the time going in to Core Data correctly.
You really have no choice but to translate from NSDate to various calendars, especially if your users employ multiple calendars.
Calendar objects are not dates themselves but rather specialized formatters. All dates are actually stored in the GMT standard format regardless of the calendar they originated with. Then they are translated/formatted to the appropriate calendar as needed. We don't see this immediately in apps targeted towards Westerners because contemporary Western calendars are based on the GMT so no translation appears needed.
Date and time programming is deceptively complex. We don't intuitive feel it is complex because so much of the grunt work is usually handled for us. However, when you start doing something unusual, the complexity because readily apparent.
As a rule of thumb, the time to worry with data and time programming is not when the code seems to complex but when it seems to simple.
I'm having a bit of a problem with NSDateFormatter failing on one user's device (returning nil when parsing a string) and working perfectly when I run it locally (either in the simulator or on my device).
I'm trying to rule out what could be causing a difference in this behaviour. My first thought was the locale but I've tried setting it explicitly to ensure the same locale is always used but it makes no difference.
Here is the code:
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyy-MM-dd'T'HH:mm:ssZ"];
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_GB"];
[dateFormatter setLocale:locale];
[locale release];
NSDate *theDate = [dateFormatter dateFromString:dateString];
NSLog(#"PARSING DATE %# AS %#", dateString, theDate);
On the failing device, I get:
PARSING DATE 2010-11-28T20:30:49-0000 AS (null)
But locally I get:
PARSING DATE 2010-11-28T20:30:49-0000 AS 2010-11-28 20:30:49 +0000
This is driving me crazy, am I missing something else?
I am running 4.2 locally (simulator) and on my device (an iPhone 4). The failing device is a 3GS running 4.2.1.
Any ideas would be much appreciated!
I'm pleased to say that I eventually got to the bottom of this issue and I must pass on my thanks to #bendodson on Twitter for helping me out with this. aBitObvious also hit on the issue in his comment above; I'd have up-voted him if I could.
There was one difference between the user's device and mine, and that was that his device was set to use the 12 hour clock and mine was not. This single thing meant that the NSDateFormatter was unable to parse the time in the above examples and returned nil.
By far the biggest issue for me with this problem was being unable to reproduce the problem locally!
So, to be clear, to solve this issue; that is, if you are parsing date/time strings that are in a known, fixed format (often coming from some API as this was in my case), you should set the correct locale for the date formatter, which will often be en_US_POSIX.
...
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US_POSIX"];
[dateFormatter setLocale:locale];
[locale release];
For more information on this, read Apple QA1480.
What am I doing wrong?
I am trying to getNSDateFormatter to translate custom patterns for dates using the current locale.
Example:
dateFormat = [[NSDateFormatter alloc] init];
dateFormat.locale = [NSLocale currentLocale];
[dateFormat setDateFormat:#"MMM"];
output = [self.dateFormat stringFromDate:dateObject];
No matter what I change my current locale settings to, I always see the English month abbreviations.
Thanks for any help you can provide.
It looks like it was working all of the time. I had been leaving my location set to United States and changing only the language. To get it to work correctly you have to change the location as well as the language.