Module: Daru::DateTimeIndexHelper

Defined in:
lib/daru/date_time/index.rb

Overview

Private module for storing helper functions for DateTimeIndex.

Constant Summary collapse

OFFSETS_HASH =
{
  'S'  => Daru::Offsets::Second,
  'M'  => Daru::Offsets::Minute,
  'H'  => Daru::Offsets::Hour,
  'D'  => Daru::Offsets::Day,
  'W'  => Daru::Offsets::Week,
  'MONTH' => Daru::Offsets::Month,
  'MB' => Daru::Offsets::MonthBegin,
  'ME' => Daru::Offsets::MonthEnd,
  'YEAR' => Daru::Offsets::Year,
  'YB' => Daru::Offsets::YearBegin,
  'YE' => Daru::Offsets::YearEnd
}.freeze
TIME_INTERVALS =
{
  Rational(1,1)     => Daru::Offsets::Day,
  Rational(1,24)    => Daru::Offsets::Hour,
  Rational(1,1440)  => Daru::Offsets::Minute,
  Rational(1,86_400) => Daru::Offsets::Second
}.freeze
DOW_REGEXP =
Regexp.new(Daru::DAYS_OF_WEEK.keys.join('|'))
FREQUENCY_PATTERN =
/^
(?<multiplier>[0-9]+)?
(
  (?<offset>MONTH|YEAR|S|H|MB|ME|M|D|YB|YE) |
  (?<offset>W)(-(?<weekday>#{DOW_REGEXP}))?
)$/x
DATE_PRECISION_REGEXP =
/^(\d\d\d\d)(-\d{1,2}(-\d{1,2}( \d{1,2}(:\d{1,2}(:\d{1,2})?)?)?)?)?$/
DATE_PRECISIONS =
[nil, :year, :month, :day, :hour, :min, :sec].freeze

Class Method Summary collapse

Class Method Details

.begin_from_offset?(offset, start) ⇒ Boolean

Returns:

  • (Boolean)


62
63
64
65
# File 'lib/daru/date_time/index.rb', line 62

def begin_from_offset? offset, start
  offset.is_a?(Daru::Offsets::Tick) ||
    offset.respond_to?(:on_offset?) && offset.on_offset?(start)
end

.coerce_date(date) ⇒ Object



57
58
59
60
# File 'lib/daru/date_time/index.rb', line 57

def coerce_date date
  return date unless date.is_a?(String)
  date_time_from(date, determine_date_precision_of(date))
end

.date_time_from(date_string, date_precision) ⇒ Object



116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/daru/date_time/index.rb', line 116

def date_time_from date_string, date_precision
  case date_precision
  when :year
    DateTime.new(date_string.gsub(/[^0-9]/, '').to_i)
  when :month
    DateTime.new(
      date_string.match(/\d\d\d\d/).to_s.to_i,
      date_string.match(/\-\d?\d/).to_s.delete('-').to_i
    )
  else
    DateTime.parse date_string
  end
end

.determine_date_precision_of(date_string) ⇒ Object



133
134
135
136
137
# File 'lib/daru/date_time/index.rb', line 133

def determine_date_precision_of date_string
  components = date_string.scan(DATE_PRECISION_REGEXP).flatten.compact
  DATE_PRECISIONS[components.count] or
    raise ArgumentError, "Unacceptable date string #{date_string}"
end

.find_date_string_bounds(date_string) ⇒ Object



110
111
112
113
114
# File 'lib/daru/date_time/index.rb', line 110

def find_date_string_bounds date_string
  date_precision = determine_date_precision_of date_string
  date_time = date_time_from date_string, date_precision
  generate_bounds date_time, date_precision
end

.find_index_of_date(data, date_time) ⇒ Object

Raises:

  • (ArgumentError)


103
104
105
106
107
108
# File 'lib/daru/date_time/index.rb', line 103

def find_index_of_date data, date_time
  searched = data.bsearch { |d| d[0] >= date_time }
  raise(ArgumentError, "Cannot find #{date_time}") if searched.nil? || searched[0] != date_time

  searched[1]
end

.generate_bounds(date_time, date_precision) ⇒ Object

rubocop:disable Metrics/AbcSize,Metrics/MethodLength



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/daru/date_time/index.rb', line 139

def generate_bounds date_time, date_precision # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
  # FIXME: about that ^ disable: I'd like to use my zverok/time_boots here, which will simplify things
  case date_precision
  when :year
    [
      date_time,
      DateTime.new(date_time.year,12,31,23,59,59)
    ]
  when :month
    [
      date_time,
      DateTime.new(date_time.year, date_time.month, ((date_time >> 1) - 1).day,
        23,59,59)
    ]
  when :day
    [
      date_time,
      DateTime.new(date_time.year, date_time.month, date_time.day,23,59,59)
    ]
  when :hour
    [
      date_time,
      DateTime.new(date_time.year, date_time.month, date_time.day,
        date_time.hour,59,59)
    ]
  when :min
    [
      date_time,
      DateTime.new(date_time.year, date_time.month, date_time.day,
        date_time.hour, date_time.min, 59)
    ]
  else # second or when precision is same as offset
    [date_time, date_time]
  end
end

.generate_data(start, en, offset, periods) ⇒ Object



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/daru/date_time/index.rb', line 67

def generate_data start, en, offset, periods
  data = []
  new_date = begin_from_offset?(offset, start) ? start : offset + start

  if periods.nil? # use end
    loop do
      break if new_date > en
      data << new_date
      new_date = offset + new_date
    end
  else
    periods.times do
      data << new_date
      new_date = offset + new_date
    end
  end

  data
end

.infer_offset(data) ⇒ Object



93
94
95
96
97
98
99
100
101
# File 'lib/daru/date_time/index.rb', line 93

def infer_offset data
  diffs = data.each_cons(2).map { |d1, d2| d2 - d1 }

  if diffs.uniq.count == 1
    TIME_INTERVALS[diffs.first].new
  else
    nil
  end
end

.key_out_of_bounds?(key, data) ⇒ Boolean

Returns:

  • (Boolean)


183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/daru/date_time/index.rb', line 183

def key_out_of_bounds? key, data
  dates = data.transpose.first

  precision = determine_date_precision_of key
  date_time = date_time_from key, precision

  # FIXME: I'm pretty suspicious about logic here:
  # why only year & month? - zverok 2016-05-16

  case precision
  when :year
    year_out_of_bounds?(date_time, dates)
  when :month
    year_month_out_of_bounds?(date_time, dates)
  end
end

.last_date(data) ⇒ Object



179
180
181
# File 'lib/daru/date_time/index.rb', line 179

def last_date data
  data.sort_by { |d| d[1] }.last
end

.offset_from_frequency(frequency) ⇒ Object

Generates a Daru::DateOffset object for generic offsets or one of the specialized classed within Daru::Offsets depending on the ‘frequency’ string.



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/daru/date_time/index.rb', line 38

def offset_from_frequency frequency
  return frequency if frequency.is_a?(Daru::DateOffset)
  frequency ||= 'D'

  matched = FREQUENCY_PATTERN.match(frequency) or
    raise ArgumentError, "Invalid frequency string #{frequency}"

  n             = (matched[:multiplier] || 1).to_i
  offset_string = matched[:offset]
  offset_klass  = OFFSETS_HASH[offset_string] or
    raise ArgumentError, "Cannont interpret offset #{offset_string}"

  if offset_string == 'W'
    offset_klass.new(n, weekday: Daru::DAYS_OF_WEEK[matched[:weekday]])
  else
    offset_klass.new(n)
  end
end

.possibly_convert_to_date_time(data) ⇒ Object



175
176
177
# File 'lib/daru/date_time/index.rb', line 175

def possibly_convert_to_date_time data
  data[0].is_a?(String) ? data.map! { |e| DateTime.parse(e) } : data
end

.verify_start_and_end(start, en) ⇒ Object

Raises:

  • (ArgumentError)


87
88
89
90
91
# File 'lib/daru/date_time/index.rb', line 87

def verify_start_and_end start, en
  raise ArgumentError, 'Start and end cannot be the same' if start == en
  raise ArgumentError, 'Start must be lesser than end'    if start > en
  raise ArgumentError, 'Only same time zones are allowed' if start.zone != en.zone
end