Interation methods #each, #map_each and more are

implemented and working. Also restructured files
again.
This commit is contained in:
2010-07-29 04:25:42 +03:00
parent d6b65dbc01
commit 438cdd5b68
7 changed files with 184 additions and 58 deletions

View File

@@ -1,3 +1,13 @@
require 'rubygems' require 'rubygems'
require 'active_support' require 'active_support'
require 'time_ext/time' 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

View File

@@ -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

View File

@@ -32,6 +32,16 @@ class Time
end end
alias :beginning_of_closest :round 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). # Short-hand for seconds_ago(1).
def prev_second def prev_second
ago(1) ago(1)
@@ -149,6 +159,16 @@ class Time
since((quarters * 3).months) since((quarters * 3).months)
end 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). # Returns a new Time representing the start of the second, XX:XX:XX.000000 (.000000000 in ruby1.9).
def beginning_of_second def beginning_of_second
change(:usec => 0) change(:usec => 0)

View File

@@ -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

View File

@@ -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

View File

@@ -1,6 +1,6 @@
require File.expand_path(File.dirname(__FILE__) + '/spec_helper') require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
describe "TimeExt" do describe "Time Calculations" do
before(:each) do before(:each) do
@time = Time.local(2010, 8, 28, 15, 57, 17, 78430) @time = Time.local(2010, 8, 28, 15, 57, 17, 78430)

View File

@@ -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