ruby-units 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -91,6 +91,13 @@ Change Log for Ruby-units
91
91
  * Can pass a strftime format string to to_s to format time
92
92
  output
93
93
  * can use U'1 mm' or '1 mm'.u to specify units now
94
- *
94
+
95
+ 2006-09-19 0.2.2 * tweaked temperature handling a bit. Now enter
96
+ temperatures like this:
97
+ '0 tempC'.unit #=> 273.15 degK
98
+ They will always be converted to kelvin to avoid
99
+ problems when temperatures are used in equations.
100
+ * added Time.in("5 min")
101
+ * added Unit.to_unit to simplify some calls
95
102
 
96
103
 
data/lib/ruby-units.rb CHANGED
@@ -2,7 +2,7 @@ require 'mathn'
2
2
  require 'rational'
3
3
  require 'date'
4
4
  require 'parsedate'
5
- # = Ruby Units 0.2.1
5
+ # = Ruby Units 0.2.2
6
6
  #
7
7
  # Copyright 2006 by Kevin C. Olbrich, Ph.D.
8
8
  #
@@ -73,7 +73,6 @@ class Unit < Numeric
73
73
  include Comparable
74
74
  attr_accessor :scalar, :numerator, :denominator, :signature, :base_scalar
75
75
 
76
-
77
76
  def to_yaml_properties
78
77
  %w{@scalar @numerator @denominator @signature @base_scalar}
79
78
  end
@@ -122,14 +121,15 @@ class Unit < Numeric
122
121
  raise ArgumentError, "Invalid Unit Format"
123
122
  end
124
123
  self.update_base_scalar
124
+ self.replace_temperature
125
125
  self.freeze
126
126
  end
127
127
 
128
- def initialize_copy(other)
129
- @numerator = other.numerator.clone
130
- @denominator = other.denominator.clone
128
+ def to_unit
129
+ self
131
130
  end
132
-
131
+ alias :unit :to_unit
132
+
133
133
  # Returns 'true' if the Unit is represented in base units
134
134
  def is_base?
135
135
  return true if @signature == 400 && @numerator.size == 1 && @numerator[0] =~ /(celcius|kelvin|farenheit|rankine)/
@@ -143,6 +143,7 @@ class Unit < Numeric
143
143
  #convert to base SI units
144
144
  def to_base
145
145
  return self if self.is_base?
146
+ # return self.to('degK') if self.units =~ /temp(C|K|F|R)/
146
147
  num = []
147
148
  den = []
148
149
  q = @scalar
@@ -262,8 +263,9 @@ class Unit < Numeric
262
263
  def +(other)
263
264
  if Unit === other
264
265
  if self =~ other then
265
- q = @scalar + other.to(self).scalar
266
- Unit.new(:scalar=>q, :numerator=>@numerator, :denominator=>@denominator)
266
+ a = self.to_base
267
+ b = other.to_base
268
+ Unit.new(:scalar=>(a.scalar + b.scalar), :numerator=>a.numerator, :denominator=>b.denominator).to(self)
267
269
  else
268
270
  raise ArgumentError, "Incompatible Units"
269
271
  end
@@ -280,13 +282,14 @@ class Unit < Numeric
280
282
  def -(other)
281
283
  if Unit === other
282
284
  if self =~ other then
283
- q = @scalar - other.to(self).scalar
284
- Unit.new(:scalar=>q, :numerator=>@numerator, :denominator=>@denominator)
285
+ a = self.to_base
286
+ b = other.to_base
287
+ Unit.new(:scalar=>(a.scalar - b.scalar), :numerator=>a.numerator, :denominator=>b.denominator).to(self)
285
288
  else
286
289
  raise ArgumentError, "Incompatible Units"
287
290
  end
288
291
  elsif Time === other
289
- other + self
292
+ other - self
290
293
  else
291
294
  x,y = coerce(other)
292
295
  x - y
@@ -411,34 +414,35 @@ class Unit < Numeric
411
414
  return self if other.nil?
412
415
  return self if TrueClass === other
413
416
  return self if FalseClass === other
414
- if String === other && other =~ /temp(K|C|R|F)/
417
+ if (Unit === other && other.units =~ /temp(K|C|R|F)/) || (String === other && other =~ /temp(K|C|R|F)/)
415
418
  raise ArgumentError, "Receiver is not a temperature unit" unless self.signature==400
416
419
  return self.to_base.to(other) unless self.is_base?
417
- #return self.to_base.to("tempF") if @numerator.size > 1 || @denominator != ['<1>']
418
- q=case
419
- when @numerator.include?('<celcius>'):
420
- case other
420
+ start_unit = self.units
421
+ target_unit = other.units rescue other
422
+ q=case start_unit
423
+ when 'degC':
424
+ case target_unit
421
425
  when 'tempC' : @scalar
422
426
  when 'tempK' : @scalar + 273.15
423
427
  when 'tempF' : @scalar * (9.0/5.0) + 32.0
424
428
  when 'tempR' : @scalar * (9.0/5.0) + 491.67
425
429
  end
426
- when @numerator.include?( '<kelvin>'):
427
- case other
430
+ when 'degK':
431
+ case target_unit
428
432
  when 'tempC' : @scalar - 273.15
429
433
  when 'tempK' : @scalar
430
434
  when 'tempF' : @scalar * (9.0/5.0) - 459.67
431
435
  when 'tempR' : @scalar * (9.0/5.0)
432
436
  end
433
- when @numerator.include?( '<farenheit>'):
434
- case other
437
+ when 'degF':
438
+ case target_unit
435
439
  when 'tempC' : (@scalar-32)*(5.0/9.0)
436
440
  when 'tempK' : (@scalar+459.67)*(5.0/9.0)
437
441
  when 'tempF' : @scalar
438
442
  when 'tempR' : @scalar + 459.67
439
443
  end
440
- when @numerator.include?( '<rankine>'):
441
- case other
444
+ when 'degR':
445
+ case target_unit
442
446
  when 'tempC' : @scalar*(5.0/9.0) -273.15
443
447
  when 'tempK' : @scalar*(5.0/9.0)
444
448
  when 'tempF' : @scalar - 459.67
@@ -447,6 +451,7 @@ class Unit < Numeric
447
451
  else
448
452
  raise ArgumentError, "Unknown temperature conversion requested #{self.numerator}"
449
453
  end
454
+ target_unit =~ /temp(C|K|F|R)/
450
455
  Unit.new("#{q} deg#{$1}")
451
456
  else
452
457
  case other
@@ -468,84 +473,11 @@ class Unit < Numeric
468
473
  Unit.new(:scalar=>q, :numerator=>target.numerator, :denominator=>target.denominator)
469
474
  end
470
475
  end
471
-
472
- alias :>> :to
473
- # calculates the unit signature vector used by unit_signature
474
- def unit_signature_vector
475
- return self.to_base.unit_signature_vector unless self.is_base?
476
- result = self
477
- y = [:length, :time, :temperature, :mass, :current, :substance, :luminosity, :currency, :memory, :angle]
478
- vector = Array.new(y.size,0)
479
- y.each_with_index do |units,index|
480
- vector[index] = result.numerator.compact.find_all {|x| @@UNIT_VECTORS[units].include? Regexp.escape(x)}.size
481
- vector[index] -= result.denominator.compact.find_all {|x| @@UNIT_VECTORS[units].include? Regexp.escape(x)}.size
482
- end
483
- vector
484
- end
485
-
486
- # calculates the unit signature id for use in comparing compatible units and simplification
487
- # the signature is based on a simple classification of units and is based on the following publication
488
- #
489
- # Novak, G.S., Jr. "Conversion of units of measurement", IEEE Transactions on Software Engineering,
490
- # 21(8), Aug 1995, pp.651-661
491
- # doi://10.1109/32.403789
492
- # http://ieeexplore.ieee.org/Xplore/login.jsp?url=/iel1/32/9079/00403789.pdf?isnumber=9079&prod=JNL&arnumber=403789&arSt=651&ared=661&arAuthor=Novak%2C+G.S.%2C+Jr.
493
- #
494
- def unit_signature
495
- vector = unit_signature_vector
496
- vector.each_with_index {|item,index| vector[index] = item * 20**index}
497
- @signature=vector.inject(0) {|sum,n| sum+n}
498
- end
499
-
476
+ alias :>> :to
477
+ alias :convert_to :to
478
+
500
479
  # Eliminates terms in the passed numerator and denominator. Expands out prefixes and applies them to the
