フィルターの実装(人情的な日付認識)

前々回からの続き。日付を期間として認識して比較する機能は、正規表現を使った文字列判定でやってみた。

  • すでにfilter_sqlsテーブルには日付のフィルターの場合は、start_at_period(item)またはend_at_period(item)を呼び出して、最適な日付文字列に変換するように設定してある。
id name not_op operator wild1 item wild2 option sort
12 ...と等しい(日時) BETWEEN start_at_period(item) "AND #{end_at_period(item)}" 12000
13 ...と等しくない(日時) NOT BETWEEN start_at_period(item) "AND #{end_at_period(item)} OR #{column} IS NULL" 13000
14 ...より大きい(日時) > end_at_period(item) 14000
15 ...以上(日時) >= start_at_period(item) 15000
16 ...より小さい(日時) start_at_period(item) 16000
17 ...以下(日時) <= end_at_period(item) 17000
  • 後は、コントローラーにstart_at_period(item)とend_at_period(item)のメソッドの中身を書くだけだ。以下のようにしてみた。
private

  # 日付を表現する文字列を、期間と認識して、その期間の始まりを'%Y-%m-%d %H:%M:%S'形式で返す。
  def start_at_period(str)
    case str
    when /(^|\s)\d{1,2}:\d{1,2}:\d{1,2}\s*$/ ## ~7:57:05
      Time.parse(str)
    when /(^|\s)\d{1,2}:\d{1,2}\s*$/ ## ~7:57
      Time.parse(str)
    when /(^|\s)\d{1,2}:$/ ## ~7:
      Time.parse(str + '0')
    when /(^|\/)\d{1,2}[\/\-]\d{1,2}\s*$/ ## ~3/3
      Time.parse(str)
    when /^\d{4}[\/\-]?$/ ## 2007
      Time.parse(str.sub(/[\/\-]$/,'') + '/1/1')
    when /^(\d{4}[\/\-]?)?\d{1,2}[\/\-]?$/ ## ~3, ~3/, 2007/3
      Time.parse(str.sub(/[\/\-]$/,'') + '/1')
    else
      # nilが返る、rescue nilでエラーを無視する。
    end.strftime('%Y-%m-%d %H:%M:%S') rescue nil
  end

  # 日付を表現する文字列を、期間と認識して、その期間の終わりを'%Y-%m-%d %H:%M:%S'形式で返す。
  def end_at_period(str)
    case str
    when /(^|\s)\d{1,2}:\d{1,2}:\d{1,2}\s*$/ ## ~7:57:05
      Time.parse(str)#.since(1)
    when /(^|\s)\d{1,2}:\d{1,2}\s*$/ ## ~7:57
      Time.parse(str).since(1.minute) - 1.second
    when /(^|\s)\d{1,2}:$/ ## ~7:
      Time.parse(str + '0').since(1.hour) - 1.second
    when /(^|\/)\d{1,2}[\/\-]\d{1,2}\s*$/ ## ~3/3
      Time.parse(str).tomorrow - 1.second
    when /^\d{4}[\/\-]?$/ ## 2007
      Time.parse(str.sub(/[\/\-]$/,'') + '/1/1').next_year - 1.second
    when /^(\d{4}[\/\-]?)?\d{1,2}[\/\-]?$/ ## ~3, ~3/, 2007/3
      Time.parse(str.sub(/[\/\-]$/,'') + '/1').next_month - 1.second
    else
      # nilが返る、rescue nilでエラーを無視する。
    end.strftime('%Y-%m-%d %H:%M:%S') rescue nil
  end


それでは、実際に試してみる。

  • 日付をちゃんと認識してくれるか?(フィルター条件:アップロード日時が「入力値」と等しい)
入力値 取得したSQL
2007、2007/ →" created_at BETWEEN '2007-01-01 00:00:00' AND '2007-12-31 23:59:59'"
4、4/ →" created_at BETWEEN '2007-04-01 00:00:00' AND '2007-04-30 23:59:59'"
2006/4、2006/4/ →" created_at BETWEEN '2006-04-01 00:00:00' AND '2006-04-30 23:59:59'"
14: →" created_at BETWEEN '2007-04-19 14:00:00' AND '2007-04-19 14:59:59'"
4/17 14: →" created_at BETWEEN '2007-04-17 14:00:00' AND '2007-04-17 14:59:59'"
  • 4/17以下で、ちゃんと4/17の項目を含めるか?
入力値 取得したSQL
4/17以下 →" created_at <= '2007-04-17 23:59:59' "
4/17より小さい →" created_at < '2007-04-17 00:00:00' "
4/17以上 →" created_at >= '2007-04-17 00:00:00' "
4/17より大きい →" created_at > '2007-04-17 23:59:59' "


うまくいった!それにしても、人情的なSQLに翻訳するのは骨が折れる...。