-
-
Notifications
You must be signed in to change notification settings - Fork 15
Date Time Range
I hope it's not only me that can't take
start_time
andfinish_time
ANYMORE!!!
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
end
You 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
end
There 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) || default
Similar to the above one, but allowing a valur to be passed.
(period.min < value && period.max > value) || default
Get the beginning of the period.
period.min
Get the ending of the period.
period.max
Get 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 - threshold
Get the ending of the period while considering the threshold.
period.max + threshold
Can't find what you're looking for? Add an issue to the issue tracker.