daru 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.build.sh +6 -6
  3. data/.gitignore +2 -0
  4. data/CONTRIBUTING.md +7 -3
  5. data/History.md +36 -0
  6. data/README.md +21 -13
  7. data/Rakefile +16 -1
  8. data/benchmarks/TradeoffData.csv +65 -0
  9. data/benchmarks/dataframe_creation.rb +39 -0
  10. data/benchmarks/group_by.rb +32 -0
  11. data/benchmarks/row_access.rb +41 -0
  12. data/benchmarks/row_assign.rb +36 -0
  13. data/benchmarks/sorting.rb +44 -0
  14. data/benchmarks/vector_access.rb +31 -0
  15. data/benchmarks/vector_assign.rb +42 -0
  16. data/benchmarks/where_clause.rb +48 -0
  17. data/benchmarks/where_vs_filter.rb +28 -0
  18. data/daru.gemspec +29 -5
  19. data/lib/daru.rb +30 -1
  20. data/lib/daru/accessors/array_wrapper.rb +2 -2
  21. data/lib/daru/accessors/nmatrix_wrapper.rb +6 -6
  22. data/lib/daru/core/group_by.rb +112 -31
  23. data/lib/daru/core/merge.rb +170 -0
  24. data/lib/daru/core/query.rb +95 -0
  25. data/lib/daru/dataframe.rb +335 -223
  26. data/lib/daru/date_time/index.rb +550 -0
  27. data/lib/daru/date_time/offsets.rb +397 -0
  28. data/lib/daru/index.rb +266 -54
  29. data/lib/daru/io/io.rb +1 -2
  30. data/lib/daru/maths/arithmetic/dataframe.rb +2 -2
  31. data/lib/daru/maths/arithmetic/vector.rb +2 -2
  32. data/lib/daru/maths/statistics/dataframe.rb +58 -8
  33. data/lib/daru/maths/statistics/vector.rb +229 -0
  34. data/lib/daru/vector.rb +230 -80
  35. data/lib/daru/version.rb +1 -1
  36. data/spec/core/group_by_spec.rb +16 -16
  37. data/spec/core/merge_spec.rb +52 -0
  38. data/spec/core/query_spec.rb +171 -0
  39. data/spec/dataframe_spec.rb +278 -280
  40. data/spec/date_time/data_spec.rb +199 -0
  41. data/spec/date_time/index_spec.rb +433 -0
  42. data/spec/date_time/offsets_spec.rb +371 -0
  43. data/spec/fixtures/stock_data.csv +500 -0
  44. data/spec/index_spec.rb +317 -11
  45. data/spec/io/io_spec.rb +18 -17
  46. data/spec/math/arithmetic/dataframe_spec.rb +3 -3
  47. data/spec/math/statistics/dataframe_spec.rb +39 -1
  48. data/spec/math/statistics/vector_spec.rb +163 -1
  49. data/spec/monkeys_spec.rb +4 -0
  50. data/spec/spec_helper.rb +3 -0
  51. data/spec/vector_spec.rb +125 -60
  52. metadata +71 -14
  53. data/lib/daru/accessors/dataframe_by_vector.rb +0 -17
  54. data/lib/daru/multi_index.rb +0 -216
  55. data/spec/multi_index_spec.rb +0 -216
