0

Working with Date and Time in Cocoa (Part 1)

-

 

 

 

One of the most common problems I see newbies to Objective-C and Cocoa struggle with on Stack Overflow is how to deal correctly with dates and times. Cocoa’s approach to date and time handling may indeed seem overly complex at first glance: where other languages’ standard libraries seem to get by with just one or two classes to cover this field, the Foundation framework employs a staggering array of separate classes: NSDateNSDateComponentsNSDateFormatterNSCalendar,NSTimeZone. These classes deal directly with date and time and you should be familiar with all of them. In addition, you should also understand the role of the NSLocale class.

Let’s have a look at those classes one by one. As you will see, the Cocoa approach to date and time handling is not only quite easy to understand but also extremely flexible.

NSDate

NSDate is the central class of the date/time handling in Foundation, and at the same time the simplest imaginable. NSDate is nothing more than a wrapper around a single number: the number of seconds since 1 January, 2001, at 00:00 (midnight), UTC1. For values representing numbers of seconds, the framework uses a custom type, NSTimeInterval, which is currently defined as a 64-bit floating point value. According to the documentation, this is enough to yield an impressive sub-millisecond precision over a range of 10,000 years.

Represents an Absolute Point in Time

An NSDate object always represents an absolute point in time.2 This insight has two important consequences:

  1. There is no way to represent a certain date without including a specific time. For instance, to say that a particular NSDate instance represents 17 November 2011 makes no sense; you always have to include the particular time and time zone, such as 17 November 2011 00:00:00 +00:00 (or any other time of your choice).

    If your app needs to store dates with less-than-second precision in order to represent entire days, months or years, you should either not use your own custom class for this or, better, define a rule how your app deals with the unused components of the date (e.g., set the time components of the date to00:00:00 +00:00).

    If you are sloppy and store dates with arbitrary time components, you will run into problems later when you want to compare or group multiple dates.

  2. NSDate has no concept of time zones. When it is midnight in London (17 November 2011 00:00:00 +00:00), it is only 6 pm on the day before in New York (16 November 2011 18:00:00 -06:00). Both dates represent the same point in time and are thus absolutely equal as far as NSDate is concerned.

    The implication of this is that you cannot store the time zone of a date and time in an NSDate object. If your app needs this information, you will have to store it in a different field. But more often than not, you will find that the time zone is actually not a field that should be stored with a date. Rather, it is aruntime preference of the person that is currently using your app, and your app should probably display most dates in the user’s current time zone.

How To Create An NSDate That Represents A Specific Date?

The easiest way to create an NSDate object is [NSDate date];. This will return an instance that represents the current moment and is often useful in code when it comes to storing creation or modification dates of records or to measure certain time intervals in your app.

The more generic task of creating an instance that represents a specific date and time turns out to be not so straightforward. There is the +dateWithTimeIntervalSinceReferenceDate: class method, but it requires you to know the interval in seconds between your desired date and the reference date (1 January 2001 00:00:00 +00:00). Turns out most people don’t count dates that way. That’s where the other classes come in.

NSCalendar

Most people reading this will probably only ever use the same single calendar with its 12 months named January, February and so on, seven-day weeks, counting the years from the reputed birth of Jesus. It is easy to forget that (1) the current “western” Gregorian Calendar has only been introduced in 1582 and (2) there are many more calendars in practical use around the world today. The Foundation framework can currently handle ten different calendars3.

It should be clear that, to specify a date unambiguously, we need to specify the calendar we use. For instance, while today’s date falls into the year 2011 in the familiar Gregorian calendar, the current year is 2554 and 5772 in the Buddhist and Hebrew calendars, respectively.

In Cocoa, a calendar is represented by the NSCalendar class. To create an instance of a specific calendar, pass one of the valid calendar identifiers to the initializer:

NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; NSCalendar *buddhist = [[NSCalendar alloc] initWithCalendarIdentifier:NSBuddhistCalendar]; NSCalendar *hebrew = [[NSCalendar alloc] initWithCalendarIdentifier:NSHebrewCalendar];

There are also two class methods, +currentCalendar and +autoupdatingCurrentCalendarthat return the current user’s preferred calendar. Note that the object returned by the latter method automatically adapts to changes in System Preferences.

The rest of the class is pretty straightforward. You can query the calendar for its configuration, i.e., things like the number of days that are in a month or which day is considered the first day of the week. Have a look at the documentation to get a feel for what is possible. There are also methods to split a date into its calendrical components or do the reverse but we are not quite ready to do that yet.

NSTimeZone

Any time specification is not precise enough without also indicating the time zone. I have already discussed that we need a way to reference time zones separately from NSDate and the NSTimeZoneclass does just that. There are several methods to create a time zone instance, the most straightforward being +timeZoneForSecondsFromGMT:.

Note, though, that the numeric offset from GMT is in many cases not enough to identify a specific time zone due to different daylight saving rules around the world. It is safer to specify a time zone by name using the +timeZoneWithName: method. Valid names are of the form @"Europe/Berlin".4

Another method, +timeZoneWithAbbreviation: should be handled with care. It is supposed to create time zones from common abbreviations such as “PST” or “CEST”. The problem is that these abbreviations are not always unique – different countries might use the same abbreviation for different time zones or different abbreviations for the same time zone. You should avoid this ambiguity if possible.

Last but not least, use the +systemTimeZone method to get a reference to the user’s current time zone.

NSDateComponents

We have almost everything we need now to manipulate dates in our code. Our fourth class,NSDateComponents, represents kind of the same information as NSDate: a single point in time. Unlike the latter, however, an NSDateComponents instance lets you access and manipulate every single calendrical component of that absolute point5, from the year down to the second and including such things as era, calendar, time zone and weekday.

Constructing Dates

Knowing this, let’s construct a date that represents the beginning of Steve Jobs’s Macworld 2007 keynote when he first introduced the iPhone:

NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; NSTimeZone *pacificTime = [NSTimeZone timeZoneWithName:@"America/Los_Angeles"]; NSDateComponents *dateComps = [[NSDateComponents alloc] init]; [dateComps setCalendar:gregorian]; [dateComps setYear:2007]; [dateComps setMonth:1]; [dateComps setDay:9]; [dateComps setTimeZone:pacificTime]; [dateComps setHour:9]; // keynote started at 9:00 am [dateComps setMinute:0]; // default value, can be omitted [dateComps setSecond:0]; // default value, can be omitted NSDate *dateOfKeynote = [dateComps date]; NSLog(@"Date of Keynote: %@", dateOfKeynote);

The output:

 Date of Keynote: 2007-01-09 17:00:00 +0000 

Hm, 17:00:00? But remember that NSDate does not care about time zones. When printing an NSDatewith NSLog(), the system always uses UTC, which is 8 hours ahead of San Francisco (or 7 hours during daylight savings time). So the resulting date is indeed correct.

Now that we have an NSDateComponents instance, you would perhaps expect that you can get more information out of it. For example, let’s try to find out what day of the week the keynote was:

NSInteger weekday = [dateComps weekday]; // => -1 == NSUndefinedDateComponent

The documentation explains this:

An instance of NSDateComponents is not responsible for answering questions about a date beyond the information with which it was initialized. For example, if you initialize one with May 6, 2004, its weekday is NSUndefinedDateComponent, not Thursday. To get the correct day of the week, you must create a suitable instance of NSCalendar, create anNSDate object using dateFromComponents: and then use components:fromDate:to retrieve the weekday.

Let’s try that:

NSDate *dateOfKeynote = [dateComps date]; // or: [gregorian dateFromComponents:dateComps] NSDateComponents *weekdayComponents = [gregorian components:NSWeekdayCalendarUnit fromDate:dateOfKeynote]; NSInteger weekday = [weekdayComponents weekday]; // => 3 == Tuesday

Note how we can specify in the -[NSCalendar components:fromDate:] method which date components we are interested in (using a bit mask). Some of the components can be expensive so it makes sense to only ask for the information you really need.

Date Calculations

The combination of NSDateComponents and NSCalendar is also the way to go for fancy date calculations. Say I want to create a date that goes back in time by exactly a month, a day and an hour from the current moment (using the current user’s calendar):

NSDate *now = [NSDate date]; NSDateComponents *comps = [[NSDateComponents alloc] init]; [comps setMonth:-1]; [comps setDay:-1]; [comps setHour:-1]; NSCalendar *calendar = [NSCalendar currentCalendar]; NSDate *newDate = [gregorian dateByAddingComponents:comps toDate:now options:0];

NSDateComponents is an incredibly flexible und useful class. In combination with NSCalendar, you can probably do all the date calculations you ever thought of.

Stay Tuned for Part 2: Date Parsing and Formatting

The classes I presented above give you a complete toolkit to work with date and time in your code. Two things are still missing, though: how to parse dates that come into your app as strings and how to output properly formatted dates as strings? Both of these tasks are handled by the NSDateFormatter class, which I will discuss in a separate post. Stay tuned.

Working with Date and Time in Cocoa (Part 1) – Ole Begemann.

0

Auto-incrementing Build Numbers in Xcode

-

Users and testers will find bugs you are sure you have already fixed. Sometimes they use the wrong version, sometimes your fix is not as good as you thought. Either way a tiny unique version number visible in the app can save you hours of work.

Incrementing the version number of your project for every small update is often not feasible, if it involves manual labor it just does not get done. Would it not be nice if Xcode just autoincremented a build number for you every time you build?

This can be done

There are dozens of solution to the problem available by Google. Unfortunately most do not work in both Xcode 3.2 and Xcode 4, and many more require a lot of hacking, even running external Perl or Python scripts. Using avgtool seems to be a major overkill for most use-cases. There must be an easier way, and there is.

What we want to do is to have the build number available in our Info.plist file, so that it can be read and displayed at run-time. And we also want Xcode to automatically increment this number for every build.

Add a key named CWBuildNumber to your Info.plist file, and set it to a sane start value, maybe "0". You can load it at run-time with a short statement like:

  1. NSString* buildNumber = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CWBuildNumber"];
NSString* buildNumber = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CWBuildNumber"];


Both Xcode 3.2 and Xcode 4 allows to add a Run Script phase to any target. Unfortunately Xcode 3.2 and 4 run these scripts with different paths. PROJECT_DIR environment variable to the rescue! Secondly we want to rewrite the target's source Info.plist file, not the file bundled with the application, so make sure to order the script phase before the Copy Resources phase. Then just add this tiny script phase to your target build:

  1. buildNumber=$(/usr/libexec/PlistBuddy -c "Print CWBuildNumber" ${PROJECT_DIR}/MyApp-Info.plist)
  2. buildNumber=$(($buildNumber + 1))
  3. /usr/libexec/PlistBuddy -c "Set :CWBuildNumber $buildNumber" ${PROJECT_DIR}/MyApp-Info.plist
buildNumber=$(/usr/libexec/PlistBuddy -c "Print CWBuildNumber" ${PROJECT_DIR}/MyApp-Info.plist)
buildNumber=$(($buildNumber + 1))
/usr/libexec/PlistBuddy -c "Set :CWBuildNumber $buildNumber" ${PROJECT_DIR}/MyApp-Info.plist


Conclusions

Happy developers, and happy tester. Maybe even happy users, if you choose to show the full version number including build number in the final product.

PS:

Make file build.plist with CWBuildNumber key int project root folder.

Add a key named CWBuildNumber to your Info.plist file, and set it to a sane start value, maybe "0".

 

Add to Build->pre-actions
 

  1. infoplist="$PROJECT_DIR/${INFOPLIST_FILE}"
  2. buildplist="$PROJECT_DIR/build.plist"
  3.  
  4. # echo "Info.plist = $infoplist \n Build.plist = $buildplist" > ${PROJECT_DIR}/log.txt
  5.  
  6. buildNumber=$(/usr/libexec/PlistBuddy -c "Print CWBuildNumber" ${buildplist})
  7. buildNumber=$(($buildNumber + 1))
  8. /usr/libexec/PlistBuddy -c "Set :CWBuildNumber $buildNumber" ${buildplist}
  9. /usr/libexec/PlistBuddy -c "Set :CWBuildNumber $buildNumber" ${infoplist}
infoplist="$PROJECT_DIR/${INFOPLIST_FILE}"
buildplist="$PROJECT_DIR/build.plist"

# echo "Info.plist = $infoplist \n Build.plist = $buildplist" > ${PROJECT_DIR}/log.txt

