lib/ruby-units.rb in ruby-units-0.2.0 vs lib/ruby-units.rb in ruby-units-0.2.1

- old
+ new

@@ -1,9 +1,10 @@ require 'mathn' require 'rational' - -# = Ruby Units 0.2.0 +require 'date' +require 'parsedate' +# = Ruby Units 0.2.1 # # Copyright 2006 by Kevin C. Olbrich, Ph.D. # # See http://rubyforge.org/ruby-units/ # @@ -183,14 +184,20 @@ inches = self.to("in").scalar "#{(inches / 12).truncate}\'#{(inches % 12).round}\"" when :lbs: ounces = self.to("oz").scalar "#{(ounces / 16).truncate} lbs, #{(ounces % 16).round} oz" + when String + begin #first try a standard format string + target_units =~ /(%[\w\d#+-.]*)*\s*(.+)*/ + return self.to($2).to_s($1) if $2 + "#{($1 || '%g') % @scalar || 0} #{self.units}".strip + rescue #if that is malformed, try a time string + return (Time.gm(0) + self).strftime(target_units) + end else - target_units =~ /(%[\w\d#+-.]*)*\s*(.+)*/ - return self.to($2).to_s($1) if $2 - "#{($1 || '%g') % @scalar || 0} #{self.units}".strip + "#{'%g' % @scalar} #{self.units}".strip end end def inspect(option=nil) return super() if option == :dump @@ -258,13 +265,15 @@ q = @scalar + other.to(self).scalar Unit.new(:scalar=>q, :numerator=>@numerator, :denominator=>@denominator) else raise ArgumentError, "Incompatible Units" end + elsif Time === other + other + self else x,y = coerce(other) - x + y + y + x end end # Subtract two units. Result is same units as receiver and scalar and base_scalar are updated appropriately # throws an exception if the units are not compatible. @@ -274,10 +283,12 @@ q = @scalar - other.to(self).scalar Unit.new(:scalar=>q, :numerator=>@numerator, :denominator=>@denominator) else raise ArgumentError, "Incompatible Units" end + elsif Time === other + other + self else x,y = coerce(other) x - y end end @@ -358,11 +369,11 @@ vec = self.unit_signature_vector vec=vec.map {|x| x % n} raise ArgumentError, "Illegal root" unless vec.max == 0 num = @numerator.clone den = @denominator.clone - + @numerator.uniq.each do |item| x = num.find_all {|i| i==item}.size r = ((x/n)*(n-1)).to_int r.times {|x| num.delete_at(num.index(item))} end @@ -370,12 +381,12 @@ @denominator.uniq.each do |item| x = den.find_all {|i| i==item}.size r = ((x/n)*(n-1)).to_int r.times {|x| den.delete_at(den.index(item))} end - q = @scalar**(1/n) - Unit.new([q,num,den]) + q = @scalar**(1/n) + Unit.new(:scalar=>q,:numerator=>num,:denominator=>den) end # returns inverse of Unit (1/unit) def inverse Unit("1") / self @@ -400,12 +411,12 @@ return self if other.nil? return self if TrueClass === other return self if FalseClass === other if String === other && other =~ /temp(K|C|R|F)/ raise ArgumentError, "Receiver is not a temperature unit" unless self.signature==400 - #return self.to_base.to(other) unless self.is_base? - return self.to_base.to("tempF") if @numerator.size > 1 || @denominator != ['<1>'] + return self.to_base.to(other) unless self.is_base? + #return self.to_base.to("tempF") if @numerator.size > 1 || @denominator != ['<1>'] q=case when @numerator.include?('<celcius>'): case other when 'tempC' : @scalar when 'tempK' : @scalar + 273.15 @@ -533,11 +544,11 @@ num = ["<1>"] if num.empty? den = ["<1>"] if den.empty? {:scalar=>q, :numerator=>num.flatten.compact, :denominator=>den.flatten.compact} end - # returns the scalar part of the Unit + # converts the unit back to a float if it is unitless def to_f return @scalar.to_f if self.unitless? raise RuntimeError, "Can't convert to float unless unitless. Use Unit#scalar" end @@ -600,10 +611,11 @@ def to_int return @scalar.to_int if unitless? Unit.new([@scalar.to_int, @numerator, @denominator]) end + # Tries to make a Time object from current unit def to_time Time.at(self) end alias :time :to_time alias :to_i :to_int @@ -615,10 +627,63 @@ # true if scalar is zero def zero? return @scalar.zero? end + + # '5 min'.unit.ago + def ago + Time.now - self + end + + # '5 min'.before(time) + def before(time_point = ::Time.now) + raise ArgumentError, "Must specify a Time" unless time_point + if String === time_point + Time.local(*ParseDate.parsedate(time_point))-self + else + time_point - self + end + end + + # 'min'.since(time) + def since(time_point = ::Time.now) + case time_point + when Time: (Time.now - time_point).unit('s').to(self) + when DateTime: (DateTime.now - time_point).unit('d').to(self) + when String: (Time.now - Time.local(*ParseDate.parsedate(time_point))).unit('s').to(self) + else + raise ArgumentError, "Must specify a Time" unless time_point + end + end + + # 'min'.until(time) + def until(time_point = ::Time.now) + case time_point + when Time: (time_point - Time.now).unit('s').to(self) + when DateTime: (time_point - DateTime.now).unit('d').to(self) + when String: (Time.local(*ParseDate.parsedate(time_point)) - Time.now).unit('s').to(self) + else + raise ArgumentError, "Must specify a Time" unless time_point + end + end + + # '5 min'.from_now + def from_now + self.from + end + + # '5 min'.from(time) + def from(time_point = ::Time.now) + raise ArgumentError, "Must specify a Time" unless time_point + if String === time_point + Time.local(*ParseDate.parsedate(time_point))+self + else + time_point + self + end + end + alias :after :from def succ raise ArgumentError, "Non Integer Scalar" unless @scalar == @scalar.to_i q = @scalar.to_i.succ Unit.new([q, @numerator, @denominator]) @@ -703,75 +768,138 @@ end else @scalar = @scalar.to_f end - @numerator = top.scan(/((#{@@PREFIX_REGEX})*(#{@@UNIT_REGEX}))/).delete_if {|x| x.empty?}.compact if top - @denominator = bottom.scan(/((#{@@PREFIX_REGEX})*(#{@@UNIT_REGEX}))/).delete_if {|x| x.empty?}.compact if bottom + @numerator = top.scan(/(#{@@PREFIX_REGEX})*?(#{@@UNIT_REGEX})\b/).delete_if {|x| x.empty?}.compact if top + @denominator = bottom.scan(/(#{@@PREFIX_REGEX})*?(#{@@UNIT_REGEX})\b/).delete_if {|x| x.empty?}.compact if bottom @numerator = @numerator.map do |item| item.map {|x| Regexp.escape(x) if x} - @@UNIT_MAP[item[0]] ? [@@UNIT_MAP[item[0]]] : [@@PREFIX_MAP[item[1]], @@UNIT_MAP[item[2]]] + @@PREFIX_MAP[item[0]] ? [@@PREFIX_MAP[item[0]], @@UNIT_MAP[item[1]]] : [@@UNIT_MAP[item[1]]] end.flatten.compact.delete_if {|x| x.empty?} @denominator = @denominator.map do |item| item.map {|x| Regexp.escape(x) if x} - @@UNIT_MAP[item[0]] ? [@@UNIT_MAP[item[0]]] : [@@PREFIX_MAP[item[1]], @@UNIT_MAP[item[2]]] + @@PREFIX_MAP[item[0]] ? [@@PREFIX_MAP[item[0]], @@UNIT_MAP[item[1]]] : [@@UNIT_MAP[item[1]]] end.flatten.compact.delete_if {|x| x.empty?} @numerator = ['<1>'] if @numerator.empty? @denominator = ['<1>'] if @denominator.empty? self end end +# Need the 'Uncertain' gem for this to do anything helpful if defined? Uncertain class Uncertain def to_unit(other=nil) other ? Unit.new(self).to(other) : Unit.new(self) end + alias :unit :to_unit + alias :u :to_unit end end +# Allow date objects to do offsets by a time unit +# Date.today + U"1 week" => gives today+1 week +class Date + alias :unit_date_add :+ + def +(unit) + if Unit === unit + unit_date_add(unit.to('day').scalar) + else + unit_date_add(unit) + end + end + + alias :unit_date_sub :- + def -(unit) + if Unit === unit + unit_date_sub(unit.to('day').scalar) + else + unit_date_sub(unit) + end + end +end + class Object def Unit(other) other.to_unit end + alias :U :Unit + alias :u :Unit end +# make a unitless unit with a given scalar class Numeric def to_unit(other = nil) other ? Unit.new(self) * Unit.new(other) : Unit.new(self) end alias :unit :to_unit + alias :u :to_unit end - +# make a unit from an array +# [1, 'mm'].unit => 1 mm class Array def to_unit(other = nil) other ? Unit.new(self).to(other) : Unit.new(self) end alias :unit :to_unit + alias :u :to_unit end +# make a string into a unit class String def to_unit(other = nil) other ? Unit.new(self) >> other : Unit.new(self) end alias :unit :to_unit + alias :u :to_unit alias :unit_format :% + # format unit output using formating codes '%0.2f' % '1 mm'.unit => '1.00 mm' def %(*args) if Unit === args[0] args[0].to_s(self) else unit_format(*args) end end + + def from(time_point = ::Time.now) + self.unit.from(time_point) + end + alias :after :from + alias :from_now :from + + def ago + self.unit.ago + end + + def before(time_point = ::Time.now) + self.unit.before(time_point) + end + alias :before_now :before + + def since(time_point = ::Time.now) + self.unit.since(time_point) + end + + def until(time_point = ::Time.now) + self.unit.until(time_point) + end + + def to(other) + self.unit.to(other) + end end + +# Allow time objects to use class Time class << self alias unit_time_at at end @@ -783,27 +911,96 @@ unit_time_at(*args) end end def to_unit(other = "s") - other ? Unit.new(self.to_f) * Unit.new(other) : Unit.new(self.to_f) + other ? Unit.new("#{self.to_f} s").to(other) : Unit.new("#{self.to_f} s") end alias :unit :to_unit - + alias :u :to_unit alias :unit_add :+ def +(other) if Unit === other - self.unit + other + unit_add(other.to('s').scalar) else unit_add(other) end end alias :unit_sub :- def -(other) if Unit === other - self.unit - other + unit_sub(other.to('s').scalar) else unit_sub(other) end end +end + +module Math + alias unit_sin sin + def sin(n) + if Unit === n + unit_sin(n.to('radian').scalar) + else + unit_sin(n) + end + end + + alias unit_cos cos + def cos(n) + if Unit === n + unit_cos(n.to('radian').scalar) + else + unit_cos(n) + end + end + + alias unit_sinh sinh + def sinh(n) + if Unit === n + unit_sinh(n.to('radian').scalar) + else + unit_sinh(n) + end + end + + alias unit_cosh cosh + def cosh(n) + if Unit === n + unit_cosh(n.to('radian').scalar) + else + unit_cosh(n) + end + end + + alias unit_tan tan + def tan(n) + if Unit === n + unit_tan(n.to('radian').scalar) + else + unit_tan(n) + end + end + + alias unit_tanh tanh + def tanh(n) + if Unit === n + unit_tanh(n.to('radian').scalar) + else + unit_tanh(n) + end + end + + module_function :unit_sin + module_function :sin + module_function :unit_cos + module_function :cos + module_function :unit_sinh + module_function :sinh + module_function :unit_cosh + module_function :cosh + module_function :unit_tan + module_function :tan + module_function :unit_tanh + module_function :tanh end \ No newline at end of file