@@ -0,0 +1,550 @@
1
+ module Daru
2
+ # Private module for storing helper functions for DateTimeIndex.
3
+ # @private
4
+ module DateTimeIndexHelper
5
+ class << self
6
+ OFFSETS_HASH = {
7
+ 'S' => Daru::Offsets::Second,
8
+ 'M' => Daru::Offsets::Minute,
9
+ 'H' => Daru::Offsets::Hour,
10
+ 'D' => Daru::Offsets::Day,
11
+ 'W' => Daru::Offsets::Week,
12
+ 'MONTH' => Daru::Offsets::Month,
13
+ 'MB' => Daru::Offsets::MonthBegin,
14
+ 'ME' => Daru::Offsets::MonthEnd,
15
+ 'YEAR' => Daru::Offsets::Year,
16
+ 'YB' => Daru::Offsets::YearBegin,
17
+ 'YE' => Daru::Offsets::YearEnd
18
+ }
19
+
20
+ TIME_INTERVALS = {
21
+ Rational(1,1) => Daru::Offsets::Day,
22
+ Rational(1,24) => Daru::Offsets::Hour,
23
+ Rational(1,1440) => Daru::Offsets::Minute,
24
+ Rational(1,86400) => Daru::Offsets::Second
25
+ }
26
+
27
+ # Generates a Daru::DateOffset object for generic offsets or one of the
28
+ # specialized classed within Daru::Offsets depending on the 'frequency'
29
+ # string.
30
+ def offset_from_frequency frequency
31
+ frequency = 'D' if frequency.nil?
32
+ return frequency if frequency.kind_of?(Daru::DateOffset)
33
+
34
+ matched = /([0-9]*)(MONTH|YEAR|S|H|MB|ME|M|D|W|YB|YE)/.match(frequency)
35
+ raise ArgumentError,
36
+ "Invalid frequency string #{frequency}" if matched.nil?
37
+
38
+ n = matched[1] == "" ? 1 : matched[1].to_i
39
+ offset_string = matched[2]
40
+ offset_klass = OFFSETS_HASH[offset_string]
41
+
42
+ raise ArgumentError,
43
+ "Cannont interpret offset #{offset_string}" if offset_klass.nil?
44
+
45
+ if offset_string.match(/W/)
46
+ day = Regexp.new(Daru::DAYS_OF_WEEK.keys.join('|')).match(frequency).to_s
47
+ return offset_klass.new(n, weekday: Daru::DAYS_OF_WEEK[day])
48
+ end
49
+
50
+ offset_klass.new(n)
51
+ end
52
+
53
+ def start_date start
54
+ start.is_a?(String) ? date_time_from(
55
+ start, determine_date_precision_of(start)) : start
56
+ end
57
+
58
+ def end_date en
59
+ en.is_a?(String) ? date_time_from(
60
+ en, determine_date_precision_of(en)) : en
61
+ end
62
+
63
+ def begin_from_offset? offset, start
64
+ if offset.kind_of?(Daru::Offsets::Tick) or
65
+ (offset.respond_to?(:on_offset?) and offset.on_offset?(start))
66
+ true
67
+ else
68
+ false
69
+ end
70
+ end
71
+
72
+ def generate_data start, en, offset, periods
73
+ data = []
74
+ new_date = begin_from_offset?(offset, start) ? start : offset + start
75
+
76
+ if periods.nil? # use end
77
+ loop do
78
+ break if new_date > en
79
+ data << new_date
80
+ new_date = offset + new_date
81
+ end
82
+ else
83
+ periods.times do |i|
84
+ data << new_date
85
+ new_date = offset + new_date
86
+ end
87
+ end
88
+
89
+ data
90
+ end
91
+
92
+ def verify_start_and_end start, en
93
+ raise ArgumentError, "Start and end cannot be the same" if start == en
94
+ raise ArgumentError, "Start must be lesser than end" if start > en
95
+ raise ArgumentError,
96
+ "Only same time zones are allowed" if start.zone != en.zone
97
+ end
98
+
99
+ def infer_offset data
100
+ possible_freq = data[1] - data[0]
101
+ inferred = true
102
+ data.each_cons(2) do |d|
103
+ if d[1] - d[0] != possible_freq
104
+ inferred = false
105
+ break
106
+ end
107
+ end
108
+
109
+ if inferred
110
+ TIME_INTERVALS[possible_freq].new
111
+ else
112
+ nil
113
+ end
114
+ end
115
+
116
+ def find_index_of_date data, date_time
117
+ searched = data.bsearch { |d| d[0] >= date_time }
118
+ (!searched.nil? and searched[0] == date_time) ? searched[1] :
119
+ raise(ArgumentError, "Cannot find #{date_time}")
120
+ end
121
+
122
+ def find_date_string_bounds date_string
123
+ date_precision = determine_date_precision_of date_string
124
+ date_time = date_time_from date_string, date_precision
125
+ generate_bounds date_time, date_precision
126
+ end
127
+
128
+ def date_time_from date_string, date_precision
129
+ case date_precision
130
+ when :year
131
+ DateTime.new(date_string.gsub(/[^0-9]/, '').to_i)
132
+ when :month
133
+ DateTime.new(
134
+ date_string.match(/\d\d\d\d/).to_s.to_i,
135
+ date_string.match(/\-\d?\d/).to_s.gsub("-",'').to_i)
136
+ else
137
+ DateTime.parse date_string
138
+ end
139
+ end
140
+
141
+ def determine_date_precision_of date_string
142
+ case date_string
143
+ when /\d\d\d\d\-\d?\d\-\d?\d \d?\d:\d?\d:\d?\d/
144
+ :sec
145
+ when /\d\d\d\d\-\d?\d\-\d?\d \d?\d:\d?\d/
146
+ :min
147
+ when /\d\d\d\d\-\d?\d\-\d?\d \d?\d/
148
+ :hour
149
+ when /\d\d\d\d\-\d?\d\-\d?\d/
150
+ :day
151
+ when /\d\d\d\d\-\d?\d/
152
+ :month
153
+ when /\d\d\d\d/
154
+ :year
155
+ else
156
+ raise ArgumentError, "Unacceptable date string #{date_string}"
157
+ end
158
+ end
159
+
160
+ def generate_bounds date_time, date_precision
161
+ case date_precision
162
+ when :year
163
+ [
164
+ date_time,
165
+ DateTime.new(date_time.year,12,31,23,59,59)
166
+ ]
167
+ when :month
168
+ [
169
+ date_time,
170
+ DateTime.new(date_time.year, date_time.month, ((date_time >> 1) - 1).day,
171
+ 23,59,59)
172
+ ]
173
+ when :day
174
+ [
175
+ date_time,
176
+ DateTime.new(date_time.year, date_time.month, date_time.day,23,59,59)
177
+ ]
178
+ when :hour
179
+ [
180
+ date_time,
181
+ DateTime.new(date_time.year, date_time.month, date_time.day,
182
+ date_time.hour,59,59)
183
+ ]
184
+ when :min
185
+ [
186
+ date_time,
187
+ DateTime.new(date_time.year, date_time.month, date_time.day,
188
+ date_time.hour, date_time.min, 59)
189
+ ]
190
+ else # second or when precision is same as offset
191
+ [ date_time, date_time ]
192
+ end
193
+ end
194
+
195
+ def possibly_convert_to_date_time data
196
+ data[0].is_a?(String) ? data.map! { |e| DateTime.parse(e) } : data
197
+ end
198
+
199
+ def last_date data
200
+ data.sort_by { |d| d[1] }.last
201
+ end
202
+
203
+ def key_out_of_bounds? key, data
204
+ precision = determine_date_precision_of key
205
+ date_time = date_time_from key, precision
206
+ case precision
207
+ when :year
208
+ date_time.year < data[0][0].year or date_time.year > data[-1][0].year
209
+ when :month
210
+ (date_time.year < data[0][0].year and date_time.month < data[0][0].month) or
211
+ (date_time.year > data[-1][0].year and date_time.month > data[-1][0].month)
212
+ end
213
+ end
214
+ end
215
+ end
216
+
217
+ class DateTimeIndex < Index
218
+ include Enumerable
219
+
220
+ def each(&block)
221
+ to_a.each(&block)
222
+ end
223
+
224
+ attr_reader :frequency, :offset, :periods
225
+
226
+ # Create a DateTimeIndex with or without a frequency in data. The constructor
227
+ # should be used for creating DateTimeIndex by directly passing in DateTime
228
+ # objects or date-like strings, typically in cases where values with frequency
229
+ # are not needed.
230
+ #
231
+ # @param [Array<String>, Array<DateTime>] data Array of date-like Strings or
232
+ # actual DateTime objects for creating the DateTimeIndex.
233
+ # @param [Hash] opts Hash of options for configuring index.
234
+ # @option opts [Symbol, NilClass, String, Daru::DateOffset, Daru::Offsets::*] freq
235
+ # Option for specifying the frequency of data, if applicable. If `:infer` is
236
+ # passed to this option, daru will try to infer the frequency of the data
237
+ # by itself.
238
+ #
239
+ # @example Usage of DateTimeIndex constructor
240
+ # index = Daru::DateTimeIndex.new(
241
+ # [DateTime.new(2012,4,5), DateTime.new(2012,4,6),
242
+ # DateTime.new(2012,4,7), DateTime.new(2012,4,8)])
243
+ # #=>#<DateTimeIndex:84232240 offset=nil periods=4 data=[2012-04-05T00:00:00+00:00...2012-04-08T00:00:00+00:00]>
244
+ #
245
+ # index = Daru::DateTimeIndex.new([
246
+ # DateTime.new(2012,4,5), DateTime.new(2012,4,6), DateTime.new(2012,4,7),
247
+ # DateTime.new(2012,4,8), DateTime.new(2012,4,9), DateTime.new(2012,4,10),
248
+ # DateTime.new(2012,4,11), DateTime.new(2012,4,12)], freq: :infer)
249
+ # #=>#<DateTimeIndex:84198340 offset=D periods=8 data=[2012-04-05T00:00:00+00:00...2012-04-12T00:00:00+00:00]>
250
+ def initialize *args
251
+ helper = DateTimeIndexHelper
252
+
253
+ data = args[0]
254
+ opts = args[1] || {freq: nil}
255
+
256
+ helper.possibly_convert_to_date_time data
257
+
258
+ @offset =
259
+ case opts[:freq]
260
+ when :infer then helper.infer_offset(data)
261
+ when nil then nil
262
+ else helper.offset_from_frequency(opts[:freq])
263
+ end
264
+
265
+ @frequency = @offset ? @offset.freq_string : nil
266
+ @data = data.zip(Array.new(data.size) { |i| i })
267
+ @data.sort_by! { |d| d[0] } if @offset.nil?
268
+ @periods = data.size
269
+ end
270
+
271
+ # Create a date range by specifying the start, end, periods and frequency
272
+ # of the data.
273
+ #
274
+ # @param [Hash] opts Options hash to create the date range with
275
+ # @option opts [String, DateTime] :start A DateTime object or date-like
276
+ # string that defines the start of the date range.
277
+ # @option opts [String, DateTime] :end A DateTime object or date-like string
278
+ # that defines the end of the date range.
279
+ # @option opts [String, Daru::DateOffset, Daru::Offsets::*] :freq ('D') The interval
280
+ # between each date in the index. This can either be a string specifying
281
+ # the frequency (i.e. one of the frequency aliases) or an offset object.
282
+ # @option opts [Fixnum] :periods The number of periods that should go into
283
+ # this index. Takes precedence over `:end`.
284
+ # @return [DateTimeIndex] DateTimeIndex object of the specified parameters.
285
+ #
286
+ # == Notes
287
+ #
288
+ # If you specify :start and :end options as strings, they can be complete or
289
+ # partial dates and daru will intelligently infer the date from the string
290
+ # directly. However, note that the date-like string must be in the format
291
+ # `YYYY-MM-DD HH:MM:SS`.
292
+ #
293
+ # The string aliases supported by the :freq option are as follows:
294
+ #
295
+ # * 'S' - seconds
296
+ # * 'M' - minutes
297
+ # * 'H' - hours
298
+ # * 'D' - days
299
+ # * 'W' - Week (default) anchored on sunday
300
+ # * 'W-SUN' - Same as 'W'
301
+ # * 'W-MON' - Week anchored on monday
302
+ # * 'W-TUE' - Week anchored on tuesday
303
+ # * 'W-WED' - Week anchored on wednesday
304
+ # * 'W-THU' - Week anchored on thursday
305
+ # * 'W-FRI' - Week anchored on friday
306
+ # * 'W-SAT' - Week anchored on saturday
307
+ # * 'MONTH' - Month
308
+ # * 'YEAR' - One year
309
+ # * 'MB' - month begin
310
+ # * 'ME' - month end
311
+ # * 'YB' - year begin
312
+ # * 'YE' - year end
313
+ #
314
+ # Multiples of these can also be specified. For example '2S' for 2 seconds
315
+ # or '2ME' for two month end offsets.
316
+ #
317
+ # Currently the precision of DateTimeIndex is upto seconds only, though this
318
+ # will improve in the future.
319
+ #
320
+ # @example Creating date ranges
321
+ # Daru::DateTimeIndex.date_range(
322
+ # :start => DateTime.new(2014,5,1),
323
+ # :end => DateTime.new(2014,5,2), :freq => '6H')
324
+ # #=>#<DateTimeIndex:83600130 offset=H periods=5 data=[2014-05-01T00:00:00+00:00...2014-05-02T00:00:00+00:00]>
325
+ #
326
+ # Daru::DateTimeIndex.date_range(
327
+ # :start => '2012-5-2', :periods => 50, :freq => 'ME')
328
+ # #=> #<DateTimeIndex:83549940 offset=ME periods=50 data=[2012-05-31T00:00:00+00:00...2016-06-30T00:00:00+00:00]>
329
+ def self.date_range opts={}
330
+ helper = DateTimeIndexHelper
331
+
332
+ start = helper.start_date opts[:start]
333
+ en = helper.end_date opts[:end]
334
+ helper.verify_start_and_end(start, en) unless en.nil?
335
+ offset = helper.offset_from_frequency opts[:freq]
336
+ data = helper.generate_data start, en, offset, opts[:periods]
337
+
338
+ DateTimeIndex.new(data, :freq => offset)
339
+ end
340
+
341
+ # Retreive a slice or a an individual index number from the index.
342
+ #
343
+ # @param [String, DateTime] Specify a date partially (as a String) or
344
+ # completely to retrieve.
345
+ def [] *key
346
+ helper = DateTimeIndexHelper
347
+ if key.size == 1
348
+ key = key[0]
349
+ return key if key.is_a?(Numeric)
350
+ else
351
+ return slice(*key)
352
+ end
353
+
354
+ if key.is_a?(Range)
355
+ first = key.first
356
+ last = key.last
357
+ return slice(first, last) if
358
+ first.is_a?(Fixnum) and last.is_a?(Fixnum)
359
+
360
+ raise ArgumentError, "Keys #{first} and #{last} are out of bounds" if
361
+ helper.key_out_of_bounds?(first, @data) and helper.key_out_of_bounds?(last, @data)
362
+
363
+ slice_begin = helper.find_date_string_bounds(first)[0]
364
+ slice_end = helper.find_date_string_bounds(last)[1]
365
+ else
366
+ if key.is_a?(DateTime)
367
+ return helper.find_index_of_date(@data, key)
368
+ else
369
+ raise ArgumentError, "Key #{key} is out of bounds" if
370
+ helper.key_out_of_bounds?(key, @data)
371
+
372
+ slice_begin, slice_end = helper.find_date_string_bounds key
373
+ end
374
+ end
375
+
376
+ slice slice_begin, slice_end
377
+ end
378
+
379
+ # Retrive a slice of the index by specifying first and last members of the slice.
380
+ #
381
+ # @param [String, DateTime] first Start of the slice as a string or DateTime.
382
+ # @param [String, DateTime] last End of the slice as a string or DateTime.
383
+ def slice first, last
384
+ helper = DateTimeIndexHelper
385
+
386
+ if first.is_a?(String) and last.is_a?(String)
387
+ self[first..last]
388
+ elsif first.is_a?(Fixnum) and last.is_a?(Fixnum)
389
+ DateTimeIndex.new(self.to_a[first..last], freq: @offset)
390
+ else
391
+ first_dt = first.is_a?(String) ?
392
+ helper.find_date_string_bounds(first)[0] : first
393
+ last_dt = last.is_a?(String) ?
394
+ helper.find_date_string_bounds(last)[1] : last
395
+
396
+ start = @data.bsearch { |d| d[0] >= first_dt }
397
+ after_en = @data.bsearch { |d| d[0] > last_dt }
398
+
399
+ result =
400
+ if @offset
401
+ en = after_en ? @data[after_en[1] - 1] : @data.last
402
+ return start[1] if start == en
403
+ DateTimeIndex.date_range :start => start[0], :end => en[0], freq: @offset
404
+ else
405
+ st = @data.index(start)
406
+ en = after_en ? @data.index(after_en) - 1 : helper.last_date(@data)[1]
407
+ return start[1] if st == en
408
+ DateTimeIndex.new(@data[st..en].transpose[0])
409
+ end
410
+
411
+ result
412
+ end
413
+ end
414
+
415
+ # Return the DateTimeIndex as an Array of DateTime objects.
416
+ # @return [Array<DateTime>] Array of containing DateTimes.
417
+ def to_a
418
+ return @data.sort_by { |d| d[1] }.transpose[0] unless @offset
419
+ @data.transpose[0]
420
+ end
421
+
422
+ # Size of index.
423
+ def size
424
+ @periods
425
+ end
426
+
427
+ def == other
428
+ self.to_a == other.to_a
429
+ end
430
+
431
+ def inspect
432
+ string = "#<DateTimeIndex:" + self.object_id.to_s + " offset=" +
433
+ (@offset ? @offset.freq_string : 'nil') + ' periods=' + @periods.to_s +
434
+ " data=[" + @data.first[0].to_s + "..." + @data.last[0].to_s + ']'+ '>'
435
+
436
+ string
437
+ end
438
+
439
+ # Shift all dates in the index by a positive number in the future. The dates
440
+ # are shifted by the same amount as that specified in the offset.
441
+ #
442
+ # @param [Fixnum, Daru::DateOffset, Daru::Offsets::*] distance Distance by
443
+ # which each date should be shifted. Passing an offset object to #shift
444
+ # will offset each data point by the offset value. Passing a positive
445
+ # integer will offset each data point by the same offset that it was
446
+ # created with.
447
+ # @return [DateTimeIndex] Returns a new, shifted DateTimeIndex object.
448
+ # @example Using the shift method
449
+ # index = Daru::DateTimeIndex.date_range(
450
+ # :start => '2012', :periods => 10, :freq => 'YEAR')
451
+ #
452
+ # # Passing a offset to shift
453
+ # index.shift(Daru::Offsets::Hour.new(3))
454
+ # #=>#<DateTimeIndex:84038960 offset=nil periods=10 data=[2012-01-01T03:00:00+00:00...2021-01-01T03:00:00+00:00]>
455
+ #
456
+ # # Pass an integer to shift
457
+ # index.shift(4)
458
+ # #=>#<DateTimeIndex:83979630 offset=YEAR periods=10 data=[2016-01-01T00:00:00+00:00...2025-01-01T00:00:00+00:00]>
459
+ def shift distance
460
+ if distance.is_a?(Fixnum)
461
+ raise IndexError, "Distance #{distance} cannot be negative" if distance < 0
462
+ if @offset
463
+ start = @data[0][0]
464
+ distance.times { start = @offset + start }
465
+ return DateTimeIndex.date_range(
466
+ :start => start, :periods => @periods, freq: @offset)
467
+ else
468
+ raise IndexError, "To shift non-freq date time index pass an offset."
469
+ end
470
+ else # its a Daru::Offset/DateOffset
471
+ DateTimeIndex.new(self.to_a.map { |e| distance + e }, freq: :infer)
472
+ end
473
+ end
474
+
475
+ # Shift all dates in the index to the past. The dates are shifted by the same
476
+ # amount as that specified in the offset.
477
+ #
478
+ # @param [Fixnum, Daru::DateOffset, Daru::Offsets::*] distance Fixnum or
479
+ # Daru::DateOffset. Distance by which each date should be shifted. Passing
480
+ # an offset object to #lag will offset each data point by the offset value.
481
+ # Passing a positive integer will offset each data point by the same offset
482
+ # that it was created with.
483
+ # @return [DateTimeIndex] A new lagged DateTimeIndex object.
484
+ def lag distance
485
+ if distance.is_a?(Fixnum)
486
+ raise IndexError, "Distance #{distance} cannot be negative" if distance < 0
487
+ if @offset
488
+ start = @data[0][0]
489
+ distance.times { start = @offset - start }
490
+ return DateTimeIndex.date_range(
491
+ :start => start, :periods => @periods, freq: @offset)
492
+ else
493
+ raise IndexError, "To lag non-freq date time index pass an offset."
494
+ end
495
+ else
496
+ DateTimeIndex.new(self.to_a.map { |e| distance - e }, freq: :infer)
497
+ end
498
+ end
499
+
500
+ def _dump depth
501
+ Marshal.dump({data: self.to_a, freq: @offset})
502
+ end
503
+
504
+ def self._load data
505
+ h = Marshal.load data
506
+
507
+ Daru::DateTimeIndex.new(h[:data], freq: h[:freq])
508
+ end
509
+
510
+ # @!method year
511
+ # @return [Array<Fixnum>] Array containing year of each index.
512
+ # @!method month
513
+ # @return [Array<Fixnum>] Array containing month of each index.
514
+ # @!method day
515
+ # @return [Array<Fixnum>] Array containing day of each index.
516
+ # @!method hour
517
+ # @return [Array<Fixnum>] Array containing hour of each index.
518
+ # @!method min
519
+ # @return [Array<Fixnum>] Array containing minutes of each index.
520
+ # @!method sec
521
+ # @return [Array<Fixnum>] Array containing seconds of each index.
522
+ [:year, :month, :day, :hour, :min, :sec].each do |meth|
523
+ define_method(meth) do
524
+ self.inject([]) do |arr, d|
525
+ arr << d.send(meth)
526
+ arr
527
+ end
528
+ end
529
+ end
530
+
531
+ # Check if a date exists in the index. Will be inferred from string in case
532
+ # you pass a string. Recommened specifying the full date as a DateTime object.
533
+ def include? date_time
534
+ return false if !(date_time.is_a?(String) or date_time.is_a?(DateTime))
535
+ helper = DateTimeIndexHelper
536
+ if date_time.is_a?(String)
537
+ date_precision = helper.determine_date_precision_of date_time
538
+ date_time = helper.date_time_from date_time, date_precision
539
+ end
540
+
541
+ result = @data.bsearch {|d| d[0] >= date_time }
542
+ result[0] == date_time
543
+ end
544
+
545
+ # Return true if the DateTimeIndex is empty.
546
+ def empty?
547
+ @data.empty?
548
+ end
549
+ end
550
+ end