From 438cdd5b68d4eaf486f77b8582edcd7cdb287b4e Mon Sep 17 00:00:00 2001 From: Jim Myhrberg Date: Thu, 29 Jul 2010 04:25:42 +0300 Subject: [PATCH] Interation methods #each, #map_each and more are implemented and working. Also restructured files again. --- lib/time_ext.rb | 12 +++- lib/time_ext/core_ext/ranges.rb | 56 --------------- .../core_ext/{ => time}/calculations.rb | 20 ++++++ lib/time_ext/core_ext/time/iterations.rb | 71 +++++++++++++++++++ lib/time_ext/method_chain.rb | 18 +++++ ..._ext_spec.rb => time_calculations_spec.rb} | 2 +- spec/time_iterations_spec.rb | 63 ++++++++++++++++ 7 files changed, 184 insertions(+), 58 deletions(-) delete mode 100644 lib/time_ext/core_ext/ranges.rb rename lib/time_ext/core_ext/{ => time}/calculations.rb (90%) create mode 100644 lib/time_ext/core_ext/time/iterations.rb create mode 100644 lib/time_ext/method_chain.rb rename spec/{time_ext_spec.rb => time_calculations_spec.rb} (99%) create mode 100644 spec/time_iterations_spec.rb diff --git a/lib/time_ext.rb b/lib/time_ext.rb index 14ea880..770ef40 100644 --- a/lib/time_ext.rb +++ b/lib/time_ext.rb @@ -1,3 +1,13 @@ require 'rubygems' require 'active_support' -require 'time_ext/time' \ No newline at end of file +require 'time_ext/method_chain' +require 'time_ext/core_ext/time/calculations' +require 'time_ext/core_ext/time/iterations' + +module TimeExt + + module Base + TIME_EXT_UNITS = [:year, :month, :day, :hour, :min, :sec, :usec] + end + +end \ No newline at end of file diff --git a/lib/time_ext/core_ext/ranges.rb b/lib/time_ext/core_ext/ranges.rb deleted file mode 100644 index 69a636a..0000000 --- a/lib/time_ext/core_ext/ranges.rb +++ /dev/null @@ -1,56 +0,0 @@ -class Time - - # Executes passed block for each unit of time specified - def each(unit, &block) - if block_given? - @each_until ||= self.tomorrow #TODO should be next larger unit - time = self.clone - while time <= @each_until - yield(time) - time = time.send("next_#{unit}") - end - self - else - @method_chain ||= [] - @method_chain << [:each, [unit]] - self - end - end - alias :iterate :each - alias :beginning_of_each :each - - # Executes passed block for each unit of time specified - def map(unit, &block) - if block_given? - @each_until ||= self.tomorrow #TODO should be next larger unit - time = self.clone - result = [] - while time <= @each_until - result << yield(time) - time = time.send("next_#{unit}") - end - result - else - @method_chain ||= [] - @method_chain << [:map, [unit]] - self - end - end - alias :iterate :each - - # Used togeter with #each to specify end of interation - def until(time, &block) - time = time.to_time if time.is_a?(::Date) - @each_until = time - if block_given? - method, args = @method_chain.pop - self.send(method, *args, &block) - else - @method_chain ||= [] - @method_chain << [:until, [time]] - self - end - end - alias :till :until - -end \ No newline at end of file diff --git a/lib/time_ext/core_ext/calculations.rb b/lib/time_ext/core_ext/time/calculations.rb similarity index 90% rename from lib/time_ext/core_ext/calculations.rb rename to lib/time_ext/core_ext/time/calculations.rb index 228d024..8940beb 100644 --- a/lib/time_ext/core_ext/calculations.rb +++ b/lib/time_ext/core_ext/time/calculations.rb @@ -32,6 +32,16 @@ class Time end alias :beginning_of_closest :round + # Returns a new Time representing the previoius unit specified (defaults to second). + def prev(unit = :sec) + send("prev_#{unit}") + end + + # Returns a new Time representing the next unit specified (defaults to second). + def next(unit = :sec) + send("next_#{unit}") + end + # Short-hand for seconds_ago(1). def prev_second ago(1) @@ -149,6 +159,16 @@ class Time since((quarters * 3).months) end + # Returns a new Time representing the start of the unit specified (defaults to second). + def beginning_of(unit = :sec) + send("beginning_of_#{unit}") + end + + # Returns a new Time representing the end of the unit specified (defaults to second). + def end_of(unit = :sec) + send("end_of_#{unit}") + end + # Returns a new Time representing the start of the second, XX:XX:XX.000000 (.000000000 in ruby1.9). def beginning_of_second change(:usec => 0) diff --git a/lib/time_ext/core_ext/time/iterations.rb b/lib/time_ext/core_ext/time/iterations.rb new file mode 100644 index 0000000..a62059f --- /dev/null +++ b/lib/time_ext/core_ext/time/iterations.rb @@ -0,0 +1,71 @@ +class Time + include TimeExt::MethodChain + + # Used by #each, #map_each and similar methods to iterate over ranges of time. + def iterate(unit, options = {}, &block) + options.reverse_merge!(:map_result => false, :beginning_of => false, :include_start => false) + if block_given? + units = [:year, :month, :day, :hour, :min, :sec, :usec] + parent_unit = units[units.index(unit)-1] + @until ||= (!parent_unit.nil?) ? self.send("#{parent_unit}s_since", 1) : self.send("#{unit}s_since", 1) + time = self.clone + direction = (self < @until) ? :f : :b + succ_method = (direction == :f) ? "next_#{unit}" : "prev_#{unit}" + time = time.beginning_of(unit) if options[:beginning_of] + time = time.send(succ_method) if !options[:include_start] + results = [] + while (direction == :f && time <= @until) || (direction == :b && time >= @until) + options[:map_result] ? results << yield(time) : yield(time) + time = time.send(succ_method) + end + options[:map_result] ? results : self + else + add_to_chain(:iterate, unit, options) + self + end + end + + # Executes passed block for each "unit" of time specified, with a new time object for each interval passed to the block. + def each(unit, options = {}, &block) + iterate(unit, options.merge(:map_result => false), &block) + end + + # Executes passed block for each "unit" of time specified, with a new time object set to the beginning of "unit" for each interval passed to the block. + def beginning_of_each(unit, options = {}, &block) + iterate(unit, options.merge(:map_result => false, :beginning_of => true), &block) + end + + # Executes passed block for each "unit" of time specified, returning an array with the return values from the passed block. + def map_each(unit, options = {}, &block) + iterate(unit, options.merge(:map_result => true), &block) + end + + # Executes passed block for each "unit" of time specified, returning an array with the return values from passed block. Additionally the time object passed into the block is set to the beginning of specified "unit". + def map_beginning_of_each(unit, options = {}, &block) + iterate(unit, options.merge(:map_result => true, :beginning_of => true), &block) + end + + # Used togeter with #each to specify end of interation. + def until(time, &block) + time = time.to_time if time.is_a?(::Date) + @until = time + if block_given? + call_chain(block) + else + add_to_chain(:until, time) + self + end + end + alias :till :until + + # Dynamically define convenience methods, like #each_hour instead of #each(:hour). + [:year, :month, :day, :hour, :min, :minute, :sec, :second].each do |unit| + [:each, :beginning_of_each, :map_each, :map_beginning_of_each].each do |method| + called_unit = (unit == :minute) ? :min : (unit == :second) ? :sec : unit + define_method "#{method}_#{unit}" do |*args, &block| + send(method, called_unit, *args, &block) + end + end + end + +end \ No newline at end of file diff --git a/lib/time_ext/method_chain.rb b/lib/time_ext/method_chain.rb new file mode 100644 index 0000000..2a27428 --- /dev/null +++ b/lib/time_ext/method_chain.rb @@ -0,0 +1,18 @@ +module TimeExt + module MethodChain + + def add_to_chain(method, *args, &block) + @method_chain ||= [] + @method_chain << [method.to_sym, args, block] + end + + def call_chain(custom_block = nil, &block) + method, args, iblock = @method_chain.pop + return nil if method.nil? + iblock = custom_block if !custom_block.nil? + method, args, iblock = yield(method, args, iblock) if block_given? + self.send(method, *args, &iblock) + end + + end +end \ No newline at end of file diff --git a/spec/time_ext_spec.rb b/spec/time_calculations_spec.rb similarity index 99% rename from spec/time_ext_spec.rb rename to spec/time_calculations_spec.rb index f9af42f..b45c14f 100644 --- a/spec/time_ext_spec.rb +++ b/spec/time_calculations_spec.rb @@ -1,6 +1,6 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper') -describe "TimeExt" do +describe "Time Calculations" do before(:each) do @time = Time.local(2010, 8, 28, 15, 57, 17, 78430) diff --git a/spec/time_iterations_spec.rb b/spec/time_iterations_spec.rb new file mode 100644 index 0000000..0aa85f4 --- /dev/null +++ b/spec/time_iterations_spec.rb @@ -0,0 +1,63 @@ +require File.expand_path(File.dirname(__FILE__) + '/spec_helper') + +describe "Time Iterations" do + + before(:each) do + @now = Time.now + end + + it "should iterate over time objects with #each" do + times = [] + result = @now.each(:hour) { |time| times << time } + times.should have(24).items + times.should == (1..24).map { |i| @now + i.hours } + result.should == @now + end + + it "should iterate over time objects with #beginning_of_each" do + times = [] + result = @now.beginning_of_each_hour { |time| times << time } + times.should have(24).items + times.should == (1..24).map { |i| @now.beginning_of_hour + i.hours } + result.should == @now + end + + it "should iterate over time objects with #map_each" do + result = @now.map_each_hour { |time| time } + result.should have(24).items + result.should == (1..24).map { |i| @now + i.hours } + end + + it "should iterate over time objects with #map_beginning_of_each" do + result = @now.map_beginning_of_each(:hour) { |time| time } + result.should have(24).items + result.should == (1..24).map { |i| @now.beginning_of_hour + i.hours } + end + + it "should iterate over time objects with #each and #until via method chaining" do + match = (1..6).map { |i| @now + i.hours } + + times = [] + result = @now.each(:hour).until(@now + 6.hours) { |time| times << time } + times.should == match + result.should == @now + + times = [] + result = @now.until(@now + 6.hours).each_hour { |time| times << time } + times.should == match + result.should == @now + end + + it "should iterate over time objects with #map_each and #until via method chaining" do + match = (1..6).map { |i| @now + i.hours } + @now.map_each_hour.until(@now + 6.hours) { |time| time }.should == match + @now.until(@now + 6.hours).map_each(:hour) { |time| time }.should == match + end + + it "should iterate over time objects backwards with #until set in the past" do + match = (1..6).map { |i| @now - i.hours } + @now.map_each_hour.until(@now - 6.hours) { |time| time }.should == match + @now.until(@now - 6.hours).map_each(:hour) { |time| time }.should == match + end + +end \ No newline at end of file