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