如何从 rails 中的字符串中识别一组日期

How to identify a set of dates from a string in rails

我有以下字符串

"sep 04 apr 06"
"29th may 1982"
"may 2006 may 2008"
"since oct 11"

输出

"September 2004 and April 2006"
"29 May 1982"
"May 2006 and May 2008"
"October 2011"

有没有办法从这些字符串中获取日期。我使用了 gem 'dates_from_string',但它无法从第一个场景中正确获取日期。

您可以像这样使用 DateTime class:

DateTime.parse('sep 04 apr 06')

输出 DateTime 对象:

#<DateTime: 2006-04-04T00:00:00+00:00 ((2453830j,0s,0n),+0s,2299161j)>

你可以使用DateTime.strptime方法

当你说 "unfortunately I can't predict in which format the date is going to be in." 时,你暗示你实际上需要 "natural language parsing"。这是核心 Date 或 DateTime 对象不能也不应该做的事情。

因此,要么您需要解析字符串,以便以可理解的格式将它们呈现给更严格的解析器。喜欢DateTime.parse('sep 04')。对于您的示例,它可以像以下一样简单:

datestring = 'sep 04 apr 06'
matches = datestring.match(/[a-z]{3}\s\d{2,4}/)
if matches.many?
  matches.map{|m| Date.parse(m) }.join(' and ')
else
  Date.parse(datestring)
end

但是,如果您想要真正的自然语言解析,请查看 Chronic。其中有各种奇特的解析器,如 Chronic.parse('summer').

编辑:仔细观察,似乎 Chronic 也只能识别一个字符串,因此您的示例 'sep 04 apr 06' 仍需要一些预处理。

我采取的做法如下:

  1. 将字符串分成单词数组。
  2. 如果数组包含的单词少于两个,return一个包含找到的所有日期字符串的数组;否则转到第 3 步。
  3. 如果数组至少包含三个单词,且前三个单词代表日期,则保存,删除数组中的前三个单词,重复步骤2;否则转到第 4 步。
  4. 如果前两个单词代表日期,保存,删除数组中的前两个单词,重复步骤2;否则转到第 5 步。
  5. 删除数组中的第一个单词并转到步骤 2。

我使用 class 方法搜索日期 Date::strptime. strptime employs a format string. For example, '%d %b %Y' searches for the day of the month, followed by a space, followed by a (case-insensitive) three-character month abbreviation ('Jan', 'Feb',...,'Dec'), followed by a four-digit year. (I initially consider using Date::parse,但这并不能充分区分日期。)

代码

我首先为月、日和年生成所有感兴趣的 strptime 格式字符串:

MON = %w{ %b %B } # '%b' for 'Jan', '%B' for 'January'
YR  = %w{ %y %Y } # '%y' for '11', '%Y' for 2011
DAY = %w{ %d }    # '4', '04' or '28' 

PERM3 = MON.product(YR, DAY).
            flat_map { |arr| arr.permutation(3).to_a }.
            map { |arr| arr.join(' ') }
  #=> ["%b %y %d", "%b %d %y", "%y %b %d", "%y %d %b", "%d %b %y", "%d %y %b",
  #    "%b %Y %d", "%b %d %Y", "%Y %b %d", "%Y %d %b", "%d %b %Y", "%d %Y %b",
  #    "%B %y %d", "%B %d %y", "%y %B %d", "%y %d %B", "%d %B %y", "%d %y %B",
  #    "%B %Y %d", "%B %d %Y", "%Y %B %d", "%Y %d %B", "%d %B %Y", "%d %Y %B"] 

然后我对日和月以及月和年的排列做同样的事情:

PERM2 = MON.product(YR).
            concat(MON.product(DAY)).
            flat_map { |arr| arr.permutation(2).to_a }.
            map { |arr| arr.join(' ') }               
  #=> ["%b %y", "%y %b", "%b %Y", "%Y %b", "%B %y", "%y %B",
  #    "%B %Y", "%Y %B", "%b %d", "%d %b", "%B %d", "%d %B"] 

然后我进行如下操作:

require 'date'

def pull_dates(str)
  arr = str.split
  dates = []
  while arr.size > 1
    if arr.size > 2
      a = depunc(arr[0,3])
      if date?(a, PERM3)
        dates << a.join(' ')
        arr.shift(3)
        next
      end
    end
    a = depunc(arr[0,2])
    if date?(a, PERM2)
      dates << a.join(' ')
      arr.shift(2)
      next
    end
    arr.shift
  end
  dates
end

depunc 删除字符串 arr.join(' ').

开头和结尾的任何标点符号
def depunc(arr)
  arr.join(' ').gsub(/^\W|\W$/,'').split  
end

date? 确定三元素或二元素字符串 arr 是否表示日期。我首先从 arr 中获取一个 "cleaned" 字符串,然后搜索适用的 strptime 格式字符串(参数 perm),寻找一个显示清理后的字符串可以是转换为日期。

def date?(arr, perm)
  clean = to_str_and_clean(arr)
  perm.find do |s|
    begin
      d = Date.strptime(clean, s)
      return true
    rescue
      false 
    end
  end
  false
end

to_str_and_clean returns 已删除标点符号的干净字符串以及 'st''nd''rd''th' 等字符串按照当天的数字表示。

def to_str_and_clean(arr)
  str = arr.map { |s| s[0][/\d/] ? s.to_i.to_s : s }.join(' ').tr('.?!,:;', '')
end

例子

我们来试试吧。

str =
"Bubba sighted a flying saucer on sep 04 2013 and again in apr 06. \
Greta was born on 29th may 1982. Hey, may 2006 may 2008 are two years apart.\
We have been at loose ends since oct 11 of this year."

pull_dates(str)
  #=> ["sep 04 2013", "apr 06", "29th may 1982", "may 2006 may", "oct 11"] 

嗯,如您所见,它并不完美。需要进行一些调整,但这可能会让您入门。