Class: Timely::TemporalPatterns::Pattern

Inherits:
Object
  • Object
show all
Defined in:
lib/timely/temporal_patterns/pattern.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(ranges, frequency) ⇒ Pattern

Returns a new instance of Pattern.



8
9
10
11
12
# File 'lib/timely/temporal_patterns/pattern.rb', line 8

def initialize(ranges, frequency)
  @intervals = Array.wrap(ranges).map { |r| Interval.new(r.first, r.last) }.sort_by(&:first_datetime)
  @frequency = Frequency.new(frequency)
  fix_frequency
end

Instance Attribute Details

#frequencyObject (readonly)

Returns the value of attribute frequency.



6
7
8
# File 'lib/timely/temporal_patterns/pattern.rb', line 6

def frequency
  @frequency
end

#intervalsObject (readonly)

Returns the value of attribute intervals.



6
7
8
# File 'lib/timely/temporal_patterns/pattern.rb', line 6

def intervals
  @intervals
end

Instance Method Details

#<=>(other) ⇒ Object



57
58
59
# File 'lib/timely/temporal_patterns/pattern.rb', line 57

def <=>(other)
  self.intervals.count <=> other.intervals.count
end

#datetimesObject

Convert each interval to a list of datetimes



15
16
17
18
19
20
21
22
23
24
25
# File 'lib/timely/temporal_patterns/pattern.rb', line 15

def datetimes
  intervals.map do |interval|
    datetimes = []
    datetime = interval.first_datetime
    while datetime <= interval.last_datetime
      datetimes << datetime
      datetime = datetime + frequency.duration
    end
    datetimes
  end
end

#first_datetimeObject



31
32
33
# File 'lib/timely/temporal_patterns/pattern.rb', line 31

def first_datetime
  surrounding_interval.first_datetime
end

#join(other) ⇒ Object

Join with other IF same frequency AND same number of intervals



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/timely/temporal_patterns/pattern.rb', line 62

def join(other)
  return nil unless self.frequency == other.frequency

  expanded_datetimes = self.datetimes.map { |datetimes_within_an_interval|
    back_one    = datetimes_within_an_interval.first - frequency.duration
    forward_one = datetimes_within_an_interval.last + frequency.duration

    [back_one] + datetimes_within_an_interval + [forward_one]
  }

  joint_ranges = []

  # Look for overlaps, where an overlap may be 'off by 1' -- hence the 'expanded_datetimes'
  # ...but start with other and join to each of it's intervals.
  #
  # Remember that 'pattern.datetimes' returns a list of datetimes per interval
  other.datetimes.each do |other_datetimes_within_an_interval|

    joinable_datetimes = expanded_datetimes.find { |expanded_datetimes_within_an_interval|
      other_datetimes_within_an_interval.any? { |d|
        expanded_datetimes_within_an_interval.include?(d)
      }
    }
    break unless joinable_datetimes

    # Joint ranges should be those that overlap
    #
    # This is buggy, because joinable_datetimes is a list of datetimes per interval that overlap
    # Excluding the first doesn't make sense
    #
    # Instead, we should exclude the first AND last for each element within joinable_datetimes 
    joint_datetimes = (other_datetimes_within_an_interval + joinable_datetimes[1...-1]).sort
    joint_ranges << (joint_datetimes.first..joint_datetimes.last)
  end

  # This seems to be trying to say "Only join when we got one for each interval of self"
  # ...it also seems too restrictive...
  #
  # What if other includes multiple intervals of self?
  # Then we don't need same number of intervals
  #
  # Also might be wrong in other ways, it's tricky to tell
  if joint_ranges.size == self.intervals.size
    Pattern.new(joint_ranges, frequency.duration)
  end
end

#last_datetimeObject



35
36
37
# File 'lib/timely/temporal_patterns/pattern.rb', line 35

def last_datetime
  surrounding_interval.last_datetime
end

#match?(datetimes) ⇒ Boolean

Returns:

  • (Boolean)


44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/timely/temporal_patterns/pattern.rb', line 44

def match?(datetimes)
  datetimes = Array.wrap(datetimes).map(&:to_datetime)
  intervals.each do |interval|
    current_datetime = interval.first_datetime
    while current_datetime <= interval.last_datetime
      datetimes.delete_if { |datetime| datetime == current_datetime }
      return true if datetimes.empty?
      current_datetime = current_datetime + frequency.duration
    end
  end
  false
end

#rangesObject



27
28
29
# File 'lib/timely/temporal_patterns/pattern.rb', line 27

def ranges
  intervals.map { |i| (i.first_datetime..i.last_datetime) }
end

#surrounding_intervalObject Also known as: interval



39
40
41
# File 'lib/timely/temporal_patterns/pattern.rb', line 39

def surrounding_interval
  Interval.surrounding(intervals)
end

#to_sObject



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/timely/temporal_patterns/pattern.rb', line 109

def to_s
  single_date_intervals, multiple_dates_intervals = intervals.partition { |i| i.first_datetime == i.last_datetime}
  patterns_strings = if multiple_dates_intervals.empty?
    single_date_intervals.map(&:to_s)
  else
    interval_surrounding_multiple_dates = Interval.surrounding(multiple_dates_intervals)

    multiple_dates_intervals_string = case frequency.unit
    when :years
      "every #{multiple_dates_intervals.map { |i| "#{i.first_datetime.day.ordinalize} of #{i.first_datetime.strftime('%B')}" }.uniq.to_sentence} #{interval_surrounding_multiple_dates}"
    when :months
      "every #{multiple_dates_intervals.map { |i| i.first_datetime.day.ordinalize }.uniq.to_sentence} of the month #{interval_surrounding_multiple_dates}"
    when :weeks
      weekdays = multiple_dates_intervals.map { |i| i.first_datetime.strftime('%A') }.uniq
      if weekdays.count == 7
        "every day #{interval_surrounding_multiple_dates}"
      else
        "every #{weekdays.to_sentence} #{interval_surrounding_multiple_dates}"
      end
    when :days
      if multiple_dates_intervals.any? { |i| i.first_datetime != i.first_datetime.beginning_of_day }
        "every day at #{multiple_dates_intervals.map { |i| i.first_datetime.strftime("%I:%M %p") }.to_sentence} #{interval_surrounding_multiple_dates}"
      else
        "every day #{interval_surrounding_multiple_dates}"
      end
    else
      "#{frequency} #{multiple_dates_intervals.map(&:to_s).to_sentence}"
    end
    [multiple_dates_intervals_string] + single_date_intervals.map(&:to_s)
  end
  patterns_strings.to_sentence
end