Skip to content

Latest commit

 

History

History
218 lines (188 loc) · 8.13 KB

helper-for-datetime-range-query.org

File metadata and controls

218 lines (188 loc) · 8.13 KB

Define helper function to create datetime range objects

Problem

When we need to query records within a time range, we need to construct a Range object representing the time range, for example:

Student.where(created_at: Time.now.beginning_of_day..Time.now.end_of_day)

or

Student.where(created_at: Time.new(2016, 1, 1)..Time.new(2016, 12, 31))

Define a helper function

To simplify the process of constructing a time Range object, we can define a helper function, for example:

Create a file ~/.arql.d/date_range.rb with the following content:

module Kernel
    
  # == Example:
  #  Range
  #    1. -3..0
  #    2. '27'..'29'
  #    3. '04-01'..'04-10'
  #    4. '2021-04-01'..'2021-04-10'
  #  Array
  #    1. [nil, nil] or [] => 1970-01-01 ~ 1000 days later from now
  #    2. [:yday, :today] or [:yesterday, :today]
  #    3. [-10.months, :today]
  #  Others
  #    1. Time.now or Date.today
  #    2. :now or :today
  #    3. :yesterday or :yday
  #    4. -2
  #    5. -2.months
  def dates_range(dates)
    dates = :yesterday if dates == :yday
    if dates.is_a?(Range)
      s = if dates.begin.is_a?(Integer)
            (Time.now+dates.begin.days).beginning_of_day
          elsif dates.begin.is_a?(String) && dates.begin =~ /^\d{4}-\d{2}-\d{2}$/
            Time.parse(dates.begin).beginning_of_day
          elsif dates.begin.is_a?(String) && dates.begin =~ /^\d{2}-\d{2}$/
            Time.strptime(dates.begin, '%m-%d').beginning_of_day
          elsif dates.begin.is_a?(String) && dates.begin =~ /^\d{2}$/
            Time.strptime(dates.begin, '%d').beginning_of_day
          else
            dates.begin
          end
      e = if dates.end.is_a?(Integer)
            (Time.now+dates.end.days).end_of_day
          elsif dates.end.is_a?(String) && dates.end =~ /^\d{4}-\d{2}-\d{2}$/
            Time.parse(dates.end).end_of_day
          elsif dates.end.is_a?(String) && dates.end =~ /^\d{2}-\d{2}$/
            Time.strptime(dates.end, '%m-%d').end_of_day
          elsif dates.end.is_a?(String) && dates.end =~ /^\d{2}$/
            Time.strptime(dates.end, '%d').end_of_day
          else
            dates.end
          end
      s..e
    elsif dates.is_a?(Array)
      if dates.size == 2
        s = dates.first || Date.new(1970, 1, 1)
        s = if s.is_a?(Time) || s.is_a?(Date)
              s.beginning_of_day
            elsif s == :now
              Time.now.beginning_of_day
            elsif s == :today
              Date.today.beginning_of_day
            elsif s == :yesterday || s == :yday
              Date.yesterday.beginning_of_day
            elsif s.in?(Time.instance_methods)
              Time.now.send(s).beginning_of_day
            elsif s.is_a?(ActiveSupport::Duration)
              (Time.now+s).beginning_of_day
            elsif s.is_a?(Integer)
              (Time.now+s.days).beginning_of_day
            else
              raise "Not supported s: #{s}"
            end
        e = dates.last || Date.today + 1000.days
        e = if e.is_a?(Time) || e.is_a?(Date)
              e.end_of_day
            elsif e == :now
              Time.now.end_of_day
            elsif e == :today
              Date.today.end_of_day
            elsif e == :yesterday || e == :yday
              Date.yesterday.end_of_day
            elsif e.in?(Time.instance_methods)
              Time.now.send(e).end_of_day
            elsif e.is_a?(ActiveSupport::Duration)
              (Time.now+e).end_of_day
            elsif e.is_a?(Integer)
              (Time.now+e.days).beginning_of_day
            else
              raise "Not supported e: #{e}"
            end
        return s..e
      else
        times = dates.map do |date|
          if date.nil?
            nil
          elsif date.is_a?(Time)
            date
          elsif date == :now
            Time.now
          elsif date.in?(Time.instance_methods)
            Time.now.send(date)
          elsif date.is_a?(ActiveSupport::Duration)
            Time.now+date
          elsif date.is_a?(Integer)
            (Time.now+date.days).beginning_of_day
          else
            raise "Not supported date: #{date}"
          end
        end
        s = times.first || Time.new(1970, 1, 1, 0, 0, 0)
        e = times.last || Time.now + 1000.days
        return s..e
      end
    else
      if dates.is_a?(Time) or dates.is_a?(Date)
        dates = dates.beginning_of_day..dates.end_of_day
      elsif dates == :now
        dates = Time.now.beginning_of_day..Time.now.end_of_day
      elsif dates == :today
        dates = Date.today.beginning_of_day..Date.today.end_of_day
      elsif dates == :yday || dates == :yesterday
        dates = Date.yesterday.beginning_of_day..Date.yesterday.end_of_day
      elsif dates.in?(Time.instance_methods)
        dates = Time.now.send(dates).beginning_of_day..Time.now.send(dates).end_of_day
      elsif dates.is_a?(ActiveSupport::Duration)
        dates = (Time.now+dates).beginning_of_day..(Time.now+dates).end_of_day
      elsif dates.is_a?(Integer)
        (Time.now+dates.days).beginning_of_day..(Time.now+dates.days).end_of_day
      else
        raise "Not supported dates: #{dates}"
      end
    end
  end
    
  alias_method :dates, :dates_range
end
    
class ::ArqlModel
    
  class << self
    def ts_attribute_for_create
      (timestamp_attributes_for_create||[]).find { |e| e.in?(column_names) }
    end
    
    def ts_attribute_for_update
      (timestamp_attributes_for_update||[]).find { |e| e.in?(column_names) }
    end
    
    
    def created_on(dates)
      attr = ts_attribute_for_create
      raise 'No attrtibute for create defined' unless attr
      where(attr => dates_range(dates))
    end
    
    alias on created_on
    
    def today
      created_on(0)
    end
    
    def modified_on(dates)
      attr = ts_attribute_for_update
      raise 'No attrtibute for update defined' unless attr
      where(attr: dates_range(dates))
    end
  end
end

Then include this file in ~/.arql.d/init.rb:

load(File.absolute_path(File.dirname(__FILE__) + "/date_range.rb"))

Usage

Then you can use this method:

Student.where(created_at: dates(0)). # Query records created today
Student.where(created_at: dates(:today)). # Query records created today
Student.where(created_at: dates('2016-01-01'..'2016-01-31')) # Query records created in January 2016
Student.where(created_at: dates('01'..'10'))  # Query records created on the 1st to the 10th of the current month
Student.where(created_at: dates('03-01'..'04-10'))  # Query records created from March 1st to April 10th of the current year
Student.where(created_at: dates(-20..-1))  # Query records created 20 days ago to yesterday

If you are querying the created_at field (configured in init.yaml with created_at, the default value is created_at), you can use:

Student.on(0). # Query records created today
Student.on(:today). # Query records created today
Student.on('2016-01-01'..'2016-01-31') # Query records created in January 2016
Student.on('01'..'10')  # Query records created on the 1st to the 10th of the current month
Student.on('03-01'..'04-10')  # Query records created from March 1st to April 10th of the current year
Student.on(-20..-1)  # Query records created 20 days ago to yesterday