3. Coding for Locale

For centuries, magicians have intuitively taken advantage of the inner workings of our brains.

Neil deGrasse Tyson

This chapter covers classes in reference to locales. We look at “inner workings” and get a better understanding of Objective-C classes and their associated methods and properties that are available to do “international magic.” I phrase it as “magic” because these classes can make it so easy to have our information automatically adjust correctly to our users’ regional settings. So many things we do not need to keep track of because they’re are taken care for us. Presto! We’re going under the hood. This is going to be classes that are specifically related to internationalization, which includes NSLocale. This class is our one-stop shopping provider for all things international. It allows us to get the user’s current language and regions settings, as well as setting them within code.

This chapter also covers other classes that have specific locale instance and class methods. These classes are not described in detail; that’s what the Apple docs are for. Here, we call out their specific locale implications and give examples of them in action. The following lists a brief description of these classes’ capabilities. Classes with locale implications include the following:

Image NSNumberFormatter—This respects how regions display numbers including the decimal character, thousands separator, and currency symbol.

Image Address Book Framework—The order of displaying contact names is locale dependent. For the majority, names are displayed with the person’s first name displayed first, followed by the last name. For locales such as Japan, this order is reversed. The Address Book Framework automatically takes this into account and correctly displays the order of your contact names.

Image NSDateFormatter—This is a powerful class that automatically formats your displayed dates correctly for the user’s current locale settings.

Image NSDate—There is more of a requirement to use NSDateFormatter because it is the heavy lifter for automatically setting the proper date format for the current locale. The NSDate class by itself is not locale-aware because it has no concepts of time zones or date formats.

Image NSTimeZone—A time zone is defined as a governmental geopolitical region. The “time” within this region is calculated as an offset from Greenwich Mean Time (GMT), the world’s reference time zone. The NSTimeZone allows you to return the current time for your provided locale based on its offset from GMT. Its return values can also correctly reflect whether daylight saving time is in effect.

Image NSCalendar—This class allows you to work with calendar types including Gregorian, Buddhist, and Japanese.

Image NSString—This is one of the most commonly used Objective-C classes. Its locale power includes comparison, sorting, and capitalization.

NSLocale Class

NSLocale is a one-stop-shopping class for accessing conventions about language and culture for a specified locale. A locale can be thought of as an area associated with particular characteristics, cultural norms, and linguistics of a particular group of people, including these:

Image Language

Image Keyboards

Image Number, date, and time formats

Image Currency

Image Sorting

Locale ID

A locale ID is an identifier consisting of a two-letter language code and a two-letter region code with each code separated by an underscore. The language code is typically lowercase, and the region code is in uppercase. Some sample locale designators include “en_US,” “fr_FR,” “ja_JP,” and “ru_RU.” The two codes reflect what international settings the customer has enabled and therefore do not necessarily need to be “associated” with each other. By this, I mean if the region is set to United States, the language does not need to be and is not automatically set to English (en). An example of this scenario is a customer who is a native Japanese speaker and living in the United States. She has her iPhone’s International settings with the language set to Japanese and the region to United States. Her locale identifier would be “jp_EN.”


Heads Up!

Some of the language and region identifiers are going to be listed in their native language name. For example, the language code for Spanish is es as in Español, the region code for Spain is ES, the language code for German is de as in Deutsch, and the region code for Germany is DE.


The following code snippet returns all 642 available locale designators:

NSArray *localeArray = [NSLocale availableLocaleIdentifiers];

Table 3.1 lists the language and region identifiers for iOS’s supported languages.

Image
Image

Table 3.1 Locale Language and Region Designators


Note

Most combinations of language and region are four-letter combinations. There are also scenarios with a six-letter combination, when there is a third variant. Remember, the system setting under iOS is a separate language and then region. Some regions, such as China, have a subset to choose from, including “Hong Kong SAR China (Simplified).” Choosing this setting results in a locale identifier of “zh-Hans_HK.”


NSLocale Component Keys

NSLocale component keys return locale-specific information. Table 3.2 lists constant values used by objectForKey. It gives a brief description of each and, in some cases, the code to get the string representation of it. Here is the code snippet to return the values:

[[NSLocale currentLocale]objectForKey: NSLocaleCountryCode]

Image
Image

Table 3.2 Locale Constants

Table 3.3 includes examples of beginning and ending quotation mark characters for the locales of U.S., Germany, and Japan. Note that all three strings are the same phrase, translated for the locale’s main language.

Image

Table 3.3 Locale Text Samples with Beginning and Ending Quotes

Return Types from NSLocale’s Component Keys

The return types for all components are NSStrings except for NSLocaleCalendar and NSLocaleExemplarCharacterSet. These two components return NSCalendar and NSCharacterSet, respectively. Listing 3.1 shows working with these two components to return NSStrings as well. The other component to note is NSLocaleUsesMetricSystem, which has a return type of NSNumber.

Listing 3.1 Logging Out of a Given Character Set


-(void) logCharacterSet:(NSCharacterSet*)characterSet{
    unichar charsetCharacters[10];
    int index = 0;

    // Loop through Unicode values
    for (unichar ucValue = 0; ucValue < (0xFFFF); ucValue ++)
    {
        if ([characterSet characterIsMember:ucValue]){
            charsetCharacters[index] = ucValue;

            index ++;

            if (index == 5){
                NSString * characters = [NSString stringWithCharacters:charsetCharacters length:index];
                NSLog(@"%@", characters);

                index = 0;
            }
        }
    }

    if (index != 0){
        NSString * charactersFromLocale = [NSString stringWithCharacters:charsetCharacters length:index];
        NSLog(@"%@", charactersFromLocale);
    }
}


