ruby-units 0.2.0 → 0.2.1

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.
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: