lib/timerange.rb in timerange-0.0.1 vs lib/timerange.rb in timerange-0.0.2

- old
+ new

@@ -1,36 +1,92 @@ +require "time" require "active_support/time" +require "active_support/core_ext/module/attribute_accessors" class TimeRange < Range - VERSION = "0.0.1" + VERSION = "0.0.2" - def initialize(options = {}) - range = options[:range] - super(range.begin, range.end, range.exclude_end?) + mattr_accessor :time_zone + + def initialize(b = nil, e = Time.now, exclude_end = false, options = {}) + if b.is_a?(Range) + b, e, exclude_end = b.begin, b.end, b.exclude_end? + end + + if b.is_a?(Hash) + options, b, e, exclude_end = b, nil, nil, false + elsif e.is_a?(Hash) + options, e, exclude_end = e, nil, false + end + + time_zone = options[:time_zone] || self.class.time_zone || Time.zone || "Etc/UTC" + if time_zone.is_a?(ActiveSupport::TimeZone) or (time_zone = ActiveSupport::TimeZone[time_zone]) + # do nothing + else + raise "Unrecognized time zone" + end + b = time_zone.parse(b) if b.is_a?(String) + e = time_zone.parse(e) if e.is_a?(String) + if options[:time_zone] + b = b.in_time_zone(b) + e = e.in_time_zone(e) + end + + if options[:duration] + e = b + options[:duration] + exclude_end = true + end + + super(b, e, exclude_end) end - def step(period, options = {}) - arr = [bucket(period, self.begin, options)] - while v = arr.last + 1.send(period) and cover?(v) + # should step expand by default? + # TODO return enum + def step(period, options = {}, &block) + period = period.is_a?(Symbol) || period.is_a?(String) ? 1.send(period) : period + arr = [self.begin] + yield(arr.last) if block_given? + while v = arr.last + period and cover?(v) + yield(v) if block_given? arr << v end arr end def expand(period, options = {}) - self.class.new(range: Range.new(bucket(period, self.begin, options), bucket(period, self.end + 1.send(period), options), true)) + e = + if exclude_end? and self.end == bucket(period, self.end, options) + self.end + else + bucket(period, self.end + 1.send(period), options) + end + self.class.new(bucket(period, self.begin, options), e, true) end - def time_zone - Time.zone + def expand_start(period, options = {}) + e = self.end + e = e.in_time_zone(options[:time_zone]) if options[:time_zone] + self.class.new(bucket(period, self.begin, options), e, exclude_end?) end def bucket(period, time, options = {}) - day_start = 0 - week_start = 6 # sunday + self.class.bucket(period, time, options) + end + + def self.bucket(period, time, options = {}) + time_zone = options[:time_zone] || Time.zone + day_start = options[:day_start] || 0 + week_start = options[:week_start] || 6 + + week_start = [:mon, :tue, :wed, :thu, :fri, :sat, :sun].index((options[:week_start] || :sun).to_sym) + if !week_start + raise "Unrecognized :week_start option" + end + time = time.to_time.in_time_zone(time_zone) - day_start.hours + period = period.to_sym time = case period when :second time.change(usec: 0) when :minute @@ -43,23 +99,33 @@ # same logic as MySQL group weekday = (time.wday - 1) % 7 (time - ((7 - week_start + weekday) % 7).days).midnight when :month time.beginning_of_month - else # year + when :year time.beginning_of_year + else + raise "Invalid period" end time + day_start.hours end def self.today date = Date.today - new(range: date..date).expand(:day) + new(date, date).expand(:day) end def self.yesterday date = Date.yesterday - new(range: date..date).expand(:day) + new(date, date).expand(:day) + end + + def +(period) + self.class.new(self.begin + period, self.end + period, exclude_end?) + end + + def -(period) + self.class.new(self.begin - period, self.end - period, exclude_end?) end end