To get the string rep of the current locale calendar, take advantage of the calendarIdentifier: method:

[[[NSLocale currentLocale] objectForKey: NSLocaleCalendar] calendarIdentifier];

Auto Updating the Locale

The NSLocale class contains a class method, autoUpdatingCurrentLocale, which will update your running app to reflect the current locale settings. The idea is that while your app is running, backgrounded, if the user changes the international settings, say, United States to France, then when you foreground your app, the new settings are reflected. In your code, you essentially replace currentLocale, which works with a cached version of the locale stored on launch, with autoUpdatingCurrentLocale.


Heads Up!

My experience is that autoUpdatingCurrentLocale doesn’t work 100% as advertised. In some cases, my locale changes were not reflected in my running app until I foregrounded a couple of times. Your mileage may vary, and you might want to trigger a redraw when observing the notification.


Another option is to set up an observer that is watching for the NSCurrentLocaleDidChangeNotification notification. Listing 3.2 updates the country setting.

Listing 3.2 Adding Observer to Update the Country Setting


- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(refreshCountryCode) name:NSCurrentLocaleDidChangeNotification
object:nil];
}

-(void) refreshCountryCode {
NSLocale *locale = [NSLocale currentLocale];
NSString *country = [locale objectForKey: NSLocaleCountryCode];
NSLog(@"Country : %@",code);
}


Address Book Framework

Three of the languages iOS supports work with contact names differently than the rest. For the Chinese (both Simplified and Traditional), Japanese, and Korean languages, contacts are referenced as starting with the last name, followed by the first name. This occurs with both Latin-based characters and characters from the given languages. For example, given the name “George Washington” and running your app with the language set to Japanese, this contact would display as “Washington George.”

To automatically handle this situation within code, we want to take advantage of the Address Book Framework and reference the record of that contact via ABRecordRef. If your application needs to support the proper display of names but doesn’t necessarily need to work with the OS address book directly, you can create a new record to store the contact information and correctly display it. Listing 3.3 demonstrates this task.

Listing 3.3 Contact Name Order with Respect to Locale


-(void)createNewContactRecord{
NSString *firstName = @"George";
NSString *lastName = @"Washington";

ABRecordRef  record = ABPersonCreate();
ABRecordSetValue(record, kABPersonFirstNameProperty, (__bridge CFStringRef)firstName,
NULL);
ABRecordSetValue(record, kABPersonLastNameProperty, (__bridge CFStringRef)lastName,
NULL);

NSString *displayName = (__bridge_transfer NSString
*)ABRecordCopyCompositeName(record);
NSLog(@"Display name is: %@", displayName);
CFRelease(record);
}


Note the following when working with the Address Book Framework:

Image You need to import the framework in your app’s source file: #import <AddressBookUI/AddressBookUI.h>.

Image The swapping of names is based on the system’s language setting, not region setting.

Image We need to work with Core Foundation strings and need to convert an NSString to a CFStringRef; you have to bridge it using (__bridge CFStringRef)firstName. If you are familiar with casting variables, it is similar to that.

Image Because we created a new record via ABPersonCreate(), we need to remember to release it after we’re done via CFRelease(record).

Table 3.4 lists the results of this code with various languages.

Image

Table 3.4 Contact Name Display via the Address Book Framework

NSNumberFormatter

An NSNumberFormatter object deals with how the textual representation of numbers will appear. This class conveniently converts between NSNumbers and NSStrings, giving localized representations of each. The constants with the direct locale implications include the following:

Image NSNumberFormatterDecimalStyle

Image NSNumberFormatterCurrencyStyle

Image NSNumberFormatterPercentStyle

Image NSNumberFormatterSpelledOutStyle

The first three constants are self-explanatory, and I want to quickly cover the NSNumberFormatterSpelledOutStyle. What gets spelled out is the supplied number. If that number is “25,” the result of the formatting with that constant is “twenty-five.” One of the most powerful functions of the number formatter is its respect for locales. When a locale is applied, the results from the localizedStringFromNumber:numberStyle: method reflects the locale settings. (See Listing 3.4 for code and results.)

Listing 3.4 NSNumberFormatter Example


-(void)numberFormatting{
NSNumber *number = @15234.89;
NSString *numberStr = [number stringValue];
NSString *currencyNumber = [NSNumberFormatter localizedStringFromNumber:number
numberStyle:NSNumberFormatterCurrencyStyle];
NSString *decimalNumber = [NSNumberFormatter localizedStringFromNumber:number
numberStyle:NSNumberFormatterDecimalStyle];
NSString *percentSymbol = [NSNumberFormatter localizedStringFromNumber:number
numberStyle:NSNumberFormatterPercentStyle];
NSString *spelledOutNumber = [NSNumberFormatter localizedStringFromNumber:number
numberStyle:NSNumberFormatterSpellOutStyle];

NSLog(@"Formatting applied to the number '%@': Currency format:
%@ Decimal format: %@ Percent format: %@ Spelled Out format: %@ ",
numberStr,currencyNumber,decimalNumber,percentSymbol,spelledOutNumber);
}