501
480
  # scalar. Returns a hash that can be used to initialize a new Unit object.
502
- def self.eliminate_terms(q, n, d)
503
- num = n.clone
504
- den = d.clone
505
-
506
- num.delete_if {|v| v == '<1>'}
507
- den.delete_if {|v| v == '<1>'}
508
- combined = Hash.new(0)
509
-
510
- i = 0
511
- loop do
512
- break if i > num.size
513
- if @@PREFIX_VALUES.has_key? num[i]
514
- k = [num[i],num[i+1]]
515
- i += 2
516
- else
517
- k = num[i]
518
- i += 1
519
- end
520
- combined[k] += 1 unless k.nil? || k == '<1>'
521
- end
522
-
523
- j = 0
524
- loop do
525
- break if j > den.size
526
- if @@PREFIX_VALUES.has_key? den[j]
527
- k = [den[j],den[j+1]]
528
- j += 2
529
- else
530
- k = den[j]
531
- j += 1
532
- end
533
- combined[k] -= 1 unless k.nil? || k == '<1>'
534
- end
535
-
536
- num = []
537
- den = []
538
- combined.each do |key,value|
539
- case
540
- when value > 0 : value.times {num << key}
541
- when value < 0 : value.abs.times {den << key}
542
- end
543
- end
544
- num = ["<1>"] if num.empty?
545
- den = ["<1>"] if den.empty?
546
- {:scalar=>q, :numerator=>num.flatten.compact, :denominator=>den.flatten.compact}
547
- end
548
-
549
481
  # converts the unit back to a float if it is unitless
550
482
  def to_f
551
483
  return @scalar.to_f if self.unitless?
@@ -644,6 +576,7 @@ class Unit < Numeric
644
576
  time_point - self
645
577
  end
646
578
  end
579
+ alias :before_now :before
647
580
 
648
581
  # 'min'.since(time)
649
582
  def since(time_point = ::Time.now)
@@ -667,11 +600,6 @@ class Unit < Numeric
667
600
  end
668
601
  end
669
602
 
670
- # '5 min'.from_now
671
- def from_now
672
- self.from
673
- end
674
-
675
603
  # '5 min'.from(time)
676
604
  def from(time_point = ::Time.now)
677
605
  raise ArgumentError, "Must specify a Time" unless time_point
@@ -682,6 +610,7 @@ class Unit < Numeric
682
610
  end
683
611
  end
684
612
  alias :after :from
613
+ alias :from_now :from
685
614
 
686
615
  def succ
687
616
  raise ArgumentError, "Non Integer Scalar" unless @scalar == @scalar.to_i
@@ -689,6 +618,10 @@ class Unit < Numeric
689
618
  Unit.new([q, @numerator, @denominator])
690
619
  end
691
620
 
621
+ # Protected and Private Functions that should only be called from this class
622
+ protected
623
+
624
+
692
625
  def update_base_scalar
693
626
  if self.is_base?
694
627
  @base_scalar = @scalar
@@ -700,6 +633,7 @@ class Unit < Numeric
700
633
  end
701
634
  end
702
635
 
636
+
703
637
  def coerce(other)
704
638
  case other
705
639
  when Unit : [other, self]
@@ -708,8 +642,107 @@ class Unit < Numeric
708
642
  end
