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.
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}
There is no way to do this with NSDataDetector. Please file a bug with Apple to request this functionality.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With