For a United States locale, this returns the following:

Formatting applied to the number '15234.89':
Currency format: $15,234.89
Decimal format: 15,234.89
Percent format: 1,523,489%
Spelled Out format: fifteen thousand two hundred thirty-four point eight nine

If we change the system region settings to Sweden and run the code, since we are using number formatter objects, the locale formats are respected and properly reflected:

Formatting applied to the number '15234.89':
Currency format: 15 234:89 kr
Decimal format: 15 234,89
Percent format: 1 523 489 %
Spelled Out format: femton-tusen två-hundra-trettio-fyra komma åtta niosss

And Arabic, Egypt:

Formatting applied to the number '15234.89':
Currency format: Image
Decimal format: Image
Percent format: Image
Spelled Out format: Image

And finally Japan:

Formatting applied to the number '15234.89':
Currency format: Image15,235
Decimal format: 15,234.89
Percent format: 1,523,489%
Spelled Out format: Image

NSDate

An NSDate object represents a single point in time. It provides functionality for creating, comparing, and representing dates and calculating time intervals. For our locale work, we just need to work with an NSDate object enough to supply that date to our NSDateFormatter! Here are some examples of working with and creating NSDate objects. Note that the return values from the NSDate object are always represented with GMT time offset.

NSDate *now = [NSDate date];
NSDate *dateCreated = [[NSDate alloc] init];

We do have one locale convenience method for NSDate, descriptionWithLocale:. The following code snippet returns a string similar to “Saturday, August 11, 1990 at 1:26:55 PM Pacific Daylight Time,” with other locale results shown in Table 3.5.

NSString *localeDateString = [date descriptionWithLocale:[NSLocale currentLocale]];

Image

Table 3.5 Results from the NSDate descriptionWithLocale Method

All for free. Nothing hard-coded. System provided.

NSDateFormatter

This is a sweet class. It allows you to take a string, turn it into a date object, and do whatever you need to do with a date. It will also take a date object and covert it to string, allowing you to do the things you need to do and can only do with a string. You can take a string that is a human-readable date, convert it to a date object, and then apply the specific date formatting to it. This allows you to take a string from any source, convert it to a date object, and apply the specific date formatting to it. This date formatting can be completely customized, or you can use predefined formatting styles. Note that if you parse a date string that does not explicitly call out a time zone offset, you need to set the time zone property of the date formatter object. If this isn’t specified, the assumption is made to use the device’s locale setting for the time zone. We’ll go into greater detail about handling time zones in the upcoming “NSTimezone” section.

Predefined Styles

The five available NSDateFormatter class style constants are described in Table 3.6.

Image

Table 3.6 Date Format Constants

These are used with both the setDate: and the setTime: instance methods. Let’s take a look at both of these methods as well as the setLocale: method. Listing 3.5 applies a Chinese locale and returns appropriate date formats for the OS current date and time based on Table 3.6 entries.

Listing 3.5 Demoing Date Format Constants


-(void)workWithDateFormatConstants{

    NSDate *date = [NSDate date];
    NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:@"zh_Hant"];

    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
   [dateFormatter setLocale:locale];

    NSString *dateAsString = [[NSString alloc] init];
    NSString *timeAsString = [[NSString alloc] init];

    int dateStyle[] = {NSDateFormatterShortStyle, NSDateFormatterNoStyle,
NSDateFormatterFullStyle, NSDateFormatterLongStyle, NSDateFormatterMediumStyle};
    int timeStyle[] = {NSDateFormatterShortStyle, NSDateFormatterNoStyle,
NSDateFormatterFullStyle, NSDateFormatterLongStyle, NSDateFormatterMediumStyle};

    for (int i = 0; i < 5;i++){
        [dateFormatter setDateStyle:dateStyle[i]];
        dateAsString = [dateFormatter stringFromDate:date];
        NSLog(@"Date: %@", dateAsString);
    }

    for (int i = 0; i < 5;i++){
        [dateFormatter setTimeStyle:timeStyle[i]];
        timeAsString = [dateFormatter stringFromDate:date];
        NSLog(@"Time: %@", timeAsString);
    }
}


This returns the following:

Date: 2014/8/7
Date:
Date: Image
Date: Image
Date: 2014/8/7
Time: 2014/8/7 Image1:47
Time: 2014/8/7
Time: 2014/8/7 Image
Time: 2014/8/7 GMT-7Image
Time: 2014/8/7 Image1:47:46

Format Specifiers

In creating custom date formats, we need to work with format specifiers. Table 3.7 lists out the available date components and the specifier syntax to use with a given component. For example, if you want to include the day of the week when displaying a date, you would need to include the "E" specifier. Looking at a custom date example of MM/dd/yy, we expect the date to be formatted with a two-digit month followed by the day and then year, such as 09/22/04. Format specifiers are straightforward for the most part, in that uppercase “M” is used to work with months, the lowercase “m” for minutes. Again looking at Table 3.7, the Comments column describes what the results are when you specify various numbers of characters.

Image
Image

Table 3.7 Date Format Specifiers

I’ve included a couple of code snippets showing these format specifiers in action. The first snippet is used to create the date string “Saturday November 8, 2008”:

NSDate *today = [NSDate date];
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:@"EEEE MMMM d, YYYY"];
NSString *dateString = [dateFormat stringFromDate:today];