buildNumber=$(/usr/libexec/PlistBuddy -c "Print CWBuildNumber" ${buildplist})
buildNumber=$(($buildNumber + 1))
/usr/libexec/PlistBuddy -c "Set :CWBuildNumber $buildNumber" ${buildplist}
/usr/libexec/PlistBuddy -c "Set :CWBuildNumber $buildNumber" ${infoplist}
0

Xcode 4 : Debug Breakpoints, Conditions and Actions

-

I recently bumped into a few new debugging features in Xcode 4 while looking at breakpoint options. To show how this works, let’s look at a small block of code where I’ve already set a breakpoint by clicking in the margin area on the left side in the editor:

With a breakpoint set, right click (or control-click) on the breakpoint indicator, the blue area shown below in the screenshot below:

The popup dialog below will be shown, select Edit Breakpoint:

At this point you will be shown the dialog below. I configured the settings to only trigger the breakpoint if the value of counter is > 2. When this occurs, a message will be spoken that indicates the breakpoint name and hit count (how many times the breakpoint has been activated).

The above is just one of many options for triggering and configuring breakpoint settings. When you get a chance, I recommend you take a few minutes to get familiar with the options for managing breakpoints. And who knows, the voice response may come in handy for those times when you are looking crossed-eyed at the screen from endless hours of coding…

0

Using your iOS 5 device with Xcode 4.1

-

To enable iOS 5 support in Xcode 4.1, read on.

You've upgraded one or more devices to iOS 5.0 and try to use Xcode 4.1, only to be told it doesn't recognize the version of the OS.  Why would you care?  Apple has been advising for some time that gcc is going away, and that date may be fast approaching.  Unfortunately, Clang / llvm lack some features available in gcc, such as register assigned local or global variables.  We've only just gained access to a version of Clang that supports C++, and so you may still need some time to migrate your code.

Assuming Xcode 4.1 is in /Developer and Xcode 4.2 is in /Xcode42, run the following, substituting the appropriate iOS 5 build numbers:

cd /Developer/Platforms/iPhoneOS.platform/DeviceSupport
ln -s /Xcode42/Platforms/iPhoneOS.platform/DeviceSupport/5.0\ \(9A5313e\)/ 5.0\ \(9A5213e\)
0

PaperStack Alpha Source Code

-

Hello, after iOS 5 release (introducing PageViewController and Book Applications) PaperStack team decided to convert the "Books" project in something more interesting and portable.

Anyway, we decided to release our alpha source code to reveal the geometry and math behind the Page Curl filter to everyone. Hope this helps!

1

Determine MAC Address

-

The MAC (Media Access Control) address is an identifier that is associated with a network adapter and uniquely identifies a device on a network. A MAC address consists of 12 hexadecimal numbers, typically formatted as follows

XX:XX:XX:YY:YY:YY

The XX values in a MAC address identify the manufacturer, the YY values are the serial number assigned to the network adapter.

The MAC address can be useful if you need a way to uniquely identify a device – this can be used as a substitute for the UDID value that is now deprecated in iOS 5 and greater.

