daru 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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