And this snippet returns a date format matching “8:02 AM, PST”:

NSDate *today = [NSDate date];
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:@"h:mm a, zzz"];
NSString *dateString = [dateFormat stringFromDate:today];

Conversion

I’ve mentioned that the date formatter will allow us to convert from a date to a string and vice versa. Let’s see this in action. The first code sample sets a date format for the date formatter object as @"MM-dd-yyyy" to match the format of our supplied date string @"08-27-2014".


Heads Up!

This is a critical step in converting a string into a date object. The date format must match the format of the string.

-(void)dateFromAString{
    NSString *dateString = @"08-27-2014";
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    // First you have to convert the string to a date object and that date format needs
to match
    [formatter setDateFormat:@"MM-dd-yyyy"];
    NSDate *dateFromDateString = [formatter dateFromString:dateString];
}



Note

The next chapter takes advantage of the preceding code to work with a date fetched from a Web site. The return value from our API call will be a string that we will convert to a date object. After that conversion is completed, we can then automatically have the correct date format displayed for the given locale.


And now we have a date object, dateFromDateString, which we can use for date calculations, manipulate with date components, and so on.

Now, let’s take the same code, but take our newly created date and apply a different format to it. To be able to do this, from starting with a string, we need to take that string, convert it to a date object, and then apply the new format to it (see Listing 3.6). The added code is bold.

Listing 3.6 Applying a New Date Format


-(void)dateFromAString{
    NSString *dateString = @"08-27-2014";
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    // First you have to convert the string to a date object
   //and that date format needs to match
    [formatter setDateFormat:@"MM-dd-yyyy"];
    NSDate *dateFromDateString = [formatter dateFromString:dateString];

    // Now convert it to desired format
    [formatter setDateFormat:@"MMMM d, y"];

    NSString *stringFromDate = [formatter stringFromDate:dateFromString];

    NSLog(@"Date is: %@", dateFromString);
    NSLog(@"Date from string is: %@", stringFromDate);
}


Date Templates

The NSDateFormatter class contains a class method named dateFormatFromTemplate:. This method returns a date format based on a specified locale as an NSString. What makes this method useful is that the returned string properly represents the locale-specific adjustments. One of these adjustments could be if the locale displays its date with the day followed by the month. This returned template provides you information about how the locale represents dates and can be used and applied with an instance of the NSFormatter object.

Listing 3.7 takes in a locale as an argument and returns whether the month precedes or follows the day.

Listing 3.7 Sample Date Template to Display Month–Date Order


-(NSString *)localeMonthOrder:(NSString *)localeIdentifier{
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:localeIdentifier];
return [NSDateFormatter dateFormatFromTemplate:@"MMMMd" options:0 locale:locale];
}


Providing locales of “en_US” and “fr_FR” to this method gives the results of “MMMM d” and “d MMMM,” respectively.

Date Format "j" Template

We’ve got a special template called @"j" that will return a specified locale’s time format. What is returned is the time format specifiers “H” or “a,” where “H” represents the hour (1–12 or 0–23) and “a” represents whether the locale displays time with “AM” and “PM.” This code snippet shows it in action, for a Japanese locale:

NSLocale *jLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"jp_JP"];
NSString *jTemplateTimeFormat = [NSDateFormatter dateFormatFromTemplate:@"j" options:0
locale:jLocale];
NSLog(@"Time format: %@",jTemplateTimeFormat);

This returns Time format: h a, meaning time is displayed with the hour from 1 to 12 and is displayed with AM/PM. Table 3.8 lists the return formats for several locales.

Image

Table 3.8 Returning Time Format for Specified Locales


Heads Up!

The @"j" template will be most useful for verifying that your custom date templates used with date formatters are returning correct values.


NSCalendar

Chapter 1, “International Settings,” introduced us to the calendars supported by iOS, which are Buddhist, Gregorian, and Japanese. Each of these calendars has properties such as number of months in a year, number of days in a month, first day of the week, and so on. The NSCalendar class contains the mapping of an NSTimeInterval and understands date calculations such as, “What is the calendar date 3 days from now?” The NSCalendar object is required to give meaning to these components by defining the exact length of a year, month, week, and so on.

The type of calendar can be specified when you are initializing a new calendar object. Listing 3.8 gives examples of creating Buddhist, Gregorian, and Japanese calendars.

Listing 3.8 Creating Calendars


NSCalendar *buddhist = [[NSCalendar alloc]
initWithCalendarIdentifier:NSBuddhistCalendar];
NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
NSCalendar *japanese = [[NSCalendar alloc]
initWithCalendarIdentifier:NSJapaneseCalendar];
NSLog(@"%@", buddhist.calendarIdentifier);
NSLog(@"%@", gregorian.calendarIdentifier);
NSLog(@"%@", japanese.calendarIdentifier);


This returns the current calendar setting:

NSCalendar *current = [NSCalendar currentCalendar];
NSLog(@"%@", preferred.calendarIdentifier);

Another option to initial your app’s calendar is initializing the locale via the NSLocale class. This sets the language to English (en), the region to United States (US) and the calendar to Gregorian for the NSLocale object; it doesn’t touch the system settings.

NSLocale *customLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"
en_US@calendar=gregorian"];

NSDateComponents

