# frozen_string_literal: true module Timely class DateGroup < ActiveRecord::Base belongs_to :season, class_name: 'Timely::Season', optional: true, inverse_of: :date_groups weekdays_field :weekdays validates :weekdays_bit_array, presence: true validates_presence_of :start_date, :end_date validate :validate_date_range! scope :covering_date, lambda { |date| # Suitable for use with joins/merge! where(arel_table[:start_date].lteq(date)).where(arel_table[:end_date].gteq(date)) } scope :within_range, lambda { |date_range| # IMPORTANT: Required for correctness in case of string param. dates = Array(date_range) where(arel_table[:start_date].lteq(dates.last)).where(arel_table[:end_date].gteq(dates.first)) } scope :for_any_weekdays, lambda { |weekdays_int| where((arel_table[:weekdays_bit_array] & weekdays_int.to_i).not_eq(0)) } scope :applying_for_duration, lambda { |date_range| weekdays_int = Timely::WeekDays.from_range(date_range).weekdays_int within_range(date_range).for_any_weekdays(weekdays_int) } def includes_date?(date) date >= start_date && date <= end_date && weekdays.applies_for_date?(date) end def applicable_for_duration?(date_range) if date_range.first > end_date || date_range.last < start_date false elsif weekdays.all_days? true else date_range.intersecting_dates(start_date..end_date).any? { |d| weekdays.applies_for_date?(d) } end end def dates start_date.upto(end_date).select { |d| weekdays.applies_for_date?(d) } end def to_s str = start_date && end_date ? (start_date..end_date).to_date_range.to_s : (start_date || end_date).to_s str += " on #{weekdays}" unless weekdays.all_days? str end ################################################################ #---------------- Date intervals and patterns -----------------# ################################################################ def pattern ranges = dates.group_by(&:wday).values.map { |weekdates| (weekdates.min..weekdates.max) } TemporalPatterns::Pattern.new(ranges, 1.week) end def self.from_patterns(patterns) date_groups = [] Array.wrap(patterns).each do |pattern| if pattern.frequency.unit == :weeks weekdays = pattern.intervals.map { |i| i.first_datetime.wday }.each_with_object({}) do |wday, hash| hash[wday] = 1 end date_groups << DateGroup.new( start_date: pattern.first_datetime.to_date, end_date: pattern.last_datetime.to_date, weekdays: weekdays ) elsif pattern.frequency.unit == :days && pattern.frequency.duration == 1.day date_groups << DateGroup.new( start_date: pattern.first_datetime.to_date, end_date: pattern.last_datetime.to_date, weekdays: 127 ) else pattern.datetimes.each do |datetimes| datetimes.group_by(&:week).values.each do |dates| weekdays = dates.map(&:wday).each_with_object({}) do |wday, hash| hash[wday] = 1 end date_groups << DateGroup.new( start_date: dates.min.to_date.beginning_of_week, end_date: dates.max.to_date.end_of_week, weekdays: weekdays ) end end end end date_groups end private def validate_date_range! return unless start_date && end_date && start_date > end_date raise ArgumentError, "Incorrect date range #{start_date} is before #{end_date}" end end end