Fuzzy date algorithm

Rog picture Rog · May 4, 2009 · Viewed 15.1k times · Source

I'm looking for a fuzzy date algorithm. I just started writing one and realised what a tedious task it is. It quickly degenerated into a lot of horrid code to cope with special cases like the difference between "yesterday", "last week" and "late last month" all of which can (in some cases) refer to the same day but are individually correct based on today's date.

I feel sure there must be an open source fuzzy date formatter but I can't find it. Ideally I'd like something using NSDate (OSX/iPhone) and its formatters but that isn't the difficult bit. Does anyone know of a fuzzy date formatter taking any time period relative to now and returning a string like (but not limited to):

  • a few moments ago
  • in the last five minutes
  • earlier today
  • this morning
  • last night
  • last week
  • last wednesday
  • early last month
  • june last year
  • a couple of years ago

In an ideal world I'd like the string to be as rich as possible (i.e. returning random variants on "Just a moment ago" such as "just now").

Clarification. I'm looking for something more subtle than basic buckts and strings. I want something that knows "yesterday" and "last wednesday" can both refer to the same period but only one is correct when today is Thursday.

Answer

Michael Bishop picture Michael Bishop · Aug 22, 2010

There is a property in NSDateFormatter - "doesRelativeDateFormatting". It appears only in 10.6/iOS4.0 and later but it will format a date into a relative date in the correct locale.

From Apple's Documentation:

If a date formatter uses relative date formatting, where possible it replaces the date component of its output with a phrase—such as “today” or “tomorrow”—that indicates a relative date. The available phrases depend on the locale for the date formatter; whereas, for dates in the future, English may only allow “tomorrow,” French may allow “the day after the day after tomorrow,” as illustrated in the following example.

Code

The following is code that will print out a good number of the relative strings for a given locale.

NSLocale *locale = [NSLocale currentLocale];
//    NSLocale *locale = [[[NSLocale alloc] initWithLocaleIdentifier:@"fr_FR"] autorelease];

NSDateFormatter *relativeDateFormatter = [[NSDateFormatter alloc] init];
[relativeDateFormatter setTimeStyle:NSDateFormatterNoStyle];
[relativeDateFormatter setDateStyle:NSDateFormatterMediumStyle];
[relativeDateFormatter setDoesRelativeDateFormatting:YES];
[relativeDateFormatter setLocale:locale];

NSDateFormatter *normalDateFormatter = [[NSDateFormatter alloc] init];
[normalDateFormatter setTimeStyle:NSDateFormatterNoStyle];
[normalDateFormatter setDateStyle:NSDateFormatterMediumStyle];
[normalDateFormatter setDoesRelativeDateFormatting:NO];
[normalDateFormatter setLocale:locale];

NSString * lastUniqueString = nil;

for ( NSTimeInterval timeInterval = -60*60*24*400; timeInterval < 60*60*24*400; timeInterval += 60.0*60.0*24.0 )
{
    NSDate * date = [NSDate dateWithTimeIntervalSinceNow:timeInterval];

    NSString * relativeFormattedString = [relativeDateFormatter stringForObjectValue:date];
    NSString * formattedString = [normalDateFormatter stringForObjectValue:date];

    if ( [relativeFormattedString isEqualToString:lastUniqueString] || [relativeFormattedString isEqualToString:formattedString] )
        continue;

    NSLog( @"%@", relativeFormattedString );
    lastUniqueString = relativeFormattedString;
}

Notes:

  • A locale is not required
  • There are not that many substitutions for English. At the time of writing there are: "Yesterday, Today, Tomorrow". Apple may include more in the future.
  • It's fun to change the locale and see what is available in other languages (French has a few more than English, for example)
  • If on iOS, you might want to subscribe to UIApplicationSignificantTimeChangeNotification

Interface Builder

You can set the "doesRelativeDateFormatting" property in Interface Builder:

  • Select your NSDateFormatter and choose the "Identity Inspector" tab of the Inspector Palette (the last one [command-6]).
  • Under the sub-section named "User Defined Runtime Attributes", you can add your own value for a key on the selected object (in this case, your NSDateFormatter instance). Add "doesRelativeDateFormatting", choose a "Boolean" type, and make sure it's checked.
  • Remember: It may look like it didn't work at all, but that might because there are only a few substituted values for your locale. Try at least a date for Yesterday, Today, and Tomorrow before you decide if it's not set up right.