709
643
  end
710
644
 
645
+
646
+ # calculates the unit signature vector used by unit_signature
647
+ def unit_signature_vector
648
+ return self.to_base.unit_signature_vector unless self.is_base?
649
+ result = self
650
+ y = [:length, :time, :temperature, :mass, :current, :substance, :luminosity, :currency, :memory, :angle]
651
+ vector = Array.new(y.size,0)
652
+ y.each_with_index do |units,index|
653
+ vector[index] = result.numerator.compact.find_all {|x| @@UNIT_VECTORS[units].include? Regexp.escape(x)}.size
654
+ vector[index] -= result.denominator.compact.find_all {|x| @@UNIT_VECTORS[units].include? Regexp.escape(x)}.size
655
+ end
656
+ vector
657
+ end
658
+
659
+ def replace_temperature
660
+ return self unless self.signature == 400 && self.units =~ /temp(R|K|F|C)/
661
+ un = $1
662
+ puts "self:#{self} #{un}"
663
+ target = self.units
664
+ @numerator = case un
665
+ when 'R' : ['<rankine>']
666
+ when 'C' : ['<celcius>']
667
+ when 'F' : ['<farenheit>']
668
+ when 'K' : ['<kelvin>']
669
+ end
670
+ r= self.to("tempK")
671
+ @numerator = r.numerator
672
+ @denominator = r.denominator
673
+ @scalar = r.scalar
674
+ end
675
+
676
+
711
677
  private
712
678
 
679
+ def initialize_copy(other)
680
+ @numerator = other.numerator.clone
681
+ @denominator = other.denominator.clone
682
+ end
683
+
684
+ # calculates the unit signature id for use in comparing compatible units and simplification
685
+ # the signature is based on a simple classification of units and is based on the following publication
686
+ #
687
+ # Novak, G.S., Jr. "Conversion of units of measurement", IEEE Transactions on Software Engineering,
688
+ # 21(8), Aug 1995, pp.651-661
689
+ # doi://10.1109/32.403789
690
+ # http://ieeexplore.ieee.org/Xplore/login.jsp?url=/iel1/32/9079/00403789.pdf?isnumber=9079&prod=JNL&arnumber=403789&arSt=651&ared=661&arAuthor=Novak%2C+G.S.%2C+Jr.
691
+ #
692
+ def unit_signature
693
+ vector = unit_signature_vector
694
+ vector.each_with_index {|item,index| vector[index] = item * 20**index}
695
+ @signature=vector.inject(0) {|sum,n| sum+n}
696
+ end
697
+
698
+ def self.eliminate_terms(q, n, d)
699
+ num = n.clone
700
+ den = d.clone
701
+
702
+ num.delete_if {|v| v == '<1>'}
703
+ den.delete_if {|v| v == '<1>'}
704
+ combined = Hash.new(0)
705
+
706
+ i = 0
707
+ loop do
708
+ break if i > num.size
709
+ if @@PREFIX_VALUES.has_key? num[i]
710
+ k = [num[i],num[i+1]]
711
+ i += 2
712
+ else
713
+ k = num[i]
714
+ i += 1
715
+ end
716
+ combined[k] += 1 unless k.nil? || k == '<1>'
717
+ end
718
+
719
+ j = 0
720
+ loop do
721
+ break if j > den.size
722
+ if @@PREFIX_VALUES.has_key? den[j]
723
+ k = [den[j],den[j+1]]
724
+ j += 2
725
+ else
726
+ k = den[j]
727
+ j += 1
728
+ end
729
+ combined[k] -= 1 unless k.nil? || k == '<1>'
730
+ end
731
+
732
+ num = []
733
+ den = []
734
+ combined.each do |key,value|
735
+ case
736
+ when value > 0 : value.times {num << key}
737
+ when value < 0 : value.abs.times {den << key}
738
+ end
739
+ end
740
+ num = ["<1>"] if num.empty?
741
+ den = ["<1>"] if den.empty?
742
+ {:scalar=>q, :numerator=>num.flatten.compact, :denominator=>den.flatten.compact}
743
+ end
744
+
745
+
713
746
  # parse a string into a unit object.
714
747
  # Typical formats like :
715
748
  # "5.6 kg*m/s^2"
@@ -926,6 +959,10 @@ class Time
926
959
  end
927
960
  end
928
961
 
962
+ def self.in(duration)
963
+ Time.now + duration.to_unit
964
+ end
965
+
929
966
  alias :unit_sub :-
930
967
  def -(other)
931
968
  if Unit === other
data/lib/units.rb CHANGED
@@ -93,6 +93,10 @@ UNIT_DEFINITIONS = {
93
93
  '<celcius>' => [%w{degC celcius Celcius}, 1.0, :temperature, %w{<kelvin>}],
94
94
  '<farenheit>' => [%w{degF farenheit Farenheit}, 1.8, :temperature, %w{<kelvin>}],
95
95
  '<rankine>' => [%w{degR rankine Rankine}, 1.8, :temperature, %w{<kelvin>}],
96
+ '<temp-K>' => [%w{tempK}, 1.0, :temperature, %w{<kelvin>}],
97
+ '<temp-C>' => [%w{tempC}, 1.0, :temperature, %w{<kelvin>}],
98
+ '<temp-F>' => [%w{tempF}, 1.0, :temperature, %w{<kelvin>}],
99
+ '<temp-R>' => [%w{tempR}, 1.0, :temperature, %w{<kelvin>}],
96
100
 
97
101
  #time
98
102
  '<second>'=> [%w{s sec second seconds}, 1.0, :time, %w{<second>}],
@@ -1,5 +1,8 @@
1
1
  require 'test/unit'
2
+ require 'rubygems'
2
3
  require 'ruby-units'
4
+ require 'yaml'
5
+ require 'uncertain'
3
6
 
4
7
  class Unit < Numeric
5
8
  @@USER_DEFINITIONS = {'<inchworm>' => [%w{inworm inchworm}, 0.0254, :length, %w{<meter>} ],
@@ -10,6 +13,27 @@ end
10
13
 
11
14
  class TestRubyUnits < Test::Unit::TestCase
12
15
 
16
+ def test_to_yaml
17
+ unit = "1 mm".u
18
+ assert_equal "--- !ruby/object:Unit \nscalar: 1.0\nnumerator: \n- <milli>\n- <meter>\ndenominator: \n- <1>\nsignature: 1\nbase_scalar: 0.001\n", unit.to_yaml
19
+ end
20
+
21
+ def test_time
22
+ a = Time.now
23
+ assert_equal "s", a.to_unit.units
24
+ assert_equal a + 3600, a + "1 h".unit
25
+ assert_equal a - 3600, a - "1 h".unit
26
+ b = Unit(a) + "1 h".unit
27
+ assert_in_delta Time.now - "1 h".unit, "1 h".ago, 1
28
+ assert_in_delta Time.now + 3600, "1 h".from_now, 1
29
+ assert_in_delta "1 h".unit + Time.now, "1 h".from_now, 1
30
+ assert_in_delta Time.now - 3600, "1 h".before_now, 1
31
+ assert_in_delta (Time.now.unit - Time.now).unit.scalar, 0, 1
32
+
33
+ assert_equal "60 min", "min".until(Time.now + 3600).to_s
34
+ assert_equal "01:00", "min".since(Time.now - 3600).to_s("%H:%M")
35
+ end
36
+
13
37
  def test_clone
14
38
  unit1= "1 mm".unit
15
39
  unit2 = unit1.clone
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: ruby-units
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.2.1
7
- date: 2006-09-18 00:00:00 -04:00
6
+ version: 0.2.2
7
+ date: 2006-09-19 00:00:00 -04:00
8
8
  summary: A model that performs unit conversions and unit math
9
9
  require_paths:
10
10
  - lib