NSDateComponents will take the components of a date (day, month, year, etc.) and encapsulate them into extensible objects. Or in other words, they allow you to pull components from a given complete date and, more important, allow you to easily do date math when used in conjunction with the NSCalender class. Let’s take a look at first creating components from a date, then creating a date object from given components, and then we’ll look at an example of date math.

Components from a Date

Listing 3.9 shows how to convert an NSDate into a NSDateComponents object.

Listing 3.9 Converting a Date to Individual Components


-(void)datesToComponents{
    NSDate *now = [NSDate date];
    NSCalendar *calendar = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *components = [calendar components:NSYearCalendarUnit |
NSMonthCalendarUnit | NSDayCalendarUnit fromDate:now];
    NSLog(@"Dates To Components");
    NSLog(@"Day: %ld", (long)[components day]);
    NSLog(@"Month: %ld", (long)[components month]);
    NSLog(@"Year: %ld", (long)[components year]);
}


The code returns this:

Day: 22
Month: 9
Year: 2004

That’s great and very straightforward in allowing us to grab the day, month, and year components. But iOS supports more than the Gregorian calendar. What results do we get with Buddhist and Japanese calendars?

Change the calendar type to NSBuddhistCalendar, and the results are as shown here:

Day: 22
Month: 9
Year: 2547

Finally, setting the calendar to NSJapaneseCalendar gives these results:

Day: 22
Month: 9
Year: 16

Now, let’s look at the reverse example, starting with components and producing a date (see Listing 3.10).

Listing 3.10 Converting Components to a Date


-(void)componentsToDates{
     NSCalendar *calendar = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
    NSDateComponents *components = [[NSDateComponents alloc] init];
    [components setYear:2014];
    [components setMonth:7];
    [components setDay:19];

    NSDate *date = [calendar dateFromComponents:components];
    NSLog(@"%0.0f seconds between Jan 1st, 2001 and July 19th, 2014", [date
timeIntervalSinceReferenceDate]);
}


Relative Date Calculations

For date calculations, you need to use a combination of components and calendars, specifically the NSCalendar dateByAddingComponents:toDate:options: method. The following example determines a future date by adding three weeks and three days to a given date:

-(void)dateCalculating{
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd"];

NSDate *date = [[NSDate alloc] init];
date = [dateFormatter dateFromString:@"2004-09-22"];

NSCalendar *calendar = [NSCalendar currentCalendar];

NSDateComponents *components = [[NSDateComponents alloc] init];
[components setWeekOfMonth:3];
 [components setDay:3
NSLog(@"3 weeks, 3 days from given date: %@", [calendar
dateByAddingComponents:components toDate:date options:0]);
}

This code returns 3 weeks, 3 days from given date: 2004-10-16.

NSTimeZone

The NSTimeZone class explains the behavior of a time zone object, in which these objects represent geopolitical regions. Time zone objects can also represent offsets from Greenwich Mean Time.

An NSDate represents a concrete point in time, regardless of the time zone. Put another way, an NSDate does not have a time zone. Time zones are relevant only when you want to display the date to the user. So 9:30pm in Mountain Time is 3:30am (+1 day) in GMT (assuming a six-hour time difference).

NSDate, because it does not have a time zone, must pick one when producing a human-readable version to return as its description. To make things simple, it always returns a date formatted in the GMT time zone. If you want the date formatted to be in a different time zone, you can set the timezone property of an NSDateFormatter and then convert the date into a string using the stringFromDate: method.


Note

You will also see references to UTC, which stands for Coordinated Universal Time. It is informally known as Zulu time and was created in an effort to deal with time zone confusion. UTC and GMT times are synonymous.


The NSTimeZone class becomes important when, obviously, we are dealing with dates that include the time. We need to take into consideration what the user’s current locale time zone is and not assume any kind of absolute time. For example, if we have a date—and again, as far as the OS is concerned about a date, it is a point in time and not a box on a paper calendar, not a position of hands on a clock face—of 2014-02-02 8:35 PM with no time zone specified, it will represent the same human-readable time regardless of the user’s locale.

The following code snippet returns an array of all the known time zone names. One of the most useful aspects of the returned values is the official names of the time zones. An example is the time zone for Perth, which is actually Australia/Perth.

NSArray *knownTimeZoneArray = [NSTimeZone knownTimeZoneNames];
NSLog(@knownTimeZoneArray = %@, knownTimeZoneArray);

Table 3.9 is a small sampling of all the available time zone names. Currently, there are more than 420 time zone names.

Image

Table 3.9 Sample of Time Zone Names

Table 3.10 is a sampling of time zone abbreviations. Currently, there is a total of 48 abbreviations.

Image

Table 3.10 Sample of Time Zone Abbreviations

The code snippet to generate the entire list is as follows:

NSLog(@"Time zone abbreviations: %@", [NSTimeZone abbreviationDictionary]);

Let’s look at some time zones, specifically the time in Billings, Montana, and the time in Perth, Australia (see Listing 3.11). We’re setting the time zones to each of these cities. Remember, remember, remember: The value that is returned is in the GMT time zone. So when we set the date/time for 8:00 PM in Billings, the return value is the time it is in Greenwich.

Listing 3.11 Returning Time Zones from Specified Locales


-(void)timeZones{

// UTC/GMT -7 hours
NSTimeZone *mountainStandardTime = [NSTimeZone timeZoneWithAbbreviation:@"MST"];

// UTC/GMT +8 hours
NSTimeZone *perthTime = [NSTimeZone timeZoneWithName:@"Australia/Perth"];

NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
// Hard coding values below to work with known values
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];

[formatter setLocale:locale];
[formatter setDateFormat:@"yyyy-MM-dd h:mm a"];
NSString *dateString = @"2014-02-02 8:35 PM";

[formatter setTimeZone:mountainStandardTime];
NSDate *eightPMInBillings = [formatter dateFromString:dateString];
NSLog(@"GMT w/ Billings timezone: %@", eightPMInBillings);

[formatter setTimeZone:perthTime];
NSDate *eightInPerth = [formatter dateFromString:dateString];
NSLog(@"GMT w/ Perth timezone: %@", eightInPerth);
}


