-
-
Notifications
You must be signed in to change notification settings - Fork 16
Date Time Range
I hope it's not only me that can't take
start_timeandfinish_timeANYMORE!!!
Date or time ranges features. This provides extended and complex calculations over date and time ranges. In a few words, you can now store start_time and finish_time in the same column and relies on the methods provided here to fo your magic. PostgreSQL Docs
Create a table with the single column set as any type of time/date range (daterange, tsrange, or tstzrange), like:
create_table :events do |t|
t.string :name
t.tsrange :period
t.interval :interval
endYou have to go to each of your models and enable the functionality for each range-type field. The method name is defined on period.base_method.
# models/event.rb
class Event < ActiveRecord::Base
period_for :period
endThere are a couple of important settings that can be provided to this:
# This means that nil value will be treated as false, the default is false, hence treated as true
period_for :period, pessimistic: true
# You can define a column or a value to be used as a threshold
period_for :period, threshold: :interval
period_for :period, threshold: 15.minutes
# This will force the creation of all the methods, which would raise an exception on conflicting. The default is false
period_for :period, force: true
# If you don't want the methods to have the field name, then you can use this option
period_for: :period, prefixed: false
# You can also rename any method that will be created
period_for :period, methods: { current: :ongoing, current?: :ongoing? }You can check the list of the methods that will be created on the configuration page for period.method_names.
This is where the period has its best features. With now provided Arel operators, a bunch of well-prepared statements can be used to query the records.
The default represents what was defined by the opposite of pessimistic option. If pessimistic is TRUE, then querying against NULL values will result in FALSE.
It basically checks if the range contains the given value or arel attribute.
COALESCE(NULLIF("events"."period", tsrange(NULL,NULL)) @> value, default)
-- With threshold
COALESCE(NULLIF(tsrange(LOWER("events"."period") - "events"."interval",UPPER("events"."period") + "events"."interval"), tsrange(NULL,NULL)) @> value, default)
Checks if the period contains the current time/date.
COALESCE(NULLIF("events"."period", tsrange(NULL,NULL)) @> Time.zone.now, default)
- With threshold -
COALESCE(NULLIF(tsrange(LOWER("events"."period") - "events"."interval",UPPER("events"."period") + "events"."interval"), tsrange(NULL,NULL)) @> Time.zone.now, default)
The opposite version of the :current scope.
NOT COALESCE(NULLIF("events"."period", tsrange(NULL,NULL)) @> Time.zone.now, default)
- With threshold -
NOT COALESCE(NULLIF(tsrange(LOWER("events"."period") - "events"."interval",UPPER("events"."period") + "events"."interval"), tsrange(NULL,NULL)) @> Time.zone.now, default)
Checks if the value contains in the range. You can pass either an Arel attribute or a plain value.
"events"."period" @> value
The opposite version of the :containing scope.
NOT "events"."period" @> value1
Checks if two ranges overlap. You can pass either another range column as an Arel attribute, a plain range on the left, or the 2 parts of a range.
"events"."period" && value
- OR -
"events"."period" && tsrange(left, right)
The opposite version of the :overlapping scope.
NOT "events"."period" && value
- OR -
NOT "events"."period" && tsrange(left, right)
Filter records that the left value is greater than the one provided, which can be either an Arel attribute or a plain value.
LOWER("events"."period") > value
Filter records that the left value is less than the one provided, which can be either an Arel attribute or a plain value.
LOWER("events"."period") < value
Filter records that the right value is greater than the one provided, which can be either an Arel attribute or a plain value.
UPPER("events"."period") > value
Filter records that the right value is less than the one provided, which can be either an Arel attribute or a plain value.
UPPER("events"."period") < value
Checks if the value contains in the range while considering the threshold. You can pass either an Arel attribute or a plain value.
tsrange(LOWER("events"."period") - "events"."interval",UPPER("events"."period") + "events"."interval") @> value
Checks if two ranges overlap while considering the threshold. You can pass either another range column as an Arel attribute, a plain range on the left, or the 2 parts of a range.
tsrange(LOWER("events"."period") - "events"."interval",UPPER("events"."period") + "events"."interval") && value
- OR -
tsrange(LOWER("events"."period") - "events"."interval",UPPER("events"."period") + "events"."interval") && tsrange(left, right)
Filter records that the left value with the threshold is greater than the one provided, which can be either an Arel attribute or a plain value.
(LOWER("events"."period") - "events"."interval") > value
Filter records that the left value with the threshold is less than the one provided, which can be either an Arel attribute or a plain value.
(LOWER("events"."period") - "events"."interval") < value
Filter records that the right value with the threshold is greater than the one provided, which can be either an Arel attribute or a plain value.
(UPPER("events"."period") + "events"."interval") > value
Filter records that the right value with the threshold is less than the one provided, which can be either an Arel attribute or a plain value.
(UPPER("events"."period") + "events"."interval") < value
Checks if the value contains in the range as date. You can pass either an Arel attribute or a plain value.
daterange(LOWER("events"."period")::date),UPPER("events"."period")::date) @> value
The opposite version of the :containing_date scope.
NOT daterange(LOWER("events"."period")::date),UPPER("events"."period")::date) @> value
- With threshold -
NOT daterange((LOWER("events"."period") - "events"."interval")::date),(UPPER("events"."period") + "events"."interval")::date) @> value
Checks if two ranges overlap but comparing only dates. You can pass either another range column as an Arel attribute, a plain range on the left, or the 2 parts of a range.
daterange(LOWER("events"."period")::date),UPPER("events"."period")::date) && value
- OR -
daterange(LOWER("events"."period")::date),UPPER("events"."period")::date) && daterange(left, right)
The opposite version of the :overlapping_date scope.
NOT daterange(LOWER("events"."period")::date),UPPER("events"."period")::date) && value
- OR -
NOT daterange(LOWER("events"."period")::date),UPPER("events"."period")::date) && daterange(left, right)
Checks if the value contains in the range as date and considering the threshold. You can pass either an Arel attribute or a plain value.
daterange((LOWER("events"."period") - "events"."interval")::date),(UPPER("events"."period") + "events"."interval")::date) @> value
Checks if two ranges overlap but comparing only dates and considering the threshold. You can pass either another range column as an Arel attribute, a plain range on the left, or the 2 parts of a range.
daterange((LOWER("events"."period") - "events"."interval")::date),(UPPER("events"."period") + "events"."interval")::date) && value
- OR -
daterange((LOWER("events"."period") - "events"."interval")::date),(UPPER("events"."period") + "events"."interval")::date) && daterange(left, right)
A couple of instance methods are provided as well. One of the options is to also use the new provided methods for the Range class.
Check if the value on the column represents a current period.
(period.min < Time.zone.now && period.max > Time.zone.now) || defaultSimilar to the above one, but allowing a valur to be passed.
(period.min < value && period.max > value) || defaultGet the beginning of the period.
period.minGet the ending of the period.
period.maxGet the real range period while considering the threshold.
((period.min - threshold)..(period.max + threshold))Get the beginning of the period while considering the threshold.
period.min - thresholdGet the ending of the period while considering the threshold.
period.max + thresholdCan't find what you're looking for? Add an issue to the issue tracker.