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