【问题标题】:Set context date for NSDataDetector为 NSDataDetector 设置上下文日期
【发布时间】:2023-03-26 02:11:01
【问题描述】:

假设今天是 2014 年 1 月 20 日。如果我使用 NSDataDetector 从字符串 "tomorrow at 4pm" 中提取日期,我将得到 2014-01-21T16:00。太好了。

但是,假设我希望 NSDataDetector 假设当前日期是 2014 年 1 月 14 日。这样,当我解析“明天下午 4 点”时,我会得到 2014-01-15T16:00。如果我更改设备上的系统时间,我会得到我想要的。但是,有没有办法以编程方式指定它?

谢谢。

【问题讨论】:

    标签: ios objective-c nsdate nsdatadetector


    【解决方案1】:

    出于测试目的,您可以使用一种称为method swizzling 的技术。诀窍是用您自己的一种方法替换NSDate 的一种方法。

    如果您将 +[NSDate date] 替换为您自己的实现,NSDataDetector 将在您指定的任何时间考虑“现在”。

    在生产代码中混用系统类方法是有风险的。以下示例代码利用知道它私下使用NSDate 来忽略NSDataDetectorEncapsulation。许多潜在的陷阱之一是,如果 iOS 的下一次更新更改了 NSDataDetector 的内部结构,您的生产应用程序可能会意外停止为最终用户正常工作

    像这样向NSDate 添加一个类别(顺便说一句:如果您正在构建库以在设备上运行,you may need to specify the -all_load linker flag 从库中加载类别):

    #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
    

    这里正在使用它:

    - (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);
                          }];
    
    }
    

    哪些输出:

    2014-01-20 19:35:57.525 TestObjectiveC2[6167:303] 从一周前开始:{0, 15}{2014-01-15 03:00:00 +0000} 2014-01-20 19:35:57.526 TestObjectiveC2[6167:303] 从现在开始:{0, 15}{2014-01-22 03:00:00 +0000}

    【讨论】:

    • 这很聪明,但对于生产代码来说是一个非常糟糕的主意。我担心您的回答没有充分表明这不是真正的用途。
    • @mattt 这正是我以“用于测试目的”开始回答的原因。
    • 我明白这一点。我所说的是“出于测试目的”并不足以解释为什么以及在什么背景下提供这个答案。考虑到有多少人盲目地从 SO 中复制粘贴代码,这样的答案会变得很烦人。
    • @mattt 我在答案中添加了一个警告,并在代码中添加了一个难以错过的代码注释,希望能提醒可能盲目复制代码的用户。谢谢!
    【解决方案2】:

    NSDataDetector 无法做到这一点。请file a bug 与 Apple 联系以请求此功能。

    【讨论】:

      【解决方案3】:

      据我所知,没有办法通过NSDataDetector 做到这一点。

      也就是说,手动应用偏移量应该非常简单 - 只需从 NSCalendar 计算所需偏移量的 NSDateComponenets,然后从 NSDataDetector 获取检测到的日期并添加 day 日期组件偏移量适当。

          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);
      

      【讨论】:

      • 谢谢!这涵盖了某些情况,但在其他情况下,解析对当前日期/时间很敏感。例如,“让我们在星期二晚上共进晚餐”,如果今天是 1 月 20 日星期一,我想假设上下文日期是 1 月 23 日星期三。 :)
      • @andros 好吧,这就是一般方法。你特别问了“明天晚上”。这同样适用于其他相对日期。
      猜你喜欢
      • 2014-06-05
      • 2014-05-19
      • 1970-01-01
      • 2014-05-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-06
      • 1970-01-01
      相关资源
      最近更新 更多