Listing 3.11 returns 2014-02-03 03:35:00 for Billings, 2014-02-02 12:35:00 for Perth.

To keep things interesting, let’s do some date math and find out what the time difference is between Billings, Montana, and Perth, Australia (see Listing 3.12). The added code appears in bold.

Listing 3.12 Calculating Time Difference Between Two Locales


-(void)timeZones{
// UTC/GMT -7 hours
NSTimeZone *mountainStandardTime = [NSTimeZone timeZoneWithAbbreviation:@"MST"];

// UTC/GMT +8 hours
NSTimeZone *perthTime = [NSTimeZone timeZoneWithName:@"Australia/Perth"];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
// Hard coding values below to work with known values
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
 [formatter setLocale:locale];
[formatter setDateFormat:@"yyyy-MM-dd h:mm a"];
NSString *dateString = @"2014-02-02 8:35 PM";

[formatter setTimeZone:mountainStandardTime];
NSDate *eightPMInBillings = [formatter dateFromString:dateString];
NSLog(@"GMT w/ Billings timezone: %@", eightPMInBillings);

[formatter setTimeZone:perthTime];
NSDate *eightInPerth = [formatter dateFromString:dateString];
NSLog(@"GMT w/ Perth timezone: %@", eightInPerth);

//Date math to calculate hours.
NSCalendar *calendar = [[NSCalendar
alloc]initWithCalendarIdentifier:NSGregorianCalendar];

NSDateComponents *components = [[NSDateComponents alloc] init];

if ([eightPMInBillings compare:eightInPerth] == NSOrderedAscending) {
components = [calendar components:NSHourCalendarUnit fromDate:eightPMInBillings
toDate:eightInPerth options:0];

}else if ([eightPMInBillings compare:eightInPerth] == NSOrderedDescending) {
components = [calendar components:NSHourCalendarUnit fromDate:eightInPerth
toDate:eightPMInBillings options:0];

}else{
NSLog(@"Times are same!");
return;
}

NSLog(@"Time difference is: %ld hours", (long)[components hour]);
}


The result of the added code is Time difference is: 15 hours. Note the use of the NSDate compare: method to determine which date has the greater value. This prevents a negative hour result.

NSString

The NSString class has a number of locale-specific methods, which are covered in this section. These methods are used to respect a locale’s specific rules for capitalization, sorting, and formatting. For the most part, the most powerful reason for calling these locale-specific methods is for representing strings in the UI. Great, powerful, built-in functionality does the heavy-lifting formatting for us.

Initializing a String

Initializing a string is a way to create a string object. As far as initializing an NSString object, using the initWithFormat:locale: method gives you flexibility to be able to specify a locale as part of that initializing.

Listing 3.13 takes two inputs, a float number and a locale identifier. The logged-out result will show that the initWithFormat:locale: method automatically respects the locale settings.

Listing 3.13 NSString initWithFormat:locale: Example


