Objective-C
Fuzzy Date Algorithm
Programming
Software Development
iOS Development

Fuzzy Date algorithm in Objective-C

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

A fuzzy date algorithm turns exact timestamps into phrases people scan quickly, such as "just now" or "2 hours ago." This is useful in feeds, chat screens, and activity logs where relative context is more important than full date formatting. In Objective-C, NSDate, NSCalendar, and NSDateFormatter are enough to build a robust solution.

Choosing Time Buckets That Match User Expectations

Most fuzzy date bugs come from unclear bucket rules. If one part of your app says "59 minutes ago" and another says "1 hour ago" for the same timestamp, users lose trust in the interface. Define explicit ranges once, then reuse them everywhere.

A practical rule set is simple:

  • Under 60 seconds: "just now"
  • Under 60 minutes: "N minutes ago"
  • Under 24 hours: "N hours ago"
  • Yesterday: "Yesterday at HH:mm"
  • Same calendar year: "MMM d at HH:mm"
  • Older: full date

The code below applies those buckets in one method. It also accepts an injected now value, which makes unit testing deterministic.

objective-c
1#import <Foundation/Foundation.h>
2
3@interface FuzzyDateFormatter : NSObject
4- (NSString *)fuzzyStringForDate:(NSDate *)date now:(NSDate *)now;
5@end
6
7@implementation FuzzyDateFormatter
8
9- (NSString *)fuzzyStringForDate:(NSDate *)date now:(NSDate *)now {
10    if (!date || !now) {
11        return @"";
12    }
13
14    NSTimeInterval seconds = [now timeIntervalSinceDate:date];
15    if (seconds < 0) {
16        seconds = 0;
17    }
18
19    NSInteger minute = 60;
20    NSInteger hour = 60 * minute;
21    NSInteger day = 24 * hour;
22
23    if (seconds < minute) {
24        return @"just now";
25    }
26    if (seconds < hour) {
27        NSInteger value = (NSInteger)(seconds / minute);
28        return [NSString stringWithFormat:@"%ld minute%@ ago",
29                (long)value,
30                value == 1 ? @"" : @"s"];
31    }
32    if (seconds < day) {
33        NSInteger value = (NSInteger)(seconds / hour);
34        return [NSString stringWithFormat:@"%ld hour%@ ago",
35                (long)value,
36                value == 1 ? @"" : @"s"];
37    }
38
39    NSCalendar *calendar = [NSCalendar currentCalendar];
40    if ([calendar isDateInYesterday:date]) {
41        NSDateFormatter *timeFormatter = [[NSDateFormatter alloc] init];
42        timeFormatter.dateFormat = @"HH:mm";
43        return [NSString stringWithFormat:@"Yesterday at %@",
44                [timeFormatter stringFromDate:date]];
45    }
46
47    NSInteger nowYear = [calendar component:NSCalendarUnitYear fromDate:now];
48    NSInteger dateYear = [calendar component:NSCalendarUnitYear fromDate:date];
49
50    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
51    formatter.dateFormat = (nowYear == dateYear) ? @"MMM d 'at' HH:mm" : @"yyyy-MM-dd HH:mm";
52    return [formatter stringFromDate:date];
53}
54
55@end

Handling Locale, Time Zone, and Calendar Correctly

Relative labels are short, but they are still date formatting, so locale and time zone rules matter. If your backend stores UTC and your device is in a local zone, converting the NSDate correctly is mandatory. Also, avoid hardcoding month names or fixed 12 hour formats unless your product requires that style.

A good pattern is to keep date math in UTC compatible NSDate values, then format output with user locale and current time zone. That separates storage from display and avoids off by one day errors around midnight.

objective-c
1- (NSDateFormatter *)localizedFormatterWithPattern:(NSString *)pattern {
2    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
3    formatter.locale = [NSLocale currentLocale];
4    formatter.timeZone = [NSTimeZone localTimeZone];
5    formatter.dateFormat = pattern;
6    return formatter;
7}

You can integrate that helper into your main formatter method. If your app supports custom locales in settings, pass that locale explicitly rather than always relying on system defaults.

Testing the Formatter With Fixed Clocks

Fuzzy date logic changes over time, so tests must avoid real clocks. If test code calls NSDate date directly, failures appear around minute boundaries and are hard to reproduce. Injecting now is the simplest fix.

The example below shows deterministic checks for common buckets:

objective-c
1#import <XCTest/XCTest.h>
2#import "FuzzyDateFormatter.h"
3
4@interface FuzzyDateFormatterTests : XCTestCase
5@end
6
7@implementation FuzzyDateFormatterTests
8
9- (void)testMinutesBucket {
10    FuzzyDateFormatter *formatter = [[FuzzyDateFormatter alloc] init];
11
12    NSDate *now = [NSDate dateWithTimeIntervalSince1970:1700000000];
13    NSDate *tenMinutesAgo = [NSDate dateWithTimeIntervalSince1970:1699999400];
14
15    NSString *result = [formatter fuzzyStringForDate:tenMinutesAgo now:now];
16    XCTAssertEqualObjects(result, @"10 minutes ago");
17}
18
19- (void)testJustNowBucket {
20    FuzzyDateFormatter *formatter = [[FuzzyDateFormatter alloc] init];
21
22    NSDate *now = [NSDate dateWithTimeIntervalSince1970:1700000000];
23    NSDate *thirtySecondsAgo = [NSDate dateWithTimeIntervalSince1970:1699999970];
24
25    NSString *result = [formatter fuzzyStringForDate:thirtySecondsAgo now:now];
26    XCTAssertEqualObjects(result, @"just now");
27}
28
29@end

With tests in place, you can safely adjust bucket rules to match product language without introducing regressions.

Common Pitfalls

  • Mixing local time display with UTC parsing rules, which can shift output by hours.
  • Calling NSDate date directly in formatter logic, making tests flaky.
  • Using multiple fuzzy implementations across screens and getting inconsistent labels.
  • Hardcoding English phrases in code when the app needs localization support.
  • Ignoring future timestamps from clock skew and showing negative values.

Summary

  • Define bucket boundaries once and reuse them across the app.
  • Keep storage timestamps neutral and apply locale plus time zone at render time.
  • Inject now into formatter methods so tests stay deterministic.
  • Use NSCalendar and NSDateFormatter intentionally for readable, stable output.
  • Validate edge cases such as yesterday boundaries and future skew.

Course illustration
Course illustration

All Rights Reserved.