Question

Suppose today is January 20th, 2014. If I use NSDataDetector to extract a date from the string "tomorrow at 4pm", I'll get 2014-01-21T16:00. Great.

But, suppose I want NSDataDetector to pretend the current date is January 14th, 2014. This way, when I parse "tomorrow at 4pm", I'll get 2014-01-15T16:00. If I change the system time on the device, I get what I want. But, is there a way to specify this programmatically?

Thanks.

Was it helpful?

Solution

For testing purposes, you can use a technique called method swizzling. The trick is to replace one of NSDate's methods with one of your own.

If you replace +[NSDate date] with your own implementation, NSDataDetector will consider 'now' to be any time you specify.

Swizzling system class methods in production code is risky. The following example code ignores the Encapsulation of NSDataDetector by taking advantage of knowing that it uses NSDate privately. One of many potential pitfalls would be if the next update to iOS changes the internals of NSDataDetector, your production app may stop working correctly for your end-users unexpectedly.

Add a category to NSDate like this (an aside: if you are building libraries to run on the device, you may need to specify the -all_load linker flag to load categories from libs):

#include <objc/runtime.h>

 @implementation NSDate(freezeDate)

static NSDate *_freezeDate;

// Freeze NSDate to a point in time.
// PROBABLY NOT A GOOD IDEA FOR PRODUCTION CODE
+(void)freezeToDate:(NSDate*)date
{
    if(_freezeDate != nil) [NSDate unfreeze];
    _freezeDate = date;
    Method _original_date_method = class_getClassMethod([NSDate class], @selector(date));
    Method _fake_date_method = class_getClassMethod([self class], @selector(fakeDate));
    method_exchangeImplementations(_original_date_method, _fake_date_method);
}

// Unfreeze NSDate so that now will really be now.
+ (void)unfreeze
{
    if(_freezeDate == nil) return;
    _freezeDate = nil;
    Method _original_date_method = class_getClassMethod([NSDate class], @selector(date));
    Method _fake_date_method = class_getClassMethod([self class], @selector(fakeDate));
    method_exchangeImplementations(_original_date_method, _fake_date_method);
}

+ (NSDate *)fakeDate
{
    return _freezeDate;
}

@end

Here is it being used:

- (void)someTestingFunction:(NSNotification *)aNotification
{
    // Set date to be frozen at a point one week ago from now.
    [NSDate freezeToDate:[NSDate dateWithTimeIntervalSinceNow:(-3600*24*7)]];

    NSString *userInput = @"tomorrow at 7pm";
    NSError *error = nil;
    NSRange range = NSMakeRange(0, userInput.length);
    NSDataDetector *dd = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeDate error:&error];
    [dd enumerateMatchesInString:userInput
                         options:0
                           range:range
                      usingBlock:^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop) {
                          NSLog(@"From one week ago: %@", match);
                      }];

    // Return date to normal
    [NSDate unfreeze];

    [dd enumerateMatchesInString:userInput
                         options:0
                           range:range
                      usingBlock:^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop) {
                          NSLog(@"From now: %@", match);
                      }];

}

Which outputs:

2014-01-20 19:35:57.525 TestObjectiveC2[6167:303] From one week ago: {0, 15}{2014-01-15 03:00:00 +0000}
2014-01-20 19:35:57.526 TestObjectiveC2[6167:303] From now: {0, 15}{2014-01-22 03:00:00 +0000}

OTHER TIPS

There is no way to do this with NSDataDetector. Please file a bug with Apple to request this functionality.

As far as I can tell, there's no way to do that through NSDataDetector.

That said, it should be pretty straightforward to apply the offset manually—just calculate the NSDateComponenets for the desired offset from an NSCalendar, and then take your detected date from NSDataDetector and add the day date component offset as appropriate.

    NSDate *futureDate = [NSDate dateWithTimeIntervalSinceNow:100000]; // Offset by about a day from now
    __block NSDate *offsetDetectedDate = nil;

    NSString *string = @"Let's have lunch Tomorrow at Noon";
    NSDataDetector *dataDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeDate error:nil];
    [dataDetector enumerateMatchesInString:string options:0 range:NSMakeRange(0, [string length]) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
        if (result.resultType == NSTextCheckingTypeDate) {
            NSDateComponents *offsetDateComponents = [[NSCalendar currentCalendar] components:NSDayCalendarUnit fromDate:result.date toDate:futureDate options:0];
            offsetDetectedDate = [[NSCalendar currentCalendar] dateByAddingUnit:NSDayCalendarUnit value:offsetDateComponents.day toDate:result.date options:0];
            *stop = YES;
        }
    }];

    NSLog(@"%@", offsetDetectedDate);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top