-(void)initFormattingTest:(float)num localeIdentifier:(NSString *) identifier{
NSLocale *testLocale = [[NSLocale alloc] initWithLocaleIdentifier: identifier];
NSString *formattedString = [[NSString alloc] initWithFormat:@"The number %1.3f
respects our "%@" locale setting!" locale:testLocale, num, identifier ];
NSLog(@"%@", formattedString);
}


Having an input of 4815.1623 and a “tr_TR” locale, the results are correctly formatted for that locale:

The number 4.815,162 respects our "tr_TR" locale setting!

As far as supporting the number format for a given locale, the functionality that NSNumberFormatter gives us is more powerful than this example of initializing a string. This example is useful in that it shows that locale support is available in a wide variety of classes. It is also a demonstration of adding a variable to a string.

If you thought that was fun, we can get similar results combining a date:

NSLocale *testLocale = [[NSLocale alloc] initWithLocaleIdentifier: @"tr_TR"];
NSDate *date = [NSDate date];
NSString *formattedStringWithDate = [[NSString alloc] initWithFormat:@"The date for the
"%@" locale setting is: %@" locale:testLocale, identifier, date];
NSLog(@"%@", formattedStringWithDate);

This gives a result of

The date for the "tr_TR" locale setting is: 4 Ağustos 2014 Pazartesi 23:33:54 Kuzey
Amerika Pasifik Yaz Saati

all automatically given to us, ready to be displayed.

Lower- and Uppercase

Which character is used as the representative of uppercase and lowercase can be dependent on locale. The occurrences of this are rare, and it is easy to guard your code against ending up with the wrong character. Listing 3.14 takes arguments of a string and locale identifier and logs out the nonlocale and locale results.

Listing 3.14 uppercaseStringWithLocale Example


-(void)capitalizedTestString:(NSString *)string localeIdentifer:(NSString
*)identifier{
NSLocale *testLocale = [[NSLocale alloc] initWithLocaleIdentifier: identifier];
NSString *result = [string uppercaseString];
NSString *resultWithLocale = [string uppercaseStringWithLocale:testLocale];

NSLog(@"No locale argument: %@", result);
NSLog(@"With locale argument: %@", resultWithLocale);
}


Some of the typical results are inputting “english” for a locale identifier of “en_EN” and having “ENGLISH” logged out. Inputting the German word “straße” with a locale identifier of “de_DE” is a little more interesting because the capital form of the character B is “SS,” so our logged-out value is “STRASSE.” The more interesting case is working with the Turkish character for the Latin “I.” Having an input value of “icicle” and a locale identifier of “tr_TR” gives us a nonlocale result of “ICICLE,” which would be incorrect for a Turkish locale. The correct result for a Turkish locale would be “İCİCLE.”

Two other methods to call out here that both respect the locale setting, lowercaseStringWithLocale: and capatalizedStringWithLocale:, should be self-explanatory; “lower” will return the locale-appropriate characters for lowercase, and “capital” will change the first character of each word in a supplied string to uppercase. For example, the string “steve owns an imac” would be changed to “Steve Owns An Imac.”

Searching

We have different options available when searching for substrings within strings. I want to walk you through some locale-specific scenarios of working with the rangeOfString: options:range:locale: method. Obviously, with this method, we have a locale option, which is of highest interest for us, but even more interesting than that is the options option.

These options take an NSStringCompareOptions type argument. The constants are a bitwise mask and can be combined.

We’ll take a detailed look at code examples for the following options:

Image NSCaseInsensitiveSearch

Image NSDiacriticInsensitiveSearch

Image NSWidthInsensitiveSearch

NSCaseInsensitiveSearch

This enum is self-explanatory: Ignore the case when searching for the substring within a string. This also includes locales that use different characters entirely. Listing 3.15 is a custom method that takes three arguments, a source string, a search string, and a locale identifier and returns YES if the search string was found. Or more precisely, if the length of the range is greater than zero, the search string was found.

Listing 3.15 Sample Code for the Locale Options of Searching Strings


-(BOOL)searchingString:(NSString *)sourceString searchString:(NSString *)searchString
options:(NSStringCompareOptions)options localeIdentifer:(NSString *)localeIdentifier{
NSRange range = NSMakeRange(0, [sourceString length]);
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:localeIdentifier];
if ([sourceString rangeOfString:searchString options:options range:range
locale:locale].length > 0){
        NSLog(@"Substring found.");
        return YES;
    }else{
        NSLog(@"No substring found in source string.");
        return NO;
    };
}


Looking at the German example of the word “straße,” the uppercase representative of the character “ß” is “SS.” If we were to call our sample code as [self searchingString:@"straße" searchString:@"SS" options:NSCaseInsensitiveSearch localeIdentifer:@"de_DE"]; the result would be true. Via this option, the case is ignored given the equivalence between “ß” and “SS.”

NSDiacriticInsensitiveSearch

With this option, characters that have diacritics, or accents, are treated as equivalents to characters that do not. So “a” is equivalent to “à,” “e” to “ê,” “u” to “ü,” and so on. Again, leveraging our sample code and calling it with the parameters [self searchingString:@"Fußgängerübergänge" searchString:@"Fußgangerubergange" options: NSDiacriticInsensitiveSearch localeIdentifer:nil]; would give a result of true. Note in this case that the locale is not critical in this evaluation, so it is set to nil.

Now let’s look at a locale-specific diacritic search. So when the locale is set to Turkish (tr_TR), it will not find a match when searching the string “Işık” for a lowercase Latin “i.” The dotless “i” is not a diacritic, but a bona fide individual character. If you set the locale to “en_US,” it will return true.

NSWidthInsensitiveSearch

This option will ignore the width characteristics of certain characters and match them with “equivalent” Latin characters. An example would be searching for “e” (LATIN SMALL LETTER E; U+0065) with a source string of “e” (FULLWIDTH LATIN SMALL LETTER E; U+FF45). Width characters are part of East Asian character sets. Note that these are separate characters with completely unique Unicode values, but to the reader they appear identical.


Heads Up!

Searching for strings is not a commonplace operation, but anytime you are searching for substrings, keep these constants in mind, especially when capitalization and diacritics come into play.


Folding Strings

Folding is the Unicode mechanism of converting words to a form—typically to lowercase—to allow case-insensitive comparisons.

A prime example of locale having an influence on this option is our trusty friend the Turkish “I.” We’ll look at the Turkish word “IŞIK” in all uppercase, using the dotless “I” character U+0130. When the locale is set to “en_US,” the result is “işik,” which uses the Latin character “i” (U+0069). With locale set to “tr_TR,” the result is “ışık,” using the dotless “i” character U+0131. Listing 3.16 gives a code example.

Listing 3.16 String Folding Example—Turkish “I”


-(void)folding{
    NSString *inputString = @"IŞIK";
    NSLog(@"Original string: %@", inputString);
    NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:@"tr_TR"];
    NSString *result = [inputString stringByFoldingWithOptions:NSCaseInsensitiveSearch locale:locale];
    NSLog(@"Unfolded string: %@", result);
}


Another common example of working with folding is with single ligature characters.

Folding these characters will return their single-character representatives. Listing 3.17 is working with the character ffl (U+FB04). It will unfold a ligature single character into the individual characters “f” and “f” and “l.” You will also see this referred to as “normalizing.”

Listing 3.17 String Folding Example with Ligature Character


-(void)folding{
    NSString *inputString = @"ffl";
    NSLog(@"Original string: %@", inputString);
NSString *result = [inputString stringByFoldingWithOptions:NSCaseInsensitiveSearch
locale:nil];
    NSLog(@"Unfolded string: %@", result);
}


Sorting

Each locale has its own “alphabetic order,” and we want to respect that in our apps when we’re working with sorted lists. These can be alphabetic lists of anything with the typical example being a list of contacts sorted by last name.

Let’s look at sorting with the Polish locale as an example. Given a sample list of text, Ucho, Labirynt, Łódź, Magia, sorting this list in alphabetic order would give the following result: Labirynt, Łódź, Magia, Ucho. Now sorting the same list with the locale set to “US” would give this result: Labirynt, Magia, Ucho, Łódź. Keep this in mind when your code is to include sorting.

When you do need to do sorting, there are a number of approaches you can take. I suggest the most straightforward one I have found, which is using the NSString localizedCompare: method. (There is a localizedCaseInsensitiveCompare: method when case is not a criterion for your sort.) This is a localized version of the compare: method. Listing 3.18 demonstrates this.

Listing 3.18 Sorting with localizedCompare:


-(void)sortingLocalizedCompare{
NSArray *plArray = @[@"Labirynt", @"Łódź", @"Magia", @"Ucho"];

NSArray *sortedArray= [NSArray new];
NSArray *sortedArrayLocalized= [NSArray new];

sortedArray= [plArray sortedArrayUsingSelector:@selector(compare:)];

sortedArrayLocalized= [plArray
sortedArrayUsingSelector:@selector(localizedCompare:)];

NSLog(@"Sorted: %@", sortedArray);
NSLog(@"Sorted - Localized: %@", sortedArrayLocalized);

    for (id obj in sortedArray) {
        NSLog(@"Sorted item, not localized: %@ ", obj);
    }
    for (id obj in sortedArrayLocalized) {
        NSLog(@"Sorted item, localized: %@ ", obj);
    }
}



Note

With Listing 3.18 “as is,” the elements in the sortedArrayLocalized array are logged out as escaped characters. The reason is that is what is logged out is the description of the array. When the description method is called, it returns a string representing the contents of the array, which is formatted as a property list. One side effect of this process is that Unicode characters are converted to NSNonLossyASCIIStringEncoding.

To rectify this situation, the description of the array needs to be encoded to UTF8, like this:

NSString *UTF8Description = [NSString stringWithCString:[[sortedArrayLocalized
description] cStringUsingEncoding:NSUTF8StringEncoding]
encoding:NSNonLossyASCIIStringEncoding];
NSLog(@"Sorting result with supplied locale: %@", UTF8Description);


The second example in Listing 3.19 uses the NSArray instance method sortedArrayWithOptions:usingComparator:. As part of that method’s comparator block, it uses the NSString class method compare:options:range:locale:, which returns an NSComparisonResult value indicating the alphabetic order. It also demonstrates the NSDiacriticInsensitiveSearch option.

Listing 3.19 Sorting via compare:options:range:locale: for a Specified Locale


-(void)sortingWithCompareAndLocale{
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:@"pl_PL"];

NSMutableArray *plArray = [[NSMutableArray alloc] initWithObjects:@"Labirynt",
@"Łódź", @"Magia", @"Ucho", nil];

NSArray *sortedWithCaseInsensitive = [plArray sortedArrayWithOptions:0
                                 usingComparator:^(NSString  *v1, NSString *v2) {
                                 return [v1 compare:v2
                                 options:NSCaseInsensitiveSearch
                                 range:NSMakeRange(0, [v1 length])
                                 locale:locale];
                                 }];

NSArray *sortedWithDiacritic = [plArray sortedArrayWithOptions:0
                               usingComparator:^(NSString  *v1, NSString *v2) {
                               return [v1 compare:v2
                               options:NSDiacriticInsensitiveSearch
                               range:NSMakeRange(0, [v1 length])
                               locale:locale];
                               }];

NSLog(@"Sorting result with supplied locale: %@", sortedWithCaseInsensitive);
    for (id obj in sortedWithCaseInsensitive) {
        NSLog(@"Sorted item: %@ ", obj);
    }
    for (id obj in sortedWithDiacritic) {
        NSLog(@"Sorted item, Diacritic Insensitive: %@ ", obj);
    }
  
}


Summary

This chapter covered the code dealing with locales. We looked at the specific locale class NSLocale and what it can provide. We looked at classes including NSNumberFormatter, NSDateFormatter, NSTimeZone, Address Book Framework, NSCalendar, and NSString, which are not specifically focused on locales, but include many convenience methods with specific locale arguments. When we take advantage of these locale arguments, the task of supporting languages and locales becomes much easier. We can code once and not need to write individual code for specific languages and locales.

We’ll take what we learned in this chapter with the small code samples and apply them to a sample project in Chapter 4, “Prepping Your App for Localization.”

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset