module Periodic module Parser def parse(string, options = { :bias => :seconds}) return Parseable.new(string, options[:bias]).seconds end private class Parseable def initialize(string, bias) @string = string validates_inclusion_of_numeral_in_string @bias = bias extract_time_parts_from_string end def seconds units = { :seconds => 1, :minutes => 60, :hours => 3600, :days => 3600*24, :weeks => 3600*24*7, :months => 3600*24*30, :years => 3600*24*365.25, :decades => 3600*24*365.25*10, :centuries => 3600*24*365.25*100, :millennia => 3600*24*365.25*1000 } seconds = @time_parts.inject(0) { |total, part| total = total + (part[1] * units[part[0]]) } return seconds % 1 == 0 ? seconds.to_i : seconds end private def validates_inclusion_of_numeral_in_string raise ArgumentError, "String contains no numbers", caller unless @string.match(/\d/) end def digital? @string.match(/:/) end def extract_time_parts_from_string @time_parts = Hash.new digital? ? extract_time_parts_from_digital : extract_time_parts_from_text end def extract_time_parts_from_digital units = [:seconds, :minutes, :hours, :days, :weeks, :months, :years, :decades, :centuries, :millennia] @string.split(":").reverse.each_with_index do |part, i| @time_parts[units[i + ((units.index(@bias) >= @string.split(":").size) ? units.index(@bias) - @string.split(":").size + 1 : 0)]] = part.to_f end end def extract_time_parts_from_text normalize_string units = { :s => :seconds, :m => :minutes, :h => :hours, :d => :days, :w => :weeks, :n => :months, :y => :years, :a => :decades, :c => :centuries, :b => :millennia } @string.split(' ').each { |part| @time_parts[part.match(/([a-z])/) ? units[part.match(/([a-z])/)[1].to_sym] : @bias] = (@time_parts[part.match(/([a-z])/) ? units[part.match(/([a-z])/)[1].to_sym] : @bias]||0) + part.to_f } end def normalize_string [/( )/, /(,)/, /(and)/].each{ |m| @string.gsub!(m, '') } @string.gsub!(/(\d)([a-zA-Z]+)/, '\1\2 ') [{:n=>/(mo\w*)/,:b=>/(m\w*l\w*)/,:a=>/(d\w*c\w*)/}, {:m=>/(m\w*)/,:h=>/(h\w*)/,:d=>/(d\w*)/,:w=>/(w\w*)/,:y=>/(y\w*)/,:c=>/(c\w*)/}, {:s=>/(s\w*)/}].each { |set| set.each{ |k,v| @string.gsub!(v, k.to_s) } } end end end end