Working with Date and Time

Posted by James

It’s highly likely that somewhere in your app you may need to work with dates and times; whether that be parsing, displaying or performing calendrical calculations. There are some important considerations to make and most certainly a few gotchas to watch out for. This post aims to give an overview of some of the Cocoa classes available to you and some tips and tricks on working with dates and times.

At Your Disposal

NSDate

The NSDate class is essentially a wrapper for an NSTimeInterval. This NSTimeInterval is nothing more than a double used to specify the number of seconds since the reference date in time. As a double it yields sub-millisecond precision over a range of 10,000 years. An NSDate represents a point in time in relation to this reference date. The references date remains a fixed point in time but can be interpreted differently depending on the calendar.

  • Gregorian calendar, 1 January 2001 00:00:00 +0000.
  • Buddhist calendar, 1 January 2544 00:00:00 +0000.

NSTimeZone

A time zone in the world is a geopolitical region where by the government have defined a set of rules for how local time should be calculated from the reference time zone of the world (GMT). In it’s simplest terms this is just an offset by a number of seconds, but there is a lot more that NSTimeZone accounts for. Daylight saving time is one of these as well as the fact that governments can changes the rules of their time zone between different years.

NSCalendar

There are many calendars in the world; Gregorian, Buddhist, Japanese, etc. Each define arithmetic properties such as the number of months in a year and the first weekday. An NSCalendar knows the mapping of an NSTimeInterval to and from a calendar date and also how to perform calendrical calculations such as “what’s the day 10 days from now”.

NSLocale

An NSLocale encapsulates a set of default regional settings for the current user. However the user can override these settings so you should not assume them. The settings define the way date and time should be formatted when presented to the user.

NSDateComponents

An object containing a set of calendar components such as month, day, hour, etc. It’s good to remember that an NSDateComponents can represent both an absolute and relative time.

  • Absolute, 14:45 in the afternoon.
  • Relative, 14 hours and 45 minutes.

You can use an NSCalendar object to convert an NSDateComponents to an NSDate, which kindly handles unit roll overs for you. For example an instance with components 2012 years, 13 months and 5 days becomes 5th January 2013.

NSDateFormatter

An NSDateFormatter is used for converting dates both to and from human-readable form, as well as machine readable form, i.e. a string with a schema. New objects are initialised with the locale settings of the current user. There are some predefined format styles for varying length outputs, e.g. NSDateFormatterShortStyle, NSDateFormatterLongStyle, or you can customise the output with a format string.

Tips & Tricks

Calendrical Calculations

The most important tip I can give you is to use the system algorithms for calendrical calculations. Do not try and write your own algorithms, as there are a lot of specific cases to account for such as:

  • The leap day in the Gregorian calendar, 29th February. Happens mostly every four years, the next omitted leap year is 2100.
  • The leap month in the Hebrew Calendar. Months are numbered 1-13 and 7 can be a leap month.
  • Time zone transitions. A forward transition causes an hour skip and a backward transition causes an hour to occur twice. In the UK the daylight saving time happens at 01:00 or 02:00 where as in Brazil the transition is at midnight.
  • Dateline transitions. In 2011 Samoa had a time zone change and moved from one side of the international date line to the other. At the end of the day on 29th December 2011, it became 31st December 2011, skipping 30th December 2011 entirely.
  • Japanese Imperial Eras. When a new Emperor comes in to power a new era begins and the year is set to 1. This means that a year can be set back to 1 during any point in the current year.

It’s also favourable to avoid stress boundaries. If you only care about the date and not the time set it to noon instead of the default of midnight. Similarly if you only care about the time and not the date use the reference date and set your time.

Another important fact is that 1 day does not always equal 86,400 seconds! If for instance you did [date dateByAddingTimeInterval:86400] where date is 30th March 2013 11:50 PM, you will get a results of 31st March 2013 00:50 AM. The issue here being that the daylight saving time transition has occurred but was not accounted for. Instead create an NSDateComponents and set the day components [dateComponents setDay:1]. Now you can add the components and daylight saving time transition is correctly handled.

