ruby-units 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/CHANGELOG +36 -1
  2. data/README +43 -8
  3. data/lib/ruby-units.rb +218 -21
  4. data/test/test_ruby-units.rb +1 -2
  5. metadata +1 -1
data/CHANGELOG CHANGED
@@ -57,5 +57,40 @@ Change Log for Ruby-units
57
57
  => Sat Sep 23 04:26:40 EDT 2006
58
58
  * Time.now.unit => 1.159e9 s
59
59
  * works well with 'Uncertain' numerics
60
+ (www.rubyforge.org/projects/uncertain)
60
61
  * Improved parsing
61
-
62
+
63
+ 2006-09-18 0.2.1 * Trig math functions (sin, cos, tan, sinh, cosh, tanh)
64
+ accept units that can be converted to radians
65
+ Math.sin("90 deg".unit) => 1.0
66
+ * Date and DateTime can be offset by a time unit
67
+ (Date.today + "1 day".unit) => 2006-09-19
68
+ Does not work with months since they aren't a consistent
69
+ size
70
+ * Tweaked time usage a bit
71
+ Time.now + "1 hr".unit => Mon Sep 18 11:51:29 EDT 2006
72
+ * can output time in 'hh:mm:ss' format by using
73
+ 'unit.to_s(:time)'
74
+ * added time helper methods
75
+ ago,
76
+ since(Time/DateTime),
77
+ until(Time/DateTime),
78
+ from(Time/DateTime),
79
+ before(Time/DateTime), and
80
+ after(Time/DateTime)
81
+ * Time helpers also work on strings. In this case they
82
+ are first converted to units
83
+ '5 min'.from_now
84
+ '1 week'.ago
85
+ 'min'.since(time)
86
+ 'min'.until(time)
87
+ '1 day'.from()
88
+ * Can pass Strings to time helpers and they will be parsed
89
+ with ParseDate
90
+ * Fixed most parsing bugs (I think)
91
+ * Can pass a strftime format string to to_s to format time
92
+ output
93
+ * can use U'1 mm' or '1 mm'.u to specify units now
94
+ *
95
+
96
+
data/README CHANGED
@@ -1,6 +1,6 @@
1
1
  =Ruby Units
2
2
 
3
- Version: 0.1.0
3
+ Version: 0.2.1
4
4
 
5
5
  Kevin C. Olbrich, Ph.D.
6
6
 
@@ -29,6 +29,11 @@ This package may be installed using:
29
29
  unit = Unit("1 mm") # shorthand
30
30
  unit = "1 mm".to_unit # convert string object
31
31
  unit = object.to_unit # convert any object using object.to_s
32
+ unit = U'1 mm'
33
+ unit = u'1 mm'
34
+ unit = '1 mm'.unit
35
+ unit = '1 mm'.u
36
+
32
37
 
33
38
  ==Rules:
34
39
  1. only 1 quantity per unit (with 2 exceptions... 6'5" and '8 lbs 8 oz')
@@ -69,8 +74,11 @@ Units can be converted to other units in a couple of ways.
69
74
  unit >>= "ft" # => convert and overwrite original object
70
75
  unit3 = unit1 + unit2 # => resulting object will have the units of unit1
71
76
  unit3 = unit1 - unit2 # => resulting object will have the units of unit1
72
- unit1 <=> unit2 # => does comparison on quantities in base units, throws an exception if not compatible
73
- unit1 === unit2 # => true if units and quantity are the same, even if 'equivalent' by <=>
77
+ unit1 <=> unit2 # => does comparison on quantities in base units,
78
+ throws an exception if not compatible
79
+ unit1 === unit2 # => true if units and quantity are the same, even if
80
+ 'equivalent' by <=>
81
+ unit.to('ft') # convert
74
82
 
75
83
  ==Text Output
76
84
  Units will display themselves nicely based on the preferred abbreviation for the units and prefixes.
@@ -78,8 +86,35 @@ Since Unit implements a Unit#to_s, all that is needed in most cases is:
78
86
  "#{Unit.new('1 mm')}" #=> "1 mm"
79
87
 
80
88
  The to_s also accepts some options.
81
- Unit.new('1.5 mm').to_s("%0.2f") # => "1.50 mm". Enter any valid format string
82
- Unit.new('1.5 mm').to_s("in") # => converts to inches before printing
83
- Unit.new("2 m").to_s(:ft) #=> returns 6'7"
84
- Unit.new("100 kg").to_s(:lbs) #=> returns 220 lbs, 7 oz
85
-
89
+ Unit.new('1.5 mm').to_s("%0.2f") # => "1.50 mm". Enter any valid format
90
+ string. Also accepts strftime format
91
+ U('1.5 mm').to_s("in") # => converts to inches before printing
92
+ U("2 m").to_s(:ft) #=> returns 6'7"
93
+ U("100 kg").to_s(:lbs) #=> returns 220 lbs, 7 oz
94
+
95
+
96
+ ==Time Helpers
97
+
98
+ Time, Date, and DateTime objects can have time units added or subtracted.
99
+
100
+ Time.now + "10 min".u
101
+
102
+ Several helpers have also been defined.
103
+
104
+ 'min'.since('9/18/06 3:00pm')
105
+ 'min'.before('9/18/08 3:00pm')
106
+ 'days'.until('1/1/07')
107
+ '5 min'.from(Time.now)
108
+ '5 min'.from_now
109
+ '5 min'.before_now
110
+ '5 min'.before(Time.now)
111
+ '10 min'.ago
112
+
113
+ ==Ranges
114
+
115
+ [U('0 h')..U('10 h')].each {|x| p x}
116
+ works so long as the starting point has an integer scalar
117
+
118
+ ==Math functions
119
+ All Trig math functions (sin, cos, sinh, ...) can take a unit as their parameter. It will be converted to radians and then used if possible.
120
+
data/lib/ruby-units.rb CHANGED
@@ -1,7 +1,8 @@
1
1
  require 'mathn'
2
2
  require 'rational'
3
-
4
- # = Ruby Units 0.2.0
3
+ require 'date'
4
+ require 'parsedate'
5
+ # = Ruby Units 0.2.1
5
6
  #
6
7
  # Copyright 2006 by Kevin C. Olbrich, Ph.D.
7
8
  #
@@ -185,10 +186,16 @@ class Unit < Numeric
185
186
  when :lbs:
186
187
  ounces = self.to("oz").scalar
187
188
  "#{(ounces / 16).truncate} lbs, #{(ounces % 16).round} oz"
189
+ when String
190
+ begin #first try a standard format string
191
+ target_units =~ /(%[\w\d#+-.]*)*\s*(.+)*/
192
+ return self.to($2).to_s($1) if $2
193
+ "#{($1 || '%g') % @scalar || 0} #{self.units}".strip
194
+ rescue #if that is malformed, try a time string
195
+ return (Time.gm(0) + self).strftime(target_units)
196
+ end
188
197
  else
189
- target_units =~ /(%[\w\d#+-.]*)*\s*(.+)*/
190
- return self.to($2).to_s($1) if $2
191
- "#{($1 || '%g') % @scalar || 0} #{self.units}".strip
198
+ "#{'%g' % @scalar} #{self.units}".strip
192
199
  end
193
200
  end
194
201
 
@@ -260,9 +267,11 @@ class Unit < Numeric
260
267
  else
261
268
  raise ArgumentError, "Incompatible Units"
262
269
  end
270
+ elsif Time === other
271
+ other + self
263
272
  else
264
273
  x,y = coerce(other)
265
- x + y
274
+ y + x
266
275
  end
267
276
  end
268
277
 
@@ -276,6 +285,8 @@ class Unit < Numeric
276
285
  else
277
286
  raise ArgumentError, "Incompatible Units"
278
287
  end
288
+ elsif Time === other
289
+ other + self
279
290
  else
280
291
  x,y = coerce(other)
281
292
  x - y
@@ -360,7 +371,7 @@ class Unit < Numeric
360
371
  raise ArgumentError, "Illegal root" unless vec.max == 0
361
372
  num = @numerator.clone
362
373
  den = @denominator.clone
363
-
374
+
364
375
  @numerator.uniq.each do |item|
365
376
  x = num.find_all {|i| i==item}.size
366
377
  r = ((x/n)*(n-1)).to_int
@@ -372,8 +383,8 @@ class Unit < Numeric
372
383
  r = ((x/n)*(n-1)).to_int
373
384
  r.times {|x| den.delete_at(den.index(item))}
374
385
  end
375
- q = @scalar**(1/n)
376
- Unit.new([q,num,den])
386
+ q = @scalar**(1/n)
387
+ Unit.new(:scalar=>q,:numerator=>num,:denominator=>den)
377
388
  end
378
389
 
379
390
  # returns inverse of Unit (1/unit)
@@ -402,8 +413,8 @@ class Unit < Numeric
402
413
  return self if FalseClass === other
403
414
  if String === other && other =~ /temp(K|C|R|F)/
404
415
  raise ArgumentError, "Receiver is not a temperature unit" unless self.signature==400
405
- #return self.to_base.to(other) unless self.is_base?
406
- return self.to_base.to("tempF") if @numerator.size > 1 || @denominator != ['<1>']
416
+ return self.to_base.to(other) unless self.is_base?
417
+ #return self.to_base.to("tempF") if @numerator.size > 1 || @denominator != ['<1>']
407
418
  q=case
408
419
  when @numerator.include?('<celcius>'):
409
420
  case other
@@ -535,7 +546,7 @@ class Unit < Numeric
535
546
  {:scalar=>q, :numerator=>num.flatten.compact, :denominator=>den.flatten.compact}
536
547
  end
537
548
 
538
- # returns the scalar part of the Unit
549
+ # converts the unit back to a float if it is unitless
539
550
  def to_f
540
551
  return @scalar.to_f if self.unitless?
541
552
  raise RuntimeError, "Can't convert to float unless unitless. Use Unit#scalar"
@@ -602,6 +613,7 @@ class Unit < Numeric
602
613
  Unit.new([@scalar.to_int, @numerator, @denominator])
603
614
  end
604
615
 
616
+ # Tries to make a Time object from current unit
605
617
  def to_time
606
618
  Time.at(self)
607
619
  end
@@ -617,6 +629,59 @@ class Unit < Numeric
617
629
  def zero?
618
630
  return @scalar.zero?
619
631
  end
632
+
633
+ # '5 min'.unit.ago
634
+ def ago
635
+ Time.now - self
636
+ end
637
+
638
+ # '5 min'.before(time)
639
+ def before(time_point = ::Time.now)
640
+ raise ArgumentError, "Must specify a Time" unless time_point
641
+ if String === time_point
642
+ Time.local(*ParseDate.parsedate(time_point))-self
643
+ else
644
+ time_point - self
645
+ end
646
+ end
647
+
648
+ # 'min'.since(time)
649
+ def since(time_point = ::Time.now)
650
+ case time_point
651
+ when Time: (Time.now - time_point).unit('s').to(self)
652
+ when DateTime: (DateTime.now - time_point).unit('d').to(self)
653
+ when String: (Time.now - Time.local(*ParseDate.parsedate(time_point))).unit('s').to(self)
654
+ else
655
+ raise ArgumentError, "Must specify a Time" unless time_point
656
+ end
657
+ end
658
+
659
+ # 'min'.until(time)
660
+ def until(time_point = ::Time.now)
661
+ case time_point
662
+ when Time: (time_point - Time.now).unit('s').to(self)
663
+ when DateTime: (time_point - DateTime.now).unit('d').to(self)
664
+ when String: (Time.local(*ParseDate.parsedate(time_point)) - Time.now).unit('s').to(self)
665
+ else
666
+ raise ArgumentError, "Must specify a Time" unless time_point
667
+ end
668
+ end
669
+
670
+ # '5 min'.from_now
671
+ def from_now
672
+ self.from
673
+ end
674
+
675
+ # '5 min'.from(time)
676
+ def from(time_point = ::Time.now)
677
+ raise ArgumentError, "Must specify a Time" unless time_point
678
+ if String === time_point
679
+ Time.local(*ParseDate.parsedate(time_point))+self
680
+ else
681
+ time_point + self
682
+ end
683
+ end
684
+ alias :after :from
620
685
 
621
686
  def succ
622
687
  raise ArgumentError, "Non Integer Scalar" unless @scalar == @scalar.to_i
@@ -705,17 +770,17 @@ class Unit < Numeric
705
770
  @scalar = @scalar.to_f
706
771
  end
707
772
 
708
- @numerator = top.scan(/((#{@@PREFIX_REGEX})*(#{@@UNIT_REGEX}))/).delete_if {|x| x.empty?}.compact if top
709
- @denominator = bottom.scan(/((#{@@PREFIX_REGEX})*(#{@@UNIT_REGEX}))/).delete_if {|x| x.empty?}.compact if bottom
773
+ @numerator = top.scan(/(#{@@PREFIX_REGEX})*?(#{@@UNIT_REGEX})\b/).delete_if {|x| x.empty?}.compact if top
774
+ @denominator = bottom.scan(/(#{@@PREFIX_REGEX})*?(#{@@UNIT_REGEX})\b/).delete_if {|x| x.empty?}.compact if bottom
710
775
 
711
776
  @numerator = @numerator.map do |item|
712
777
  item.map {|x| Regexp.escape(x) if x}
713
- @@UNIT_MAP[item[0]] ? [@@UNIT_MAP[item[0]]] : [@@PREFIX_MAP[item[1]], @@UNIT_MAP[item[2]]]
778
+ @@PREFIX_MAP[item[0]] ? [@@PREFIX_MAP[item[0]], @@UNIT_MAP[item[1]]] : [@@UNIT_MAP[item[1]]]
714
779
  end.flatten.compact.delete_if {|x| x.empty?}
715
780
 
716
781
  @denominator = @denominator.map do |item|
717
782
  item.map {|x| Regexp.escape(x) if x}
718
- @@UNIT_MAP[item[0]] ? [@@UNIT_MAP[item[0]]] : [@@PREFIX_MAP[item[1]], @@UNIT_MAP[item[2]]]
783
+ @@PREFIX_MAP[item[0]] ? [@@PREFIX_MAP[item[0]], @@UNIT_MAP[item[1]]] : [@@UNIT_MAP[item[1]]]
719
784
  end.flatten.compact.delete_if {|x| x.empty?}
720
785
 
721
786
  @numerator = ['<1>'] if @numerator.empty?
@@ -724,43 +789,77 @@ class Unit < Numeric
724
789
  end
725
790
  end
726
791
 
792
+ # Need the 'Uncertain' gem for this to do anything helpful
727
793
  if defined? Uncertain
728
794
  class Uncertain
729
795
  def to_unit(other=nil)
730
796
  other ? Unit.new(self).to(other) : Unit.new(self)
731
797
  end
798
+ alias :unit :to_unit
799
+ alias :u :to_unit
732
800
  end
733
801
  end
734
802
 
735
803
 
804
+ # Allow date objects to do offsets by a time unit
805
+ # Date.today + U"1 week" => gives today+1 week
806
+ class Date
807
+ alias :unit_date_add :+
808
+ def +(unit)
809
+ if Unit === unit
810
+ unit_date_add(unit.to('day').scalar)
811
+ else
812
+ unit_date_add(unit)
813
+ end
814
+ end
815
+
816
+ alias :unit_date_sub :-
817
+ def -(unit)
818
+ if Unit === unit
819
+ unit_date_sub(unit.to('day').scalar)
820
+ else
821
+ unit_date_sub(unit)
822
+ end
823
+ end
824
+ end
825
+
736
826
  class Object
737
827
  def Unit(other)
738
828
  other.to_unit
739
829
  end
830
+ alias :U :Unit
831
+ alias :u :Unit
740
832
  end
741
833
 
834
+ # make a unitless unit with a given scalar
742
835
  class Numeric
743
836
  def to_unit(other = nil)
744
837
  other ? Unit.new(self) * Unit.new(other) : Unit.new(self)
745
838
  end
746
839
  alias :unit :to_unit
840
+ alias :u :to_unit
747
841
  end
748
842
 
749
-
843
+ # make a unit from an array
844
+ # [1, 'mm'].unit => 1 mm
750
845
  class Array
751
846
  def to_unit(other = nil)
752
847
  other ? Unit.new(self).to(other) : Unit.new(self)
753
848
  end
754
849
  alias :unit :to_unit
850
+ alias :u :to_unit
755
851
  end
756
852
 
853
+ # make a string into a unit
757
854
  class String
758
855
  def to_unit(other = nil)
759
856
  other ? Unit.new(self) >> other : Unit.new(self)
760
857
  end
761
858
  alias :unit :to_unit
859
+ alias :u :to_unit
762
860
  alias :unit_format :%
763
861
 
862
+ # format unit output using formating codes '%0.2f' % '1 mm'.unit => '1.00 mm'
764
863
  def %(*args)
765
864
  if Unit === args[0]
766
865
  args[0].to_s(self)
@@ -768,8 +867,37 @@ class String
768
867
  unit_format(*args)
769
868
  end
770
869
  end
870
+
871
+ def from(time_point = ::Time.now)
872
+ self.unit.from(time_point)
873
+ end
874
+ alias :after :from
875
+ alias :from_now :from
876
+
877
+ def ago
878
+ self.unit.ago
879
+ end
880
+
881
+ def before(time_point = ::Time.now)
882
+ self.unit.before(time_point)
883
+ end
884
+ alias :before_now :before
885
+
886
+ def since(time_point = ::Time.now)
887
+ self.unit.since(time_point)
888
+ end
889
+
890
+ def until(time_point = ::Time.now)
891
+ self.unit.until(time_point)
892
+ end
893
+
894
+ def to(other)
895
+ self.unit.to(other)
896
+ end
771
897
  end
772
898
 
899
+
900
+ # Allow time objects to use
773
901
  class Time
774
902
 
775
903
  class << self
@@ -785,14 +913,14 @@ class Time
785
913
  end
786
914
 
787
915
  def to_unit(other = "s")
788
- other ? Unit.new(self.to_f) * Unit.new(other) : Unit.new(self.to_f)
916
+ other ? Unit.new("#{self.to_f} s").to(other) : Unit.new("#{self.to_f} s")
789
917
  end
790
918
  alias :unit :to_unit
791
-
919
+ alias :u :to_unit
792
920
  alias :unit_add :+
793
921
  def +(other)
794
922
  if Unit === other
795
- self.unit + other
923
+ unit_add(other.to('s').scalar)
796
924
  else
797
925
  unit_add(other)
798
926
  end
@@ -801,9 +929,78 @@ class Time
801
929
  alias :unit_sub :-
802
930
  def -(other)
803
931
  if Unit === other
804
- self.unit - other
932
+ unit_sub(other.to('s').scalar)
805
933
  else
806
934
  unit_sub(other)
807
935
  end
808
936
  end
937
+ end
938
+
939
+ module Math
940
+ alias unit_sin sin
941
+ def sin(n)
942
+ if Unit === n
943
+ unit_sin(n.to('radian').scalar)
944
+ else
945
+ unit_sin(n)
946
+ end
947
+ end
948
+
949
+ alias unit_cos cos
950
+ def cos(n)
951
+ if Unit === n
952
+ unit_cos(n.to('radian').scalar)
953
+ else
954
+ unit_cos(n)
955
+ end
956
+ end
957
+
958
+ alias unit_sinh sinh
959
+ def sinh(n)
960
+ if Unit === n
961
+ unit_sinh(n.to('radian').scalar)
962
+ else
963
+ unit_sinh(n)
964
+ end
965
+ end
966
+
967
+ alias unit_cosh cosh
968
+ def cosh(n)
969
+ if Unit === n
970
+ unit_cosh(n.to('radian').scalar)
971
+ else
972
+ unit_cosh(n)
973
+ end
974
+ end
975
+
976
+ alias unit_tan tan
977
+ def tan(n)
978
+ if Unit === n
979
+ unit_tan(n.to('radian').scalar)
980
+ else
981
+ unit_tan(n)
982
+ end
983
+ end
984
+
985
+ alias unit_tanh tanh
986
+ def tanh(n)
987
+ if Unit === n
988
+ unit_tanh(n.to('radian').scalar)
989
+ else
990
+ unit_tanh(n)
991
+ end
992
+ end
993
+
994
+ module_function :unit_sin
995
+ module_function :sin
996
+ module_function :unit_cos
997
+ module_function :cos
998
+ module_function :unit_sinh
999
+ module_function :sinh
1000
+ module_function :unit_cosh
1001
+ module_function :cosh
1002
+ module_function :unit_tan
1003
+ module_function :tan
1004
+ module_function :unit_tanh
1005
+ module_function :tanh
809
1006
  end
@@ -447,7 +447,6 @@ class TestRubyUnits < Test::Unit::TestCase
447
447
  assert_in_delta 558.27, unit1.to("tempR").scalar, 0.01
448
448
  unit3 = Unit.new("1 J/degC")
449
449
  assert_in_delta 1, (unit3 >> "J/degK").scalar, 0.01
450
- assert_raises(ArgumentError) {"10 degH".unit >> "tempF"}
451
450
 
452
451
  # round trip conversions
453
452
  unit1 = Unit "37 degC"
@@ -562,7 +561,7 @@ class TestRubyUnits < Test::Unit::TestCase
562
561
  def test_inspect
563
562
  unit1 = Unit "1 mm"
564
563
  assert_equal "1 mm", unit1.inspect
565
- assert_not_equal "1 mm", unit1.inspect(:dump)
564
+ assert_not_equal "1.0 mm", unit1.inspect(:dump)
566
565
  end
567
566
 
568
567
  def test_to_f
metadata CHANGED
@@ -3,7 +3,7 @@ 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.0
6
+ version: 0.2.1
7
7
  date: 2006-09-18 00:00:00 -04:00
8
8
  summary: A model that performs unit conversions and unit math
9
9
  require_paths: