Working with Dates and Date offsets in iPhone Development
It's been a slow iPhone development week partly because I'm in the peak week of my marathon training but the main reason is that the work I did was pretty frustrating so it put me off doing more until today.
When is a date not a date?
When it's a datetime, silly. Working with dates in iPhone means you work with the misleading named class NSDate which not only stores a date and a time but the timezone data too. That little caveat aside I can dive into my issue dujour.I'm building a UITableView where the groups are the week numbers and the cells are the days within those weeks calculated from a start date for the whole table. So if the start date is Monday September 21st 2009, the first section header is "week 1" and the first cell is Monday September 21st 2009.
The week section is easy to calculate:
NSUInteger row = [indexPath row];
NSUInteger currentWeek = section+1;
Next up is the calculation for the first cell. It should be easy right? The first cell of week 1 represents 0 weeks after the start date for the tableview and 0 days after the start, so our formula for dayOffset:
So day 1 of week 1 has a dayOffset of 0, day 1 of week 2 has a dayOffset of 7 etc. All that remains is to add the dayOffset to my startDate and I've got the date for the cell.
The challenge isn't finishing - starting is the hardest part
Working with an NSDate object my first instinct was to look at the NSDate class and find a function to add a set number of days to a date and found that there is only the function addTimeInterval:First a quick side-bar. As I mentioned earlier, I'm passing in a date, not a date time so I set my date formatter object to parse MM/dd/yy dates (where MM is 2 digit month vs mm which is 2 digit minutes).
[df setDateFormat:@"MM/dd/yy"];
NSDate *frDate = [df dateFromString:[tableSourceFile objectForKey:@"startDate"]];
NSLog(@"%@",[frDate description]);
I added my dayOffset to my date:
I'm a little embarrassed at the hour and a half of bashing my head against this, double checking my start date, my logic, my days to seconds calculations, NSLogging out every variable involved to the log. It seems the first November 1st is "2009-11-01 00:00:00 -0500" and the second is "2009-11-01 23:00:00 -0600". A timezone switch? I plead late night development insanity on this but eagle-eyed wide-awake super-designer Jeff spots that it's not a change in timezone but the change in daylight savings time.
So turning days into seconds becomes nuts if you cross DST boundaries. To workaround this I have the bright idea to pass in noon as part of my start date because if the date shifts by 1 hour either way it still remains the same day:
//Change dateformatter to look for time
[df setDateFormat:@"MM/dd/yy HH:mm"];
NSDate *frDate = [df dateFromString:[tableSourceFile objectForKey:@"startDate"]];
//Correctly writes out "2009-09-21 12:00:00 -0500"
NSLog(@"%@",[frDate description]);
//Now do the addition:
NSDate *cellDate = [frDate addTimeInterval:secondsOffset];
I've created... a monster!
It works! By setting the time as noon I've got it working but I have a nagging feeling I'm missing something obviously simpler. It's close to midnight so I wrap up and leave for the night kicking myself for missing the obvious.The next morning I pull up the docs and start digging around again looking in forums for date manipulations. I'm not sure of the exact path to enlightenment but I find the "Date and Time Programming Guide for Cocoa" and, more importantly, the section "Calendrical Calculations".
It *appears* that by using NSDateComponents to build an offset and NSCalendar to specify a basis for the calculations I can add the offset in days to a date based on the gregorian calendar. Since I'm adding days not seconds I wonder if this will avoid the issue I jammed thru.
Pressure creates diamonds
I didn't have time until this morning to test my theory so I sat down and plugged at it earlier.NSDateFormatter *df = [[NSDateFormatter alloc] init];
//Change dateformatter back to date only
[df setDateFormat:@"MM/dd/yy"];
NSDate *frDate = [df dateFromString:[tableSourceFile objectForKey:@"startDate"]];
//NSLog confirms my date"2009-09-21 00:00:00 -0500"
NSLog(@"%@",[frDate description]);
//Create an offset object
NSDateComponents *offsetComponents = [[NSDateComponents alloc] init];
//Pass in our offset in days
[offsetComponents setDay:(section*7)+row];
//Select a calendar for the calculations
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
//With the gregorian calendar add my offset object to the start date
NSDate *cellDate = [gregorian dateByAddingComponents:offsetComponents toDate:frDate options:0];
//Release the offset and the calendar
[offsetComponents release];
[gregorian release];
[df setDateFormat:@"EEE, dd MMM"];
cell.textLabel.text = [edf stringFromDate:today];
I hope this helps someone else dodge the bullet and let me know if there are typos/errors/omissions in the code since typing in anger like this rarely reproduces perfect code! Have a great weekend.