fill the void

Posted
13 September 2008 @ 5pm

Tagged
development

7 Comments

Cocoa Tutorial: Yesterday, Today, and Tomorrow with NSDate

One common need is to make dates within the last week more readable, like using “Today” instead of “September 13″. Apple Mail and NetNewsWire both do this. When using Cocoa Bindings, the easiest path to this is through an NSValueTransformer. I initially tried to use NSCalendarDate (following this example), but apparently Apple might deprecate that object in 10.6. So instead I went with NSDateComponents.

Below is code from BDDateTransformer. It converts appropriate dates into “Tomorrow”, “Today”, “Yesterday”, and the past five days of the week. Feel free to use this wherever.

// snippet from BDDateTransformer.m //
- (id)transformedValue:(NSDate *)date
{
	// Initialize the formatter.
	NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease];
	[formatter setDateStyle:NSDateFormatterShortStyle];
	[formatter setTimeStyle:NSDateFormatterNoStyle];

	// Initialize the calendar and flags.
	unsigned unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit |  NSDayCalendarUnit | NSWeekdayCalendarUnit;
	NSCalendar *calendar = [NSCalendar currentCalendar];

	// Create reference date for supplied date.
	NSDateComponents *comps = [calendar components:unitFlags fromDate:date];
	[comps setHour:0];
	[comps setMinute:0];
	[comps setSecond:0];
	NSDate *suppliedDate = [calendar dateFromComponents:comps];

	// Iterate through the eight days (tomorrow, today, and the last six).
	int i;
	for (i = -1; i < 7; i++)
	{
		// Initialize reference date.
		comps = [calendar components:unitFlags fromDate:[NSDate date]];
		[comps setHour:0];
		[comps setMinute:0];
		[comps setSecond:0];
		[comps setDay:[comps day] - i];
		NSDate *referenceDate = [calendar dateFromComponents:comps];
		// Get week day (starts at 1).
		int weekday = [[calendar components:unitFlags fromDate:referenceDate] weekday] - 1;

		if ([suppliedDate compare:referenceDate] == NSOrderedSame && i == -1)
		{
			// Tomorrow
			return [NSString stringWithString:@"Tomorrow"];
		}
		else if ([suppliedDate compare:referenceDate] == NSOrderedSame && i == 0)
		{
			// Today's time (a la iPhone Mail)
			[formatter setDateStyle:NSDateFormatterNoStyle];
			[formatter setTimeStyle:NSDateFormatterShortStyle];
			return [formatter stringFromDate:date];
		}
		else if ([suppliedDate compare:referenceDate] == NSOrderedSame && i == 1)
		{
			// Today
			return [NSString stringWithString:@"Yesterday"];
		}
		else if ([suppliedDate compare:referenceDate] == NSOrderedSame)
		{
			// Day of the week
			NSString *day = [[formatter weekdaySymbols] objectAtIndex:weekday];
			return day;
		}
	}

	// It's not in those eight days.
	NSString *defaultDate = [formatter stringFromDate:date];
	return defaultDate;
}

07/31/2009 UPDATE: I finally added Ford’s code for listing a time for today, like the iPhone’s Mail. I also updated RssBucket on Google Code, where I use this date transformer in context.


7 Comments

Posted by
Ford
23 February 2009 @ 7pm

Very cool!

NSCalendarDate isn’t available for the iPhone, so regardless of Apple’s plans for 10.6, this code is super useful.

FYI, I made a slight modification in my adaptation of your code. Instead of returning @”Today”, I wrote:

[formatter setDateStyle:NSDateFormatterNoStyle];
[formatter setTimeStyle:NSDateFormatterShortStyle];
return [formatter stringFromDate:self];

This returns just the time, a la iPhone Mail.

Thanks again!


Posted by
bdunagan
24 February 2009 @ 12am

Very good point! Do as Apple does. I’ll update the code soon. Thanks!


Posted by
Fabien
4 July 2009 @ 11am

Thanks, all I needed!


Posted by
bdunagan
31 July 2009 @ 12pm

Glad people are finding this useful! I finally added Ford’s logic for listing the time to the code above and to RssBucket. Now the transformer matches the date on iPhone Mail.


Posted by
Asok
9 October 2009 @ 6am

[comps setDay:[comps day] – i]; will it work for corner cases like 31 or 1?
just wondering as apple documentation is not clear.


Posted by
bdunagan
9 October 2009 @ 8pm

@Asok Yep, the API handles it. You can start with April 1, execute [comps setDay:-3], and get March 28. Even handles leap years.


Posted by
nonamelive
5 February 2010 @ 11am

Thank you! This is so useful. I have added it to my NSDate category!


Leave a Comment