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.
Related
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!
I'm currently running Mountain Lion OS X 10.8 with Xcode 4.4 installed. I'm running the iOS 5.1 simulator. I'm using Buzztouch as a learning tool while I'm studying Objective-C and Xcode. I get the following alerts when I compile, but the build succeeds. However, I would like to know exactly what is going on and how I can remedy the situation. Thank you for any assistance you can provide. Here's the code and the alerts I'm getting:
BT_fileManager.m
Data argument not used by format string
[BT_debugger showIt:self:[NSString stringWithFormat:#"readTextFileFromBundleWithEncoding ERROR using encoding NSUTF8StringEncoding, trying NSISOLatin1StringEncoding", #""]];
Data argument not used by format string
[BT_debugger showIt:self:[NSString stringWithFormat:#"readTextFileFromCacheWithEncoding ERROR using encoding NSUTF8StringEncoding, trying NSISOLatin1StringEncoding", #""]];
BT_camera_email.m
Semantic Issue
Sending 'BT_camera_email *' to parameter of incompatible type 'id'
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
NSLog(#"is camera ok");
UIActionSheet *photoSourceSheet = [[UIActionSheet alloc] initWithTitle:#"Select Image Source"
delegate:self
Again, thanks.
Greg
I have no idea what Buzztouch might be, however.... :-)
The first warning is fairly simple. In a format string there are placeholders beginning with a '%' sign to indicate where data values should be substituted. For example, to substitute a string, one would use '%#'. In the examples you show, there are no placeholders but there are data values -- empty strings. The compiler is warning that something you indicate you want to have put into the new string created by stringWithFormat: won't be.
To be sure about the second one, I'd want to see the .h file that declares BT_camera_email but my best guess is that it doesn't adopt the UIActionSheetDelegate protocol. The description of initWithTitle:... says the second parameter should be id<UIActionSheetDelegate> and that's probably what is being complained about.
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.
I was writing a CLI-Tool for Mac OS X (10.5+) that has to deal with command-line arguments which are very likely to contain non-ASCII characters.
For further processing, I convert these arguments using +[NSString stringWithCString:encoding:].
My problem is, that I couldn't find good information on how to determine the character-encoding used by the shell in which said cli-tool is running in.
What I came up with as a solution is the following:
NSDictionary *environment = [[NSProcessInfo processInfo] environment];
NSString *ianaName = [[environment objectForKey:#"LANG"] pathExtension];
NSStringEncoding encoding = CFStringConvertEncodingToNSStringEncoding(
CFStringConvertIANACharSetNameToEncoding( (CFStringRef)ianaName ) );
NSString *someArgument = [NSString stringWithCString:argv[someIndex] encoding:encoding];
I find that a little crude, however -- which makes me think that I missed out something obvious...but what?
Is there a saner/cleaner way of achieving essentially the same?
Thanks in advance
D
The answer depends on what the non-asciiness comes from.
In OS X, the environment variable LANG does not reflect the choice of language in the GUI. Very few people will set LANG at the command line.
The choice of the "system encoding" at the GUI is stored in ~/.CFUserTextEncoding, and can be obtained by CFStringGetSystemEncoding, see this Apple doc.
That said, this "system encoding" is rarely used except in a very old, non-unicode aware softwares. Any sane Cocoa program uses just Unicode and nothing else.
In particular, the file path at the level of Cocoa is always encoded in (a variant of) UTF-8. So, to get an NSString from a C string, use
NSString*string=[NSString stirngWithCString:cString encoding:NSUTF8Encoding];
and to get a C-string for the file path from an NSString, use
char*path=[string fileSystemRepresentation];
Here it is recommended not to use just [string UTF8String], due to the subtlety, see this Apple doc.
So, I recommend you not to care about the encoding and just assume UTF-8.
That said, there might be a very small number of people who sets LANG on the command line, and you might want to take care of them. Then, what you did is the only thing I can come up with.
Can’t you just use [[NSProcessInfo processInfo] arguments]?
Okay, it turns out there seems to be none!
As Yuji pointed out, the underlying encoding of filenames is UTF-8, no matter what. Therefore, one needed to handle two scenarios:
Arguments that are typed in, character for character, by the user.
Arguments that are tab-completed or the output of commands like ls, as they do not convert any characters.
The second case is simply covered by the assumption of UTF-8.
The first case, however, is problematic:
On Mac OS 10.6, $LANG contains the IANA-name of the used encoding like de_DE.IANA_NAME.
Prior to Snow Leopard, this is not the case for charsets other than UTF-8!
I didn't test each and every charset I could think of, but none of the european ones were included. Instead, $LANG only was the language-locale (de_DE in my case)!
Since the results of calling +[NSString stringWithCString:encoding:] with an incorrect encoding are undefined, you cannot safely assume that it will return nil in that case* (if eg. it's ASCII-only, it might work perfectly fine!).
What adds to the overall mess is that $LANG is not guarateed to be around, anyway: There's a checkbox in Terminal.app's preferences, that enables a user to not set $LANG at all (not to speak of X11.app which doesn't seem to handle any non-ASCII input...).
So what's left:
Check for presence of $LANG. If it's not set, Goto:4!
Check if $LANG contains information on the encoding. If it doesn't, Goto:4!
Check if the encoding you find there is UTF-8. If it is Goto:6, else...
If argc is greater than 2 and [[NSString stringWithCString: argv[0] encoding: NSUTF8StringEncoding] isEqualToString: yourForceUTFArgumentFlag], print that you are forcing UTF-8 now and Goto 6. If not:
Assume you don't know anything, issue a warning that your user should set the Terminal encoding to UTF-8 and may consider passing yourForceUTFArgumentFlag as the first argument and exit().
Assume UTF-8 and do what you have to...
Sounds shitty? That's because it is, but I can't think of any saner way of doing it.
One further note though:
If you are using UTF-8 as an encoding, stringWithCString:encoding: returns nil whenever it encounters non-ASCII characters in a C-String that is not encoded in UTF-8.)