lib/timespan.rb in timespan-0.1.4 vs lib/timespan.rb in timespan-0.2.0
- old
+ new
@@ -1,217 +1,211 @@
require 'duration'
+require 'chronic'
+require 'chronic_duration'
require 'spanner'
-class TimeSpan
- class ParseError < StandardError; end
+require 'timespan/units'
+require 'timespan/compare'
+require 'timespan/printer'
+require 'timespan/span'
- attr_reader :start_time, :end_time, :seconds
+if defined?(Rails) && Rails::VERSION::STRING.to >= '3.1'
+ require 'duration/rails/engine'
+end
+class Timespan
+ include Span
+ include Printer
+ include Compare
+ include Units
+
+ class TimeParseError < StandardError; end
+
+ attr_reader :start_time, :end_time
+
alias_method :start_date, :start_time
alias_method :end_date, :end_time
+ START_KEYS = [:start, :from]
+ END_KEYS = [:to, :end]
+ DURATION_KEYS = [:duration, :lasting]
+
+ ALL_KEYS = START_KEYS + END_KEYS + DURATION_KEYS
+
def initialize options = {}
@is_new = true
- set_with_options options
+ case options
+ when Numeric, Duration, String
+ options = {:duration => options}
+ end
+ configure options
+
@is_new = false
end
- def start_time= start_time
- @start_time = start_time
- refresh!
+ def start_time= time
+ @start_time = convert_to_time time
+ unless is_new?
+ refresh!
+ add_dirty :start
+ calculate!
+ end
end
alias_method :start_date=, :start_time=
+
+ def from time
+ self.start_time = time
+ self
+ end
- def end_time= start_time
- @start_time = start_time
- refresh!
+ def end_time= time
+ @end_time = convert_to_time time
+ unless is_new?
+ add_dirty :end
+ refresh!
+ calculate!
+ end
end
alias_method :end_date=, :end_time=
- def seconds= seconds
- @seconds = seconds
- refresh!
+ def until time
+ self.end_time = time
+ self
end
- def duration
- @duration ||= Duration.new(seconds)
- end
-
- def duration= duration
- @duration = case duration
- when Duration
- duration
- when Integer, Hash
- Duration.new duration
+ def convert_to_time time
+ case time
when String
- begin
- Duration.new Spanner.parse(duration.gsub /and/, '')
- rescue Exception => e
- raise ParseError, "Internal error: Spanner couldn't parse String '#{duration}'"
- end
+ Chronic.parse(time)
+ when Date, DateTime
+ time.to_time
+ when Time
+ time
else
- raise ArgumentError, "the duration option must be set to any of: #{valid_duration_types}"
- end
- refresh! unless is_new?
- end
-
- def to_s
- if duration
- "TimeSpan: from #{start_time} lasting #{duration} = #{seconds} secs" if start_time
- "TimeSpan: from #{end_time} to #{duration} before = #{seconds} secs" if end_time
- return
+ raise ArgumentError, "A valid time must be either a String, Date, Time or DateTime, was: #{time.inspect}"
end
+ end
- if start_time && end_time
- "TimeSpan: #{start_time} to #{end_time} = #{seconds} secs"
- return
- end
+ protected
- if seconds
- "TimeSpan: #{seconds} seconds"
- end
+ def first_from keys, options = {}
+ keys.select {|key| options[key] }.first
end
- include Comparable
+ def configure options = {}
+ from = options[first_from START_KEYS, options]
+ to = options[first_from END_KEYS, options]
+ dur = options[first_from DURATION_KEYS, options]
- def <=> time
- raise ArgumentError, "Not a valid argument for timespan comparison, was #{time}" unless valid_compare?(time)
- case time
- when TimeSpan
- millis <=> time.seconds
- when Time
- millis <=> time.to_i
- when Date, DateTime
- time.to_time.to_i
- when Integer
- millis <=> time
- end
- end
+ self.duration = dur if dur
+ self.start_time = from if from
+ self.end_time = to if to
- alias_method :to_secs, :seconds
- alias_method :to_seconds, :seconds
+ default_from_now! unless start_time || end_time
- def to_milliseconds
- @to_seconds ||= (seconds * 1000.0).round
+ calculate_miss!
+ rescue Exception => e
+ calculate_miss!
+ validate!
+ end
+
+ def default_from_now!
+ self.start_time = Time.now
end
- alias_method :to_mils, :to_milliseconds
- alias_method :millis, :to_mils
- alias_method :milliseconds, :to_mils
- def to_minutes
- @to_minutes ||= (to_seconds / 60.0).round
+ def validate!
+ raise ArgumentError, "#{valid_requirement}, was: #{current_config}" unless valid?
end
- alias_method :to_m, :to_minutes
- alias_method :to_mins, :to_minutes
- alias_method :minutes, :to_minutes
- def to_hours
- @to_hours ||= (to_minutes / 60.0).round
+ def valid_requirement
+ "Timespan must take a :start and :end time or any of :start and :end time and a :duration"
end
- alias_method :to_h, :to_hours
- alias_method :hrs, :to_hours
- alias_method :hours, :to_hours
- def to_days
- @to_days ||= (to_hours / 24.0).round
- end
- alias_method :to_d, :to_days
- alias_method :days, :to_days
+ def current_config
+ "end time: #{end_time}, start time: #{start_time}, duration: #{duration}"
+ end
- def to_weeks
- @to_weeks ||= (to_days / 7.0).round
- end
- alias_method :to_w, :to_weeks
- alias_method :weeks, :to_days
+ def valid?
+ (end_time && start_time) || (end_time || start_time && duration)
+ end
- def to_months
- @to_months ||= (to_days / 30.0).round
- end
- alias_method :to_mon, :to_months
- alias_method :months, :to_months
+ def is_new?
+ @is_new
+ end
- def to_years
- @to_years ||= (to_days.to_f / 365.25).round
- end
- alias_method :to_y, :to_years
- alias_method :years, :to_years
+ def dirty
+ @dirty ||= []
+ end
- def to_decades
- @to_decades ||= (to_years / 10.0).round
- end
- alias_method :decades, :to_decades
+ def add_dirty type
+ reset_dirty if dirty.size > 2
+ dirty << type
+ end
- def to_centuries
- @to_centuries ||= (to_decades / 10.0).round
- end
- alias_method :centuries, :to_centuries
+ def reset_dirty
+ @dirty = []
+ end
- def units
- %w{seconds minutes hours days weeks months years}
+ def dirty? type
+ dirty.include? type
end
- protected
+ def calculate!
+ set_duration unless dirty? :duration
+ set_start_time unless dirty? :start
+ set_end_time unless dirty? :end
+ end
- def set_with_options options = {}
- case options
- when Hash
- duration = options[:duration]
- @start_time = options[:from] || options[:start]
- @end_time = options[:to] || options[:end]
- when Integer, String
- duration = options
- else
- raise ArgumentError, "Timespan must take Hash or Integer, was: #{options}"
- end
+ def calculate_miss!
+ set_end_time_miss
+ set_start_time_miss
+ set_duration_miss
+ set_end_time_miss
+ set_start_time_miss
+ end
- set_seconds options
- end
+ def set_end_time_miss
+ set_end_time if missing_end_time?
+ end
- def set_seconds options = nil
- set_seconds_opts(options)
+ def set_end_time
+ self.end_time = start_time - duration.total
+ end
- unless @duration
- if @end_time && @start_time
- @seconds ||= (@end_time - @start_time).to_i
- end
- else
- @seconds ||= @duration.total
- end
+ def set_start_time_miss
+ set_start_time if missing_start_time?
end
- def set_seconds_opts options = {}
- case options
- when Integer
- @seconds = options
- when Hash
- @seconds = options[:seconds] if options[:seconds]
- self.duration = options[:duration] if options[:duration]
- end
+ def set_start_time
+ self.start_time = end_time - duration.total
end
- def is_new?
- @is_new
+ def set_duration_miss
+ set_duration if missing_duration?
end
- # reset all stored instance vars for units
- def refresh!
- units.each do |unit|
- var_name = :"@#{unit}"
- instance_variable_set var_name, nil
- end
- set_seconds
+ def set_duration
+ self.duration = end_time - start_time
end
- def valid_duration_types
- [Duration, String, Integer, Hash]
+ def missing_end_time?
+ start_time && duration && !end_time
end
- def valid_compare? time
- valid_compare_types.any? {|type| time.kind_of? type }
+ def missing_start_time?
+ end_time && duration && !start_time
end
- def valid_compare_types
- [TimeSpan, Time, Date, DateTime, Integer]
+ def missing_duration?
+ start_time && end_time && !duration
+ end
+
+ # reset all stored instance vars for units
+ def refresh!
+ units.each do |unit|
+ var_name = :"@#{unit}"
+ instance_variable_set var_name, nil
+ end
end
end
\ No newline at end of file