Swift iOS中如何检测含上周、下月等的自然语言日期?
Swift中识别自然语言相对日期的解决方案
问题背景
使用NSDataDetector的.date检测类型可以识别包含具体日期组件的字符串(如“next Monday at 3 PM”),但无法识别“last week”、“next month”、“last year”这类相对日期表述;而RelativeDateTimeFormatter仅支持将Date对象转换为相对日期字符串,无法反向解析。
可行解决方案
1. 结合NSDataDetector与自定义规则补全
先通过NSDataDetector处理常规可识别的日期,若未匹配到结果,再用正则匹配常见相对日期关键词,通过Calendar手动计算对应日期:
func extractNaturalLanguageDate(from input: String) -> Date? { // 第一步:尝试NSDataDetector识别常规日期 let dateCheckingType = NSTextCheckingResult.CheckingType.date.rawValue guard let detector = try? NSDataDetector(types: dateCheckingType) else { return nil } let matches = detector.matches( in: input, options: [], range: NSRange(location: 0, length: input.utf16.count) ) if let firstMatch = matches.first, let detectedDate = firstMatch.date { return detectedDate } // 第二步:处理相对日期的正则匹配与计算 let relativeDateRules: [(pattern: String, calendarComponent: Calendar.Component, offset: Any)] = [ ("last\\s+week", .weekOfYear, -1), ("next\\s+week", .weekOfYear, 1), ("last\\s+month", .month, -1), ("next\\s+month", .month, 1), ("last\\s+year", .year, -1), ("next\\s+year", .year, 1), ("(\\d+)\\s+weeks?\\s+ago", .weekOfYear, { (match: NSTextCheckingResult) in -Int(match.range(at: 1).substring(from: input))! }), ("(\\d+)\\s+months?\\s+ago", .month, { (match: NSTextCheckingResult) in -Int(match.range(at: 1).substring(from: input))! }) ] extension NSRange { func substring(from string: String) -> String { guard let range = Range(self, in: string) else { return "" } return String(string[range]) } } let calendar = Calendar.current for rule in relativeDateRules { guard let regex = try? NSRegularExpression(pattern: rule.pattern, options: .caseInsensitive) else { continue } guard let match = regex.firstMatch(in: input, options: [], range: NSRange(location: 0, length: input.utf16.count)) else { continue } var offsetValue = 0 if let fixedOffset = rule.offset as? Int { offsetValue = fixedOffset } else if let dynamicOffset = rule.offset as? ((NSTextCheckingResult) -> Int) { offsetValue = dynamicOffset(match) } return calendar.date(byAdding: rule.calendarComponent, value: offsetValue, to: Date()) } return nil } // 测试示例 if let date = extractNaturalLanguageDate(from: "we had the meeting last month") { print(date.formatted(date: .complete, time: .omitted)) } if let date = extractNaturalLanguageDate(from: "I will be free next Monday at 3 PM") { print(date.formatted(date: .complete, time: .shortened)) } if let date = extractNaturalLanguageDate(from: "3 weeks ago") { print(date.formatted(date: .complete, time: .omitted)) }
2. 利用NaturalLanguage框架识别日期关键词
使用NLTagger识别文本中的日期相关词汇,再根据词汇类型计算对应日期:
import NaturalLanguage func extractDateWithNLTagger(from input: String) -> Date? { let tagger = NLTagger(tagSchemes: [.nameTypeOrLexicalClass]) tagger.string = input let options: NLTagger.Options = [.omitWhitespace, .omitPunctuation] var relativeComponent: Calendar.Component? var offset = 0 tagger.enumerateTags( in: input.startIndex..<input.endIndex, unit: .word, scheme: .nameTypeOrLexicalClass, options: options ) { tag, range in let word = input[range].lowercased() switch word { case "last": offset = -1 case "next": offset = 1 case "week", "weeks": relativeComponent = .weekOfYear case "month", "months": relativeComponent = .month case "year", "years": relativeComponent = .year default: break } return true } if let component = relativeComponent { return Calendar.current.date(byAdding: component, value: offset, to: Date()) } // 若未识别到相对日期,回退到NSDataDetector逻辑 return extractNaturalLanguageDate(from: input) }
注意事项
- 正则规则需要根据需求扩展,覆盖更多相对日期表述(如“a month ago”、“this weekend”等);
- 多语言场景下,需适配对应语言的关键词与正则;
- 若需要更复杂的自然语言解析能力,可考虑使用专门的日期解析第三方库。
内容的提问来源于stack exchange,提问作者Tobi Omotayo
相关产品推荐
相关产品推荐

