ruby-units 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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