NSDate *start = ...; // start: 30th March 2013 11:50 PM
NSDateComponents *dateComponents = [[NSDateComponents alloc] init];
[dateComponents setDay:1];
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDate *end = [calendar dateByAddingComponents:dateComponents toDate:start options:0];
// end: 31st March 2013 11:50 PM

When performing calendrical calculation you should always work relative to your starting date and not to any intermediate date. For example taking 31st January, adding one month, and then adding a further month to the result would mean the following.

31st January -> 28th February -> 28th March

This is because there is no 31st February so the result returned is the last day of February. So when adding an additional month to this date the day number is preserved and we’re returned 28th March.

So as mentioned, always work relative to your starting date, and in this example add two months to 31st January to get 31st March.

31st January -> -> 31st March

Displaying Dates

To display a date to the user you’ll use an NSDateFormatter. Remember that these are always initialised with the locale settings of the current user, as well as with any customisations the user may have made.

Some user preferences override even a specifically set date format, for instance a user may prefer to see a 24-hour clock format. Setting the date format may seem like the correct way to get the output you’re after. However although the following is a valid date format for here in the UK, it isn’t for China.

[dateFormatter setDateFormat:@"dd/MM/yyyy"];

It’s always best to use the predefined format styles which will be based on the locale settings of the current user, or what they have customised in their system preferences.

[dateFormatter setDateStyle:NSDateFormatterShortStyle];

When these format styles don’t suit and you need to use a date format, use the method +dateFormatFromTemplate:options:locale: to generate a locale specific format.

NSString *format = [NSDateFormatter dateFormatFromTemplate:@"EdMMM" options:0 locale:[NSLocale currentLocale]];
[dateFormatter setDateFormat:format];
  • en_US locale generates @"EEE, MMM d" format, “Wed, Feb 20”.
  • de_DE locale generates @"EEE, d. MMM" format, “Mi., 20. Feb”.

This also works perfectly for the AM/PM of time.

NSString *format = [NSDateFormatter dateFormatFromTemplate:@"j" options:0 locale:[NSLocale currentLocale]];
[dateFormatter setDateFormat:format];
  • en_US locale generates @"h a" format, “6 PM”.
  • de_DE locale generates @"HH 'Uhr'" format, “18 Uhr”.

Parsing Dates

When parsing a date make sure you explicitly configured your NSDateFormatter with the correct calendar to ensure you get the correct result.

NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
[dateFormatter setCalendar:calendar];
[dateFormatter setDateFormat:@"yyyy-MM-dd"];
NSDate *date = [dateFormatter dateFromString:@"2013-02-20"];

When parsing fixed-format dates like those from a web server, setting the date formatter locale to en_US_POSIX often works well. This locale is specifically designed to yield US English results regardless of both user and system preferences and will remain fixed even if the US changes the way it formats dates in the future.

NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
[dateFormatter setLocale:locale];

Working with Time Zones

Ensure you set the appropriate time zone when storing and parsing a date. The time in the string 2013-02-20 13:00:00 is 1pm in GMT. But if the time zone is not correctly set and a user in New York parsing the date it will be interpreted as 1pm EST, the equivalent of 6pm GMT.

[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
[dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
NSDate *date = [dateFormatter dateFromString:@"2013-02-20 13:00:00"];

Working with Week-based Calendars

All calendars can be interpreted in a week-based fashion; a cyclic period of 7 days. There are two properties that define a week-based calendar.

  • The weekday which is the beginning of the week (and year).
  • Minimum number of days a straddling week needs in the new year to be considered the first week of that new year.

Below illustrates the different ways, including in a week-based fashion, to represent the date 20th February 2013 in the Gregorian calendar. Note: Weekday 1 is a Sunday.

  • {Year, Day} : {2013, 51}
  • {Year, Month, Day} : {2013, 2, 20}
  • {YearForWeekOfYear, WeekOfYear, Weekday} : {2013, 8, 4}

Do not mix an ordinary year number with week-based components, nor a week-based year number with ordinary components. For week based use the following NSCalendar component types.

NSWeekOfYearCalendarUnit
NSWeekOfMonthCalendarUnit
NSYearForWeekOfYearCalendarUnit

Also when using a date format string using the correct format is important.

  • @"YYYY" is week-based calendar year.
  • @"yyyy" is ordinary calendar year.

Unit Lengths

Finally NSCalendar has some methods to determine unit lengths. For instance if in your application you want to draw a calendar, you’d use the following method on your calendar object to determine how many months to draw.

NSRange months = [calendar maximumRangeOfUnit:NSMonthCalendarUnit];

You can ask for a range too, so you could use the following to determine how many days are in the month of the specified date. This also means you don’t need to hard code a leap year algorithm yourself.

NSRange days = [calendar rangeOfUnit:NSDayCalendarUnit inUnit:NSMonthCalendarUnit forDate:date];

There are a lot of complexities in date, time and calendrical calculations. Make sure you let the Foundation framework do all the heavy lifting for you and you don’t try and write your own algorithms. I hope you’ve been able to pick up a few tips and tricks and if you’ve anything else to add give me a shout on Twitter.

Cocoa Error Handling and Recovery

Posted by James

For those engineers amongst us, we’re all too familiar with handling errors in the software we develop. Here at Realmac Software we take error handling very seriously simply because it’s just so common place. Whether it be user input, networking, file systems, databases, etc, errors can easily occur. Inadequately handling these error can really impact your app and your users, leaving them with unexpected results, frustration, confusion, loss of their data and crashes.

Doing it wrong

Below is an example of how not to handle an error in your app.

NSError *error = nil;
BOOL didSave = [managedObjectContext save:&error];
if (!didSave) {
    // WARNING: Handle the error.
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
}

What’s so bad? Well, lots! Sure we’ve detected an error has occurred and have logged it to the console, but really that’s not much help at all. It’s especially not much help if your app has shipped; you can’t ask your users to download the iPhone Configuration Utility and dig through the console for these logs when they’ve experienced a problem.

Maybe the above is something quick whilst you’re developing your app. I’d recommend using breakpoints in Xcode instead of NSLog(). Breakpoints are powerful, quick and you you can add them whilst your app is running instead of having to rebuild. I also wouldn’t recommend using a WARNING: to highlight where you need to handle your errors. You should get in to the practice of handling all your errors as you go, the logic plays an important role in the development of your app. Also, leaving a whole bunch of error handling to do at the end is impractical and certainly no fun.

Most importantly we’ve not actually handled the error. We haven’t programatically inspected the error and we most certainly haven’t informed the user or asked them to choose a recovery option.

Recovery Attempting

Recovering from errors is the ultimate goal. We’ve developed our very own RMErrorRecoveryAttempter class which harnesses the power of blocks to provide recovery options for an error. The class conforms to the NSErrorRecoveryAttempting informal protocol.

The NSErrorRecoveryAttempting informal protocol provides methods that allow your application to attempt to recover from an error. These methods are invoked when an NSError object is returned that specifies the implementing object as the error recoveryAttempter and the user has selected one of the error’s localised recovery options.

Let’s assume we’ve an entry object that a user has created. The user then tries to save this entry but because they haven’t set the required date property a save error occurs. To recover from this error we’ve created an RMErrorRecoveryAttempter object that has two recovery options.

RMErrorRecoveryAttempter *errorRecoveryAttempter = [[RMErrorRecoveryAttempter alloc] init];

[errorRecoveryAttempter addCancelRecoveryOption];

[errorRecoveryAttempter addRecoveryOptionWithLocalizedTitle:NSLocalizedString(@"Use Today\u2019s Date", @"RMEntry date error use today's date recovery option") recoveryBlock:^ BOOL (NSError *recoveryError) {
    [entry setDate:[NSDate date]];
    return YES;
}];

The first is a cancel recovery option that does not attempt to recover from the error and whose recovery block returns NO. The second recovery option has the title Use Today’s Date and a recovery block that sets the entry’s date to today and then returns YES. The errorRecoveryAttempter is added to the user info dictionary of an NSError object and then presented in an alert. For iOS projects we wrote the category UIResponder+RMErrorRecovery to present the error that includes a completion handler. If the recovered parameter of this completion handler is YES then the user chose a recovery path and so the message to save the entry can be resent.

The class is fit for use in both iOS and OS X projects and we use it throughout all our apps. In Analog, for example, when you share your photos to online services; authenticating against 3rd party services over the network is certainly prone to error conditions beyond our control. We think many people can benefit from what we’ve put together and so we’ve open sourced the code. You can download RMErrorRecoveryAttempter on GitHub, which includes a sample iOS project too. If you’ve questions or comments you can catch me @jamesbeith on Twitter and if you’d love to join us, we’re hiring.

Keeping Your iOS Device Secure

Posted by James

Apple does a great job at keeping your iOS device secure against malicious attacks and unauthorised access. But there are a few steps that you can take to ensure that your private data stays private, and increase the likelihood that you get your device back whether it’s lost or stolen.

Passcode

Setting a passcode for your iOS device is a great first step in keeping your iOS device secure. To set one go to Settings, tap General and then tap Passcode Lock.

Before you Turn Passcode On you should consider whether to turn Simple Passcode off. With Simple Passcode on you will set a 4-digit number, with it off you can set a longer alphanumeric passcode. Longer alphanumeric passcodes are more secure but also a little more fiddly to enter on the lock screen as you'll be presented with the full keyboard instead of the numeric keypad. But here's a pro tip where you can get best of both worlds. If you have Simple Passcode off and set a long passcode that only contains numbers the lock screen will now present the numeric keypad instead of the full keyboard. Now you can choose a longer, more secure numeric passcode which is both easier to remember and quicker to enter that an alphanumeric one.

Password

With your passcode set you’ll now want to consider whether to turn Erase Data on. When Erase Data is on if someone tries to access your device and fails to enter the correct passcode ten times all the data on the device will be erased. If you have very confidential data on your device then this may be a feature you want, but it does come with a couple of draw backs. If you have young children or a friend at the pub trying to guess your passcode they could be unaware of the consequence of guessing it incorrectly ten times. Secondly, if the device is lost or stolen and subsequently erased then it means that you won't be able to use Find My iPhone to help track it down.

Find My iPhone

If you setup Find My iPhone on your iOS device then the chance of getting it back after it's lost or stolen can be greatly increased. You can turn Find My iPhone on by going to Settings and tap iCloud. You can now use any computer web browser to sign in to iCloud to find the approximation location of the device on a map. Alternatively your can use a friends iOS device and Apple's Find My iPhone app available from the App Store.

You can also use the service to make the device display a contact message and play a sound, great if you’ve misplaced your device. You can also remotely lock the device; with a new 4-digit number if you'd previously not set a passcode lock. And if all hope is lost and you really don't want your private data getting accessed you can remotely wipe the device. The downside to this is once performed the device will no longer be able to be located.

FindMyiPhone

Restrictions

Having Find My iPhone enabled offers some great features to help you get your device back, but there is an extra step you can take to help recover your device when it’s stolen. A would be thief could pinch your device, and if the passcode lock is off or not required to unlock until after a long period of time, they could go straight in to Settings and turn Find My iPhone off. To prevent such a tactic you can use device restrictions. Go to Settings, tap General and then tap Restrictions. When you tap Enable Restrictions you will set a passcode lock and can then prevent changes to particular services. Around the middle of the list you will see Location and Accounts, tap on each and set Don't Allow Changes. Now the thief won’t be able to prevent your device showing up on Find My iPhone whilst it's switched on.

Restrictions

Conclusion

We all store a lot of personal data on our iOS devices, probably more than we'd anticipate. Hopefully you’re now a little more aware of some of the measures you can take to help keep your iOS devices more secure. I’d recommend at a minimum that you set a simple passcode for all your devices. Find My iPhone is great and I'd recommend you switch it on too, fortunately I've never had to use it out of anything other that curiosity. Tom Symonds, a Home Affairs correspondent for the BBC, has though and wrote an interesting account of his experience tracking down a thief with the police. If you'd like to read more about the security technologies and features implemented on Apple's iOS devices they have published an in-depth document titled iOS Security.