The code below shows how to get the MAC address on an iOS device:

  1. #include <sys/socket.h>
  2. #include <sys/sysctl.h>
  3. #include <net/if.h>
  4. #include <net/if_dl.h>
  5.  
  6. - (NSString *)getMacAddress
  7. {
  8. int mgmtInfoBase[6];
  9. char *msgBuffer = NULL;
  10. NSString *errorFlag = NULL;
  11. size_t length;
  12.  
  13. // Setup the management Information Base (mib)
  14. mgmtInfoBase[0] = CTL_NET; // Request network subsystem
  15. mgmtInfoBase[1] = AF_ROUTE; // Routing table info
  16. mgmtInfoBase[2] = 0;
  17. mgmtInfoBase[3] = AF_LINK; // Request link layer information
  18. mgmtInfoBase[4] = NET_RT_IFLIST; // Request all configured interfaces
  19.  
  20. // With all configured interfaces requested, get handle index
  21. if ((mgmtInfoBase[5] = if_nametoindex("en0")) == 0)
  22. errorFlag = @"if_nametoindex failure";
  23. // Get the size of the data available (store in len)
  24. else if (sysctl(mgmtInfoBase, 6, NULL, &length, NULL, 0) < 0)
  25. errorFlag = @"sysctl mgmtInfoBase failure";
  26. // Alloc memory based on above call
  27. else if ((msgBuffer = malloc(length)) == NULL)
  28. errorFlag = @"buffer allocation failure";
  29. // Get system information, store in buffer
  30. else if (sysctl(mgmtInfoBase, 6, msgBuffer, &length, NULL, 0) < 0)
  31. {
  32. free(msgBuffer);
  33. errorFlag = @"sysctl msgBuffer failure";
  34. }
  35. else
  36. {
  37. // Map msgbuffer to interface message structure
  38. struct if_msghdr *interfaceMsgStruct = (struct if_msghdr *) msgBuffer;
  39.  
  40. // Map to link-level socket structure
  41. struct sockaddr_dl *socketStruct = (struct sockaddr_dl *) (interfaceMsgStruct + 1);
  42.  
  43. // Copy link layer address data in socket structure to an array
  44. unsigned char macAddress[6];
  45. memcpy(&macAddress, socketStruct->sdl_data + socketStruct->sdl_nlen, 6);
  46.  
  47. // Read from char array into a string object, into traditional Mac address format
  48. NSString *macAddressString = [NSString stringWithFormat:@"%02X:%02X:%02X:%02X:%02X:%02X",
  49. macAddress[0], macAddress[1], macAddress[2], macAddress[3], macAddress[4], macAddress[5]];
  50. NSLog(@"Mac Address: %@", macAddressString);
  51.  
  52. // Release the buffer memory
  53. free(msgBuffer);
  54.  
  55. return macAddressString;
  56. }
  57.  
  58. // Error...
  59. NSLog(@"Error: %@", errorFlag);
  60.  
  61. return errorFlag;
  62. }
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <net/if.h>
#include <net/if_dl.h>

- (NSString *)getMacAddress
{
int mgmtInfoBase[6];
char *msgBuffer = NULL;
NSString *errorFlag = NULL;
size_t length;

// Setup the management Information Base (mib)
mgmtInfoBase[0] = CTL_NET; // Request network subsystem
mgmtInfoBase[1] = AF_ROUTE; // Routing table info
mgmtInfoBase[2] = 0;
mgmtInfoBase[3] = AF_LINK; // Request link layer information
mgmtInfoBase[4] = NET_RT_IFLIST; // Request all configured interfaces

// With all configured interfaces requested, get handle index
if ((mgmtInfoBase[5] = if_nametoindex("en0")) == 0)
errorFlag = @"if_nametoindex failure";
// Get the size of the data available (store in len)
else if (sysctl(mgmtInfoBase, 6, NULL, &length, NULL, 0) < 0)
errorFlag = @"sysctl mgmtInfoBase failure";
// Alloc memory based on above call
else if ((msgBuffer = malloc(length)) == NULL)
errorFlag = @"buffer allocation failure";
// Get system information, store in buffer
else if (sysctl(mgmtInfoBase, 6, msgBuffer, &length, NULL, 0) < 0)
{
free(msgBuffer);
errorFlag = @"sysctl msgBuffer failure";
}
else
{
// Map msgbuffer to interface message structure
struct if_msghdr *interfaceMsgStruct = (struct if_msghdr *) msgBuffer;

// Map to link-level socket structure
struct sockaddr_dl *socketStruct = (struct sockaddr_dl *) (interfaceMsgStruct + 1);

// Copy link layer address data in socket structure to an array
unsigned char macAddress[6];
memcpy(&macAddress, socketStruct->sdl_data + socketStruct->sdl_nlen, 6);

// Read from char array into a string object, into traditional Mac address format
NSString *macAddressString = [NSString stringWithFormat:@"%02X:%02X:%02X:%02X:%02X:%02X",
macAddress[0], macAddress[1], macAddress[2], macAddress[3], macAddress[4], macAddress[5]];
NSLog(@"Mac Address: %@", macAddressString);

// Release the buffer memory
free(msgBuffer);

return macAddressString;
}

// Error...
NSLog(@"Error: %@", errorFlag);

return errorFlag;
}

The output will look as follows: Mac Address: E0:F8:47:C0:E3:C9

 

UPD: Update code to fixed version. Thx to C?ur

« 1 ... 27 28 29 30 31 32 33 34 35 36 37 38 »