ruby-units 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/ruby-units.rb CHANGED
@@ -1,1414 +1,11 @@
1
- require 'mathn'
2
- require 'rational'
3
- require 'date'
4
- require 'parsedate'
5
- # = Ruby Units 1.0.1
6
- #
7
- # Copyright 2006 by Kevin C. Olbrich, Ph.D.
8
- #
9
- # See http://rubyforge.org/ruby-units/
10
- #
11
- # http://www.sciwerks.org
12
- #
13
- # mailto://kevin.olbrich+ruby-units@gmail.com
14
- #
15
- # See README for detailed usage instructions and examples
16
- #
17
- # ==Unit Definition Format
18
- #
19
- # '<name>' => [%w{prefered_name synonyms}, conversion_to_base, :classification, %w{<base> <units> <in> <numerator>} , %w{<base> <units> <in> <denominator>} ],
20
- #
21
- # Prefixes (e.g., a :prefix classification) get special handling
22
- # Note: The accuracy of unit conversions depends on the precision of the conversion factor.
23
- # If you have more accurate estimates for particular conversion factors, please send them
24
- # to me and I will incorporate them into the next release. It is also incumbent on the end-user
25
- # to ensure that the accuracy of any conversions is sufficient for their intended application.
26
- #
27
- # While there are a large number of unit specified in the base package,
28
- # there are also a large number of units that are not included.
29
- # This package covers nearly all SI, Imperial, and units commonly used
30
- # in the United States. If your favorite units are not listed here, send me an email
31
- #
32
- # To add / override a unit definition, add a code block like this..
33
- #
34
- # class Unit < Numeric
35
- # UNIT_DEFINITIONS = {
36
- # <name>' => [%w{prefered_name synonyms}, conversion_to_base, :classification, %w{<base> <units> <in> <numerator>} , %w{<base> <units> <in> <denominator>} ]
37
- # }
38
- # end
39
- # Unit.setup
40
- class Unit < Numeric
41
- require 'units'
42
- # pre-generate hashes from unit definitions for performance.
43
- VERSION = '1.0.1'
44
- @@USER_DEFINITIONS = {}
45
- @@PREFIX_VALUES = {}
46
- @@PREFIX_MAP = {}
47
- @@UNIT_MAP = {}
48
- @@UNIT_VALUES = {}
49
- @@OUTPUT_MAP = {}
50
- @@BASE_UNITS = ['<meter>','<kilogram>','<second>','<mole>', '<farad>', '<ampere>','<radian>','<kelvin>','<byte>','<dollar>','<candela>','<each>','<steradian>','<decibel>']
51
- UNITY = '<1>'
52
- UNITY_ARRAY= [UNITY]
53
- FEET_INCH_REGEX = /(\d+)\s*(?:'|ft|feet)\s*(\d+)\s*(?:"|in|inches)/
54
- TIME_REGEX = /(\d+)*:(\d+)*:*(\d+)*[:,]*(\d+)*/
55
- LBS_OZ_REGEX = /(\d+)\s*(?:#|lbs|pounds)+[\s,]*(\d+)\s*(?:oz|ounces)/
56
- SCI_NUMBER = %r{([+-]?\d*[.]?\d+(?:[Ee][+-]?)?\d*)}
57
- RATIONAL_NUMBER = /(\d+)\/(\d+)/
58
- COMPLEX_NUMBER = /#{SCI_NUMBER}?#{SCI_NUMBER}i\b/
59
- NUMBER_REGEX = /#{SCI_NUMBER}*\s*(.+)?/
60
- UNIT_STRING_REGEX = /#{SCI_NUMBER}*\s*([^\/]*)\/*(.+)*/
61
- TOP_REGEX = /([^ \*]+)(?:\^|\*\*)([\d-]+)/
62
- BOTTOM_REGEX = /([^* ]+)(?:\^|\*\*)(\d+)/
63
- UNCERTAIN_REGEX = /#{SCI_NUMBER}\s*\+\/-\s*#{SCI_NUMBER}\s(.+)/
64
- COMPLEX_REGEX = /#{COMPLEX_NUMBER}\s?(.+)?/
65
- RATIONAL_REGEX = /#{RATIONAL_NUMBER}\s?(.+)?/
66
- KELVIN = ['<kelvin>']
67
- FARENHEIT = ['<farenheit>']
68
- RANKINE = ['<rankine>']
69
- CELCIUS = ['<celcius>']
1
+ require 'ruby_units/array'
2
+ require 'ruby_units/date'
3
+ require 'ruby_units/time'
4
+ require 'ruby_units/math'
5
+ require 'ruby_units/numeric'
6
+ require 'ruby_units/object'
7
+ require 'ruby_units/string'
8
+ require 'ruby_units/complex'
9
+ require 'ruby_units/units'
10
+ require 'ruby_units/ruby-units'
70
11
 
71
- SIGNATURE_VECTOR = [:length, :time, :temperature, :mass, :current, :substance, :luminosity, :currency, :memory, :angle, :capacitance]
72
- @@KINDS = {
73
- -312058=>:resistance,
74
- -312038=>:inductance,
75
- -152040=>:magnetism,
76
- -152038=>:magnetism,
77
- -152058=>:potential,
78
- -39=>:acceleration,
79
- -38=>:radiation,
80
- -20=>:frequency,
81
- -19=>:speed,
82
- -18=>:viscosity,
83
- 0=>:unitless,
84
- 1=>:length,
85
- 2=>:area,
86
- 3=>:volume,
87
- 20=>:time,
88
- 400=>:temperature,
89
- 7942=>:power,
90
- 7959=>:pressure,
91
- 7962=>:energy,
92
- 7979=>:viscosity,
93
- 7981=>:force,
94
- 7997=>:mass_concentration,
95
- 8000=>:mass,
96
- 159999=>:magnetism,
97
- 160000=>:current,
98
- 160020=>:charge,
99
- 312058=>:resistance,
100
- 3199980=>:activity,
101
- 3199997=>:molar_concentration,
102
- 3200000=>:substance,
103
- 63999998=>:illuminance,
104
- 64000000=>:luminous_power,
105
- 1280000000=>:currency,
106
- 25600000000=>:memory,
107
- 511999999980=>:angular_velocity,
108
- 512000000000=>:angle,
109
- 10240000000000=>:capacitance,
110
- }
111
-
112
- @@cached_units = {}
113
- @@base_unit_cache = {}
114
-
115
- def self.setup
116
- @@ALL_UNIT_DEFINITIONS = UNIT_DEFINITIONS.merge!(@@USER_DEFINITIONS)
117
- for unit in (@@ALL_UNIT_DEFINITIONS) do
118
- key, value = unit
119
- if value[2] == :prefix then
120
- @@PREFIX_VALUES[key]=value[1]
121
- for name in value[0] do
122
- @@PREFIX_MAP[name]=key
123
- end
124
- else
125
- @@UNIT_VALUES[key]={}
126
- @@UNIT_VALUES[key][:scalar]=value[1]
127
- @@UNIT_VALUES[key][:numerator]=value[3] if value[3]
128
- @@UNIT_VALUES[key][:denominator]=value[4] if value[4]
129
- for name in value[0] do
130
- @@UNIT_MAP[name]=key
131
- end
132
- end
133
- @@OUTPUT_MAP[key]=value[0][0]
134
- end
135
- @@PREFIX_REGEX = @@PREFIX_MAP.keys.sort_by {|prefix| prefix.length}.reverse.join('|')
136
- @@UNIT_REGEX = @@UNIT_MAP.keys.sort_by {|unit| unit.length}.reverse.join('|')
137
- @@UNIT_MATCH_REGEX = /(#{@@PREFIX_REGEX})*?(#{@@UNIT_REGEX})\b/
138
- Unit.new(1)
139
- end
140
-
141
-
142
- include Comparable
143
- attr_accessor :scalar, :numerator, :denominator, :signature, :base_scalar, :base_numerator, :base_denominator, :output, :unit_name
144
-
145
- def to_yaml_properties
146
- %w{@scalar @numerator @denominator @signature @base_scalar}
147
- end
148
-
149
- def copy(from)
150
- @scalar = from.scalar
151
- @numerator = from.numerator
152
- @denominator = from.denominator
153
- @is_base = from.is_base?
154
- @signature = from.signature
155
- @base_scalar = from.base_scalar
156
- @output = from.output rescue nil
157
- @unit_name = from.unit_name rescue nil
158
- end
159
-
160
- # basically a copy of the basic to_yaml. Needed because otherwise it ends up coercing the object to a string
161
- # before YAML'izing it.
162
- def to_yaml( opts = {} )
163
- YAML::quick_emit( object_id, opts ) do |out|
164
- out.map( taguri, to_yaml_style ) do |map|
165
- for m in to_yaml_properties do
166
- map.add( m[1..-1], instance_variable_get( m ) )
167
- end
168
- end
169
- end
170
- end
171
-
172
- # Create a new Unit object. Can be initialized using a string, or a hash
173
- # Valid formats include:
174
- # "5.6 kg*m/s^2"
175
- # "5.6 kg*m*s^-2"
176
- # "5.6 kilogram*meter*second^-2"
177
- # "2.2 kPa"
178
- # "37 degC"
179
- # "1" -- creates a unitless constant with value 1
180
- # "GPa" -- creates a unit with scalar 1 with units 'GPa'
181
- # 6'4" -- recognized as 6 feet + 4 inches
182
- # 8 lbs 8 oz -- recognized as 8 lbs + 8 ounces
183
- #
184
- def initialize(*options)
185
- @scalar = nil
186
- @base_scalar = nil
187
- @unit_name = nil
188
- @signature = nil
189
- @output = nil
190
- if options.size == 2
191
- begin
192
- cached = @@cached_units[options[1]] * options[0]
193
- copy(cached)
194
- rescue
195
- initialize("#{options[0]} #{(options[1].units rescue options[1])}")
196
- end
197
- return
198
- end
199
- if options.size == 3
200
- begin
201
- cached = @@cached_units["#{options[1]}/#{options[2]}"] * options[0]
202
- copy(cached)
203
- rescue
204
- initialize("#{options[0]} #{options[1]}/#{options[2]}")
205
- end
206
- return
207
- end
208
-
209
-
210
- case options[0]
211
- when Hash:
212
- @scalar = options[0][:scalar] || 1
213
- @numerator = options[0][:numerator] || UNITY_ARRAY
214
- @denominator = options[0][:denominator] || UNITY_ARRAY
215
- @signature = options[0][:signature]
216
- when Array:
217
- initialize(*options[0])
218
- return
219
- when Numeric:
220
- @scalar = options[0]
221
- @numerator = @denominator = UNITY_ARRAY
222
- when Time:
223
- @scalar = options[0].to_f
224
- @numerator = ['<second>']
225
- @denominator = UNITY_ARRAY
226
- when DateTime:
227
- @scalar = options[0].ajd
228
- @numerator = ['<day>']
229
- @denominator = UNITY_ARRAY
230
- when "": raise ArgumentError, "No Unit Specified"
231
- when String: parse(options[0])
232
- else
233
- raise ArgumentError, "Invalid Unit Format"
234
- end
235
- self.update_base_scalar
236
- self.replace_temperature
237
-
238
- unary_unit = self.units || ""
239
- opt_units = options[0].scan(NUMBER_REGEX)[0][1] if String === options[0]
240
- unless @@cached_units.keys.include?(opt_units) || (opt_units =~ /(temp|deg(C|K|R|F))|(pounds|lbs[ ,]\d+ ounces|oz)|('\d+")|(ft|feet[ ,]\d+ in|inch|inches)|%|(#{TIME_REGEX})|i\s?(.+)?/)
241
- @@cached_units[opt_units] = (self.scalar == 1 ? self : opt_units.unit) if opt_units && !opt_units.empty?
242
- end
243
- unless @@cached_units.keys.include?(unary_unit) || (unary_unit =~ /(temp|deg)(C|K|R|F)/) then
244
- @@cached_units[unary_unit] = (self.scalar == 1 ? self : unary_unit.unit)
245
- end
246
- [@scalar, @numerator, @denominator, @base_scalar, @signature, @is_base].each {|x| x.freeze}
247
- self
248
- end
249
-
250
- def kind
251
- return @@KINDS[self.signature]
252
- end
253
-
254
- def self.cached
255
- return @@cached_units
256
- end
257
-
258
- def self.clear_cache
259
- @@cached_units = {}
260
- @@base_unit_cache = {}
261
- end
262
-
263
- def self.base_unit_cache
264
- return @@base_unit_cache
265
- end
266
-
267
- def to_unit
268
- self
269
- end
270
- alias :unit :to_unit
271
-
272
- # Returns 'true' if the Unit is represented in base units
273
- def is_base?
274
- return @is_base if defined? @is_base
275
- return @is_base=true if @signature == 400 && @numerator.size == 1 && @numerator[0] =~ /(celcius|kelvin|farenheit|rankine)/
276
- n = @numerator + @denominator
277
- for x in n.compact do
278
- return @is_base=false unless x == UNITY || (@@BASE_UNITS.include?((x)))
279
- end
280
- return @is_base = true
281
- end
282
-
283
- # convert to base SI units
284
- # results of the conversion are cached so subsequent calls to this will be fast
285
- def to_base
286
- return self if self.is_base?
287
- cached = @@base_unit_cache[self.units] * self.scalar rescue nil
288
- return cached if cached
289
-
290
- num = []
291
- den = []
292
- q = 1
293
- for unit in @numerator.compact do
294
- if @@PREFIX_VALUES[unit]
295
- q *= @@PREFIX_VALUES[unit]
296
- else
297
- q *= @@UNIT_VALUES[unit][:scalar] if @@UNIT_VALUES[unit]
298
- num << @@UNIT_VALUES[unit][:numerator] if @@UNIT_VALUES[unit] && @@UNIT_VALUES[unit][:numerator]
299
- den << @@UNIT_VALUES[unit][:denominator] if @@UNIT_VALUES[unit] && @@UNIT_VALUES[unit][:denominator]
300
- end
301
- end
302
- for unit in @denominator.compact do
303
- if @@PREFIX_VALUES[unit]
304
- q /= @@PREFIX_VALUES[unit]
305
- else
306
- q /= @@UNIT_VALUES[unit][:scalar] if @@UNIT_VALUES[unit]
307
- den << @@UNIT_VALUES[unit][:numerator] if @@UNIT_VALUES[unit] && @@UNIT_VALUES[unit][:numerator]
308
- num << @@UNIT_VALUES[unit][:denominator] if @@UNIT_VALUES[unit] && @@UNIT_VALUES[unit][:denominator]
309
- end
310
- end
311
-
312
- num = num.flatten.compact
313
- den = den.flatten.compact
314
- num = UNITY_ARRAY if num.empty?
315
- base= Unit.new(Unit.eliminate_terms(q,num,den))
316
- @@base_unit_cache[self.units]=base
317
- return base * @scalar
318
- end
319
-
320
- # Generate human readable output.
321
- # If the name of a unit is passed, the unit will first be converted to the target unit before output.
322
- # some named conversions are available
323
- #
324
- # :ft - outputs in feet and inches (e.g., 6'4")
325
- # :lbs - outputs in pounds and ounces (e.g, 8 lbs, 8 oz)
326
- #
327
- # You can also pass a standard format string (i.e., '%0.2f')
328
- # or a strftime format string.
329
- #
330
- # output is cached so subsequent calls for the same format will be fast
331
- #
332
- def to_s(target_units=nil)
333
- out = @output[target_units] rescue nil
334
- if out
335
- return out
336
- else
337
- case target_units
338
- when :ft:
339
- inches = self.to("in").scalar.to_int
340
- out = "#{(inches / 12).truncate}\'#{(inches % 12).round}\""
341
- when :lbs:
342
- ounces = self.to("oz").scalar.to_int
343
- out = "#{(ounces / 16).truncate} lbs, #{(ounces % 16).round} oz"
344
- when String
345
- begin #first try a standard format string
346
- target_units =~ /(%[\w\d#+-.]*)*\s*(.+)*/
347
- out = $2 ? self.to($2).to_s($1) : "#{($1 || '%g') % @scalar || 0} #{self.units}".strip
348
- rescue #if that is malformed, try a time string
349
- out = (Time.gm(0) + self).strftime(target_units)
350
- end
351
- else
352
- out = case @scalar
353
- when Rational :
354
- "#{@scalar} #{self.units}"
355
- else
356
- "#{'%g' % @scalar} #{self.units}"
357
- end.strip
358
- end
359
- @output = {target_units => out}
360
- return out
361
- end
362
- end
363
-
364
- # Normally pretty prints the unit, but if you really want to see the guts of it, pass ':dump'
365
- def inspect(option=nil)
366
- return super() if option == :dump
367
- self.to_s
368
- end
369
-
370
- # returns true if no associated units
371
- # false, even if the units are "unitless" like 'radians, each, etc'
372
- def unitless?
373
- (@numerator == UNITY_ARRAY && @denominator == UNITY_ARRAY)
374
- end
375
-
376
- # Compare two Unit objects. Throws an exception if they are not of compatible types.
377
- # Comparisons are done based on the value of the unit in base SI units.
378
- def <=>(other)
379
- case other
380
- when Unit:
381
- raise ArgumentError, "Incompatible Units" unless self =~ other
382
- self.base_scalar <=> other.base_scalar
383
- else
384
- x,y = coerce(other)
385
- x <=> y
386
- end
387
- end
388
-
389
- # check to see if units are compatible, but not the scalar part
390
- # this check is done by comparing signatures for performance reasons
391
- # if passed a string, it will create a unit object with the string and then do the comparison
392
- # this permits a syntax like:
393
- # unit =~ "mm"
394
- # if you want to do a regexp on the unit string do this ...
395
- # unit.units =~ /regexp/
396
- def =~(other)
397
- case other
398
- when Unit : self.signature == other.signature
399
- else
400
- x,y = coerce(other)
401
- x =~ y
402
- end
403
- end
404
-
405
- alias :compatible? :=~
406
- alias :compatible_with? :=~
407
-
408
- # Compare two units. Returns true if quantities and units match
409
- #
410
- # Unit("100 cm") === Unit("100 cm") # => true
411
- # Unit("100 cm") === Unit("1 m") # => false
412
- def ===(other)
413
- case other
414
- when Unit: (self.scalar == other.scalar) && (self.units == other.units)
415
- else
416
- x,y = coerce(other)
417
- x === y
418
- end
419
- end
420
-
421
- alias :same? :===
422
- alias :same_as? :===
423
-
424
- # Add two units together. Result is same units as receiver and scalar and base_scalar are updated appropriately
425
- # throws an exception if the units are not compatible.
426
- # It is possible to add Time objects to units of time
427
- def +(other)
428
- if Unit === other
429
- if self =~ other then
430
- @q ||= @@cached_units[self.units].scalar / @@cached_units[self.units].base_scalar
431
- Unit.new(:scalar=>(self.base_scalar + other.base_scalar)*@q, :numerator=>@numerator, :denominator=>@denominator, :signature => @signature)
432
- else
433
- raise ArgumentError, "Incompatible Units"
434
- end
435
- elsif Time === other
436
- other + self
437
- else
438
- x,y = coerce(other)
439
- y + x
440
- end
441
- end
442
-
443
- # Subtract two units. Result is same units as receiver and scalar and base_scalar are updated appropriately
444
- # throws an exception if the units are not compatible.
445
- def -(other)
446
- if Unit === other
447
- if self =~ other then
448
- @q ||= @@cached_units[self.units].scalar / @@cached_units[self.units].base_scalar
449
- Unit.new(:scalar=>(self.base_scalar - other.base_scalar)*@q, :numerator=>@numerator, :denominator=>@denominator, :signature=>@signature)
450
- else
451
- raise ArgumentError, "Incompatible Units"
452
- end
453
- elsif Time === other
454
- other - self
455
- else
456
- x,y = coerce(other)
457
- y-x
458
- end
459
- end
460
-
461
- # Multiply two units.
462
- def *(other)
463
- case other
464
- when Unit
465
- opts = Unit.eliminate_terms(@scalar*other.scalar, @numerator + other.numerator ,@denominator + other.denominator)
466
- opts.merge!(:signature => @signature + other.signature)
467
- Unit.new(opts)
468
- when Numeric
469
- Unit.new(:scalar=>@scalar*other, :numerator=>@numerator, :denominator=>@denominator, :signature => @signature)
470
- else
471
- x,y = coerce(other)
472
- x * y
473
- end
474
- end
475
-
476
- # Divide two units.
477
- # Throws an exception if divisor is 0
478
- def /(other)
479
- case other
480
- when Unit
481
- raise ZeroDivisionError if other.zero?
482
- opts = Unit.eliminate_terms(@scalar/other.scalar, @numerator + other.denominator ,@denominator + other.numerator)
483
- opts.merge!(:signature=> @signature - other.signature)
484
- Unit.new(opts)
485
- when Numeric
486
- raise ZeroDivisionError if other.zero?
487
- Unit.new(:scalar=>@scalar/other, :numerator=>@numerator, :denominator=>@denominator, :signature => @signature)
488
- else
489
- x,y = coerce(other)
490
- y / x
491
- end
492
- end
493
-
494
- # Exponentiate. Only takes integer powers.
495
- # Note that anything raised to the power of 0 results in a Unit object with a scalar of 1, and no units.
496
- # Throws an exception if exponent is not an integer.
497
- # Ideally this routine should accept a float for the exponent
498
- # It should then convert the float to a rational and raise the unit by the numerator and root it by the denominator
499
- # but, sadly, floats can't be converted to rationals.
500
- #
501
- # For now, if a rational is passed in, it will be used, otherwise we are stuck with integers and certain floats < 1
502
- def **(other)
503
- if Numeric === other
504
- return Unit("1") if other.zero?
505
- return self if other == 1
506
- return self.inverse if other == -1
507
- end
508
- case other
509
- when Rational:
510
- self.power(other.numerator).root(other.denominator)
511
- when Integer:
512
- self.power(other)
513
- when Float:
514
- return self**(other.to_i) if other == other.to_i
515
- valid = (1..9).map {|x| 1/x}
516
- raise ArgumentError, "Not a n-th root (1..9), use 1/n" unless valid.include? other.abs
517
- self.root((1/other).to_int)
518
- else
519
- raise ArgumentError, "Invalid Exponent"
520
- end
521
- end
522
-
523
- def real
524
- return Unit.new(self.scalar.real,self.units)
525
- end
526
-
527
- def imag
528
- return Unit.new(self.scalar.imag, self.units)
529
- end
530
-
531
- # returns the unit raised to the n-th power. Integers only
532
- def power(n)
533
- raise ArgumentError, "Can only use Integer exponenents" unless Integer === n
534
- return self if n == 1
535
- return Unit("1") if n == 0
536
- return self.inverse if n == -1
537
- if n > 0 then
538
- (1..n.to_i).inject(Unit.new("1")) {|product, x| product * self}
539
- else
540
- (1..-n.to_i).inject(Unit.new("1")) {|product, x| product / self}
541
- end
542
- end
543
-
544
- # Calculates the n-th root of a unit, where n = (1..9)
545
- # if n < 0, returns 1/unit^(1/n)
546
- def root(n)
547
- raise ArgumentError, "Exponent must an Integer" unless Integer === n
548
- raise ArgumentError, "0th root undefined" if n == 0
549
- return self if n == 1
550
- return self.root(n.abs).inverse if n < 0
551
-
552
- vec = self.unit_signature_vector
553
- vec=vec.map {|x| x % n}
554
- raise ArgumentError, "Illegal root" unless vec.max == 0
555
- num = @numerator.dup
556
- den = @denominator.dup
557
-
558
- for item in @numerator.uniq do
559
- x = num.find_all {|i| i==item}.size
560
- r = ((x/n)*(n-1)).to_int
561
- r.times {|x| num.delete_at(num.index(item))}
562
- end
563
-
564
- for item in @denominator.uniq do
565
- x = den.find_all {|i| i==item}.size
566
- r = ((x/n)*(n-1)).to_int
567
- r.times {|x| den.delete_at(den.index(item))}
568
- end
569
- q = @scalar < 0 ? (-1)**Rational(1,n) * (@scalar.abs)**Rational(1,n) : @scalar**Rational(1,n)
570
- Unit.new(:scalar=>q,:numerator=>num,:denominator=>den)
571
- end
572
-
573
- # returns inverse of Unit (1/unit)
574
- def inverse
575
- Unit("1") / self
576
- end
577
-
578
- # convert to a specified unit string or to the same units as another Unit
579
- #
580
- # unit >> "kg" will covert to kilograms
581
- # unit1 >> unit2 converts to same units as unit2 object
582
- #
583
- # To convert a Unit object to match another Unit object, use:
584
- # unit1 >>= unit2
585
- # Throws an exception if the requested target units are incompatible with current Unit.
586
- #
587
- # Special handling for temperature conversions is supported. If the Unit object is converted
588
- # from one temperature unit to another, the proper temperature offsets will be used.
589
- # Supports Kelvin, Celcius, Farenheit, and Rankine scales.
590
- #
591
- # Note that if temperature is part of a compound unit, the temperature will be treated as a differential
592
- # and the units will be scaled appropriately.
593
- def to(other)
594
- return self if other.nil?
595
- return self if TrueClass === other
596
- return self if FalseClass === other
597
- if (Unit === other && other.units =~ /temp(K|C|R|F)/) || (String === other && other =~ /temp(K|C|R|F)/)
598
- raise ArgumentError, "Receiver is not a temperature unit" unless self.signature==400
599
- start_unit = self.units
600
- target_unit = other.units rescue other
601
- q=case start_unit
602
- when 'degC':
603
- case target_unit
604
- when 'tempC' : @scalar
605
- when 'tempK' : @scalar + 273.15
606
- when 'tempF' : @scalar * (9.0/5.0) + 32.0
607
- when 'tempR' : @scalar * (9.0/5.0) + 491.67
608
- end
609
- when 'degK':
610
- case target_unit
611
- when 'tempC' : @scalar - 273.15
612
- when 'tempK' : @scalar
613
- when 'tempF' : @scalar * (9.0/5.0) - 459.67
614
- when 'tempR' : @scalar * (9.0/5.0)
615
- end
616
- when 'degF':
617
- case target_unit
618
- when 'tempC' : (@scalar-32)*(5.0/9.0)
619
- when 'tempK' : (@scalar+459.67)*(5.0/9.0)
620
- when 'tempF' : @scalar
621
- when 'tempR' : @scalar + 459.67
622
- end
623
- when 'degR':
624
- case target_unit
625
- when 'tempC' : @scalar*(5.0/9.0) -273.15
626
- when 'tempK' : @scalar*(5.0/9.0)
627
- when 'tempF' : @scalar - 459.67
628
- when 'tempR' : @scalar
629
- end
630
- else
631
- return self.to_base.to(other) unless self.is_base?
632
- #raise ArgumentError, "Unknown temperature conversion requested #{self.numerator}"
633
- end
634
- target_unit =~ /temp(C|K|F|R)/
635
- Unit.new("#{q} deg#{$1}")
636
- else
637
- case other
638
- when Unit:
639
- return self if other.units == self.units
640
- target = other
641
- when String: target = Unit.new(other)
642
- else
643
- raise ArgumentError, "Unknown target units"
644
- end
645
- raise ArgumentError, "Incompatible Units" unless self =~ target
646
- one = @numerator.map {|x| @@PREFIX_VALUES[x] ? @@PREFIX_VALUES[x] : x}.map {|i| i.kind_of?(Numeric) ? i : @@UNIT_VALUES[i][:scalar] }.compact
647
- two = @denominator.map {|x| @@PREFIX_VALUES[x] ? @@PREFIX_VALUES[x] : x}.map {|i| i.kind_of?(Numeric) ? i : @@UNIT_VALUES[i][:scalar] }.compact
648
- v = one.inject(1) {|product,n| product*n} / two.inject(1) {|product,n| product*n}
649
- one = target.numerator.map {|x| @@PREFIX_VALUES[x] ? @@PREFIX_VALUES[x] : x}.map {|x| x.kind_of?(Numeric) ? x : @@UNIT_VALUES[x][:scalar] }.compact
650
- two = target.denominator.map {|x| @@PREFIX_VALUES[x] ? @@PREFIX_VALUES[x] : x}.map {|x| x.kind_of?(Numeric) ? x : @@UNIT_VALUES[x][:scalar] }.compact
651
- y = one.inject(1) {|product,n| product*n} / two.inject(1) {|product,n| product*n}
652
- q = @scalar * v/y
653
- Unit.new(:scalar=>q, :numerator=>target.numerator, :denominator=>target.denominator, :signature => target.signature)
654
- end
655
- end
656
- alias :>> :to
657
- alias :convert_to :to
658
-
659
- # converts the unit back to a float if it is unitless. Otherwise raises an exception
660
- def to_f
661
- return @scalar.to_f if self.unitless?
662
- raise RuntimeError, "Can't convert to Float unless unitless. Use Unit#scalar"
663
- end
664
-
665
- # converts the unit back to a complex if it is unitless. Otherwise raises an exception
666
-
667
- def to_c
668
- return Complex(@scalar) if self.unitless?
669
- raise RuntimeError, "Can't convert to Complex unless unitless. Use Unit#scalar"
670
- end
671
-
672
- # returns the 'unit' part of the Unit object without the scalar
673
- def units
674
- return "" if @numerator == UNITY_ARRAY && @denominator == UNITY_ARRAY
675
- return @unit_name unless @unit_name.nil?
676
- output_n = []
677
- output_d =[]
678
- num = @numerator.clone.compact
679
- den = @denominator.clone.compact
680
- if @numerator == UNITY_ARRAY
681
- output_n << "1"
682
- else
683
- num.each_with_index do |token,index|
684
- if token && @@PREFIX_VALUES[token] then
685
- output_n << "#{@@OUTPUT_MAP[token]}#{@@OUTPUT_MAP[num[index+1]]}"
686
- num[index+1]=nil
687
- else
688
- output_n << "#{@@OUTPUT_MAP[token]}" if token
689
- end
690
- end
691
- end
692
- if @denominator == UNITY_ARRAY
693
- output_d = ['1']
694
- else
695
- den.each_with_index do |token,index|
696
- if token && @@PREFIX_VALUES[token] then
697
- output_d << "#{@@OUTPUT_MAP[token]}#{@@OUTPUT_MAP[den[index+1]]}"
698
- den[index+1]=nil
699
- else
700
- output_d << "#{@@OUTPUT_MAP[token]}" if token
701
- end
702
- end
703
- end
704
- on = output_n.reject {|x| x.empty?}.map {|x| [x, output_n.find_all {|z| z==x}.size]}.uniq.map {|x| ("#{x[0]}".strip+ (x[1] > 1 ? "^#{x[1]}" : ''))}
705
- od = output_d.reject {|x| x.empty?}.map {|x| [x, output_d.find_all {|z| z==x}.size]}.uniq.map {|x| ("#{x[0]}".strip+ (x[1] > 1 ? "^#{x[1]}" : ''))}
706
- out = "#{on.join('*')}#{od == ['1'] ? '': '/'+od.join('*')}".strip
707
- @unit_name = out unless self.kind == :temperature
708
- return out
709
- end
710
-
711
- # negates the scalar of the Unit
712
- def -@
713
- return -@scalar if self.unitless?
714
- #Unit.new(-@scalar,@numerator,@denominator)
715
- -1 * self.dup
716
- end
717
-
718
- # returns abs of scalar, without the units
719
- def abs
720
- return @scalar.abs
721
- end
722
-
723
- def ceil
724
- return @scalar.ceil if self.unitless?
725
- Unit.new(@scalar.ceil, @numerator, @denominator)
726
- end
727
-
728
- def floor
729
- return @scalar.floor if self.unitless?
730
- Unit.new(@scalar.floor, @numerator, @denominator)
731
- end
732
-
733
- # if unitless, returns an int, otherwise raises an error
734
- def to_i
735
- return @scalar.to_int if self.unitless?
736
- raise RuntimeError, 'Cannot convert to Integer unless unitless'
737
- end
738
- alias :to_int :to_i
739
-
740
- # Tries to make a Time object from current unit. Assumes the current unit hold the duration in seconds from the epoch.
741
- def to_time
742
- Time.at(self)
743
- end
744
- alias :time :to_time
745
-
746
- def truncate
747
- return @scalar.truncate if self.unitless?
748
- Unit.new(@scalar.truncate, @numerator, @denominator)
749
- end
750
-
751
- # convert a duration to a DateTime. This will work so long as the duration is the duration from the zero date
752
- # defined by DateTime
753
- def to_datetime
754
- DateTime.new(self.to('d').scalar)
755
- end
756
-
757
- def round
758
- return @scalar.round if self.unitless?
759
- Unit.new(@scalar.round, @numerator, @denominator)
760
- end
761
-
762
- # true if scalar is zero
763
- def zero?
764
- return @scalar.zero?
765
- end
766
-
767
- # '5 min'.unit.ago
768
- def ago
769
- self.before
770
- end
771
-
772
- # '5 min'.before(time)
773
- def before(time_point = ::Time.now)
774
- raise ArgumentError, "Must specify a Time" unless time_point
775
- if String === time_point
776
- time_point.time - self rescue time_point.datetime - self
777
- else
778
- time_point - self rescue time_point.to_datetime - self
779
- end
780
- end
781
- alias :before_now :before
782
-
783
- # 'min'.since(time)
784
- def since(time_point = ::Time.now)
785
- case time_point
786
- when Time: (Time.now - time_point).unit('s').to(self)
787
- when DateTime, Date: (DateTime.now - time_point).unit('d').to(self)
788
- when String:
789
- (DateTime.now - time_point.time(:context=>:past)).unit('d').to(self)
790
- else
791
- raise ArgumentError, "Must specify a Time, DateTime, or String"
792
- end
793
- end
794
-
795
- # 'min'.until(time)
796
- def until(time_point = ::Time.now)
797
- case time_point
798
- when Time: (time_point - Time.now).unit('s').to(self)
799
- when DateTime, Date: (time_point - DateTime.now).unit('d').to(self)
800
- when String:
801
- r = (time_point.time(:context=>:future) - DateTime.now)
802
- Time === time_point.time ? r.unit('s').to(self) : r.unit('d').to(self)
803
- else
804
- raise ArgumentError, "Must specify a Time, DateTime, or String"
805
- end
806
- end
807
-
808
- # '5 min'.from(time)
809
- def from(time_point = ::Time.now)
810
- raise ArgumentError, "Must specify a Time" unless time_point
811
- if String === time_point
812
- time_point.time + self rescue time_point.datetime + self
813
- else
814
- time_point + self rescue time_point.to_datetime + self
815
- end
816
- end
817
- alias :after :from
818
- alias :from_now :from
819
-
820
- # returns next unit in a range. '1 mm'.unit.succ #=> '2 mm'.unit
821
- # only works when the scalar is an integer
822
- def succ
823
- raise ArgumentError, "Non Integer Scalar" unless @scalar == @scalar.to_i
824
- q = @scalar.to_i.succ
825
- Unit.new(q, @numerator, @denominator)
826
- end
827
-
828
- # automatically coerce objects to units when possible
829
- # if an object defines a 'to_unit' method, it will be coerced using that method
830
- def coerce(other)
831
- if other.respond_to? :to_unit
832
- return [other.to_unit, self]
833
- end
834
- case other
835
- when Unit : [other, self]
836
- else
837
- [Unit.new(other), self]
838
- end
839
- end
840
-
841
- # Protected and Private Functions that should only be called from this class
842
- protected
843
-
844
-
845
- def update_base_scalar
846
- return @base_scalar unless @base_scalar.nil?
847
- if self.is_base?
848
- @base_scalar = @scalar
849
- @signature = unit_signature
850
- else
851
- base = self.to_base
852
- @base_scalar = base.scalar
853
- @signature = base.signature
854
- end
855
- end
856
-
857
-
858
-
859
-
860
- # calculates the unit signature vector used by unit_signature
861
- def unit_signature_vector
862
- return self.to_base.unit_signature_vector unless self.is_base?
863
- result = self
864
- vector = Array.new(SIGNATURE_VECTOR.size,0)
865
- for element in @numerator
866
- if r=@@ALL_UNIT_DEFINITIONS[element]
867
- n = SIGNATURE_VECTOR.index(r[2])
868
- vector[n] = vector[n] + 1 if n
869
- end
870
- end
871
- for element in @denominator
872
- if r=@@ALL_UNIT_DEFINITIONS[element]
873
- n = SIGNATURE_VECTOR.index(r[2])
874
- vector[n] = vector[n] - 1 if n
875
- end
876
- end
877
- vector
878
- end
879
-
880
- def replace_temperature
881
- return self unless self.kind == :temperature && self.units =~ /temp(R|K|F|C)/
882
- un = $1
883
- @numerator = case un
884
- when 'R' : RANKINE
885
- when 'C' : CELCIUS
886
- when 'F' : FARENHEIT
887
- when 'K' : KELVIN
888
- end
889
- @unit_name = nil
890
- r= self.to("tempK")
891
- copy(r)
892
- end
893
-
894
- private
895
-
896
- def initialize_copy(other)
897
- @numerator = other.numerator.dup
898
- @denominator = other.denominator.dup
899
-
900
- end
901
-
902
- # calculates the unit signature id for use in comparing compatible units and simplification
903
- # the signature is based on a simple classification of units and is based on the following publication
904
- #
905
- # Novak, G.S., Jr. "Conversion of units of measurement", IEEE Transactions on Software Engineering,
906
- # 21(8), Aug 1995, pp.651-661
907
- # doi://10.1109/32.403789
908
- # 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.
909
- #
910
- def unit_signature
911
- return @signature unless @signature.nil?
912
- vector = unit_signature_vector
913
- vector.each_with_index {|item,index| vector[index] = item * 20**index}
914
- @signature=vector.inject(0) {|sum,n| sum+n}
915
- end
916
-
917
- def self.eliminate_terms(q, n, d)
918
- num = n.dup
919
- den = d.dup
920
-
921
- num.delete_if {|v| v == UNITY}
922
- den.delete_if {|v| v == UNITY}
923
- combined = Hash.new(0)
924
-
925
- i = 0
926
- loop do
927
- break if i > num.size
928
- if @@PREFIX_VALUES.has_key? num[i]
929
- k = [num[i],num[i+1]]
930
- i += 2
931
- else
932
- k = num[i]
933
- i += 1
934
- end
935
- combined[k] += 1 unless k.nil? || k == UNITY
936
- end
937
-
938
- j = 0
939
- loop do
940
- break if j > den.size
941
- if @@PREFIX_VALUES.has_key? den[j]
942
- k = [den[j],den[j+1]]
943
- j += 2
944
- else
945
- k = den[j]
946
- j += 1
947
- end
948
- combined[k] -= 1 unless k.nil? || k == UNITY
949
- end
950
-
951
- num = []
952
- den = []
953
- for key, value in combined do
954
- case
955
- when value > 0 : value.times {num << key}
956
- when value < 0 : value.abs.times {den << key}
957
- end
958
- end
959
- num = UNITY_ARRAY if num.empty?
960
- den = UNITY_ARRAY if den.empty?
961
- {:scalar=>q, :numerator=>num.flatten.compact, :denominator=>den.flatten.compact}
962
- end
963
-
964
-
965
- # parse a string into a unit object.
966
- # Typical formats like :
967
- # "5.6 kg*m/s^2"
968
- # "5.6 kg*m*s^-2"
969
- # "5.6 kilogram*meter*second^-2"
970
- # "2.2 kPa"
971
- # "37 degC"
972
- # "1" -- creates a unitless constant with value 1
973
- # "GPa" -- creates a unit with scalar 1 with units 'GPa'
974
- # 6'4" -- recognized as 6 feet + 4 inches
975
- # 8 lbs 8 oz -- recognized as 8 lbs + 8 ounces
976
- def parse(passed_unit_string="0")
977
- unit_string = passed_unit_string.dup
978
- if unit_string =~ /\$\s*(#{NUMBER_REGEX})/
979
- unit_string = "#{$1} USD"
980
- end
981
-
982
-
983
- unit_string.gsub!(/%/,'percent')
984
- unit_string.gsub!(/'/,'feet')
985
- unit_string.gsub!(/"/,'inch')
986
- unit_string.gsub!(/#/,'pound')
987
- if defined?(Uncertain) && unit_string =~ /(\+\/-|&plusmn;)/
988
- value, uncertainty, unit_s = unit_string.scan(UNCERTAIN_REGEX)[0]
989
- result = unit_s.unit * Uncertain(value.to_f,uncertainty.to_f)
990
- copy(result)
991
- return
992
- end
993
-
994
- if defined?(Complex) && unit_string =~ COMPLEX_NUMBER
995
- real, imaginary, unit_s = unit_string.scan(COMPLEX_REGEX)[0]
996
- result = Unit(unit_s || '1') * Complex(real.to_f,imaginary.to_f)
997
- copy(result)
998
- return
999
- end
1000
-
1001
- if defined?(Rational) && unit_string =~ RATIONAL_NUMBER
1002
- numerator, denominator, unit_s = unit_string.scan(RATIONAL_REGEX)[0]
1003
- result = Unit(unit_s || '1') * Rational(numerator.to_i,denominator.to_i)
1004
- copy(result)
1005
- return
1006
- end
1007
-
1008
- unit_string =~ NUMBER_REGEX
1009
- unit = @@cached_units[$2]
1010
- mult = ($1.empty? ? 1.0 : $1.to_f) rescue 1.0
1011
- if unit
1012
- copy(unit)
1013
- @scalar *= mult
1014
- @base_scalar *= mult
1015
- return self
1016
- end
1017
-
1018
- unit_string.gsub!(/[<>]/,"")
1019
-
1020
- if unit_string =~ /:/
1021
- hours, minutes, seconds, microseconds = unit_string.scan(TIME_REGEX)[0]
1022
- raise ArgumentError, "Invalid Duration" if [hours, minutes, seconds, microseconds].all? {|x| x.nil?}
1023
- result = "#{hours || 0} h".unit +
1024
- "#{minutes || 0} minutes".unit +
1025
- "#{seconds || 0} seconds".unit +
1026
- "#{microseconds || 0} usec".unit
1027
- copy(result)
1028
- return
1029
- end
1030
-
1031
-
1032
- # Special processing for unusual unit strings
1033
- # feet -- 6'5"
1034
- feet, inches = unit_string.scan(FEET_INCH_REGEX)[0]
1035
- if (feet && inches)
1036
- result = Unit.new("#{feet} ft") + Unit.new("#{inches} inches")
1037
- copy(result)
1038
- return
1039
- end
1040
-
1041
- # weight -- 8 lbs 12 oz
1042
- pounds, oz = unit_string.scan(LBS_OZ_REGEX)[0]
1043
- if (pounds && oz)
1044
- result = Unit.new("#{pounds} lbs") + Unit.new("#{oz} oz")
1045
- copy(result)
1046
- return
1047
- end
1048
-
1049
- raise( ArgumentError, "'#{passed_unit_string}' Unit not recognized") if unit_string.count('/') > 1
1050
- raise( ArgumentError, "'#{passed_unit_string}' Unit not recognized") if unit_string.scan(/\s\d+\S*/).size > 0
1051
-
1052
- @scalar, top, bottom = unit_string.scan(UNIT_STRING_REGEX)[0] #parse the string into parts
1053
-
1054
- top.scan(TOP_REGEX).each do |item|
1055
- n = item[1].to_i
1056
- x = "#{item[0]} "
1057
- case
1058
- when n>=0 : top.gsub!(/#{item[0]}(\^|\*\*)#{n}/) {|s| x * n}
1059
- when n<0 : bottom = "#{bottom} #{x * -n}"; top.gsub!(/#{item[0]}(\^|\*\*)#{n}/,"")
1060
- end
1061
- end
1062
- bottom.gsub!(BOTTOM_REGEX) {|s| "#{$1} " * $2.to_i} if bottom
1063
- @scalar = @scalar.to_f unless @scalar.nil? || @scalar.empty?
1064
- @scalar = 1 unless @scalar.kind_of? Numeric
1065
-
1066
- @numerator ||= UNITY_ARRAY
1067
- @denominator ||= UNITY_ARRAY
1068
- @numerator = top.scan(@@UNIT_MATCH_REGEX).delete_if {|x| x.empty?}.compact if top
1069
- @denominator = bottom.scan(@@UNIT_MATCH_REGEX).delete_if {|x| x.empty?}.compact if bottom
1070
- us = "#{(top || '' + bottom || '')}".to_s.gsub(@@UNIT_MATCH_REGEX,'').gsub(/[\d\*, "'_^\/\$]/,'')
1071
-
1072
- raise( ArgumentError, "'#{passed_unit_string}' Unit not recognized") unless us.empty?
1073
-
1074
- @numerator = @numerator.map do |item|
1075
- @@PREFIX_MAP[item[0]] ? [@@PREFIX_MAP[item[0]], @@UNIT_MAP[item[1]]] : [@@UNIT_MAP[item[1]]]
1076
- end.flatten.compact.delete_if {|x| x.empty?}
1077
-
1078
- @denominator = @denominator.map do |item|
1079
- @@PREFIX_MAP[item[0]] ? [@@PREFIX_MAP[item[0]], @@UNIT_MAP[item[1]]] : [@@UNIT_MAP[item[1]]]
1080
- end.flatten.compact.delete_if {|x| x.empty?}
1081
-
1082
- @numerator = UNITY_ARRAY if @numerator.empty?
1083
- @denominator = UNITY_ARRAY if @denominator.empty?
1084
- self
1085
- end
1086
- end
1087
-
1088
-
1089
- # Allow date objects to do offsets by a time unit
1090
- # Date.today + U"1 week" => gives today+1 week
1091
- class Date
1092
- alias :unit_date_add :+
1093
- def +(unit)
1094
- case unit
1095
- when Unit:
1096
- unit = unit.to('d').round if ['y', 'decade', 'century'].include? unit.units
1097
- unit_date_add(unit.to('day').scalar)
1098
- when Time: unit_date_add(unit.to_datetime)
1099
- else
1100
- unit_date_add(unit)
1101
- end
1102
- end
1103
-
1104
- alias :unit_date_sub :-
1105
- def -(unit)
1106
- case unit
1107
- when Unit:
1108
- unit = unit.to('d').round if ['y', 'decade', 'century'].include? unit.units
1109
- unit_date_sub(unit.to('day').scalar)
1110
- when Time: unit_date_sub(unit.to_datetime)
1111
- else
1112
- unit_date_sub(unit)
1113
- end
1114
- end
1115
-
1116
- def to_unit(other = nil)
1117
- other ? Unit.new(self).to(other) : Unit.new(self)
1118
- end
1119
- alias :unit :to_unit
1120
-
1121
- def to_time
1122
- Time.local(*ParseDate.parsedate(self.to_s))
1123
- end
1124
-
1125
- alias :units_datetime_inspect :inspect
1126
- def inspect(raw = false)
1127
- return self.units_datetime_inspect if raw
1128
- self.to_s
1129
- end
1130
-
1131
- def to_date
1132
- Date.civil(self.year, self.month, self.day)
1133
- end
1134
-
1135
- end
1136
-
1137
- class Object
1138
- def Unit(*other)
1139
- other.to_unit
1140
- end
1141
-
1142
- alias :U :Unit
1143
- alias :u :Unit
1144
- end
1145
-
1146
- # make a unitless unit with a given scalar
1147
- class Numeric
1148
- def to_unit(other = nil)
1149
- other ? Unit.new(self, other) : Unit.new(self)
1150
- end
1151
- alias :unit :to_unit
1152
- alias :u :to_unit
1153
- end
1154
-
1155
- # make a unit from an array
1156
- # [1, 'mm'].unit => 1 mm
1157
- class Array
1158
- def to_unit(other = nil)
1159
- other ? Unit.new(self).to(other) : Unit.new(self)
1160
- end
1161
- alias :unit :to_unit
1162
- alias :u :to_unit
1163
- end
1164
-
1165
- # make a string into a unit
1166
- class String
1167
- def to_unit(other = nil)
1168
- other ? Unit.new(self).to(other) : Unit.new(self)
1169
- end
1170
- alias :unit :to_unit
1171
- alias :u :to_unit
1172
- alias :unit_format :%
1173
-
1174
- # format unit output using formating codes '%0.2f' % '1 mm'.unit => '1.00 mm'
1175
- def %(*args)
1176
- case args[0]
1177
- when Unit: args[0].to_s(self)
1178
- when Complex: args[0].to_s
1179
- else
1180
- unit_format(*args)
1181
- end
1182
- end
1183
-
1184
- #needed for compatibility with Rails, which defines a String.from method
1185
- if self.public_instance_methods.include? 'from'
1186
- alias :old_from :from
1187
- end
1188
-
1189
- def from(time_point = ::Time.now)
1190
- return old_from(time_point) if Integer === time_point
1191
- self.unit.from(time_point)
1192
- end
1193
-
1194
- alias :after :from
1195
- alias :from_now :from
1196
-
1197
- def ago
1198
- self.unit.ago
1199
- end
1200
-
1201
- def before(time_point = ::Time.now)
1202
- self.unit.before(time_point)
1203
- end
1204
- alias :before_now :before
1205
-
1206
- def since(time_point = ::Time.now)
1207
- self.unit.since(time_point)
1208
- end
1209
-
1210
- def until(time_point = ::Time.now)
1211
- self.unit.until(time_point)
1212
- end
1213
-
1214
- def to(other)
1215
- self.unit.to(other)
1216
- end
1217
-
1218
- def time(options = {})
1219
- self.to_time(options) rescue self.to_datetime(options)
1220
- end
1221
-
1222
- def to_time(options = {})
1223
- begin
1224
- #raises exception when Chronic not defined or when it returns a nil (i.e., can't parse the input)
1225
- r = Chronic.parse(self,options)
1226
- raise(ArgumentError, 'Invalid Time String') unless r
1227
- return r
1228
- rescue
1229
- Time.local(*ParseDate.parsedate(self))
1230
- end
1231
- end
1232
-
1233
- def to_datetime(options = {})
1234
- begin
1235
- # raises an exception if Chronic.parse = nil or if Chronic not defined
1236
- r = Chronic.parse(self,options).to_datetime
1237
- rescue
1238
- r=DateTime.civil(*ParseDate.parsedate(self)[0..5].compact)
1239
- end
1240
- raise RuntimeError, "Invalid Time String" if r == DateTime.new
1241
- return r
1242
- end
1243
-
1244
- def to_date(options={})
1245
- begin
1246
- r = Chronic.parse(self,options).to_date
1247
- rescue
1248
- r = Date.civil(*ParseDate.parsedate(self)[0..5].compact)
1249
- end
1250
- raise RuntimeError, 'Invalid Date String' if r == Date.new
1251
- return r
1252
- end
1253
-
1254
- def datetime(options = {})
1255
- self.to_datetime(options) rescue self.to_time(options)
1256
- end
1257
- end
1258
-
1259
-
1260
- #
1261
- # Time math is handled slightly differently. The difference is considered to be an exact duration if
1262
- # the subtracted value is in hours, minutes, or seconds. It is rounded to the nearest day if the offset
1263
- # is in years, decades, or centuries. This leads to less precise values, but ones that match the
1264
- # calendar better.
1265
- class Time
1266
-
1267
- class << self
1268
- alias unit_time_at at
1269
- end
1270
-
1271
- def self.at(*args)
1272
- if Unit === args[0]
1273
- unit_time_at(args[0].to("s").scalar)
1274
- else
1275
- unit_time_at(*args)
1276
- end
1277
- end
1278
-
1279
- def to_unit(other = nil)
1280
- other ? Unit.new(self).to(other) : Unit.new(self)
1281
- end
1282
- alias :unit :to_unit
1283
- alias :u :to_unit
1284
- alias :unit_add :+
1285
-
1286
- def to_datetime
1287
- DateTime.civil(1970,1,1)+(self.to_f+self.gmt_offset)/86400
1288
- end
1289
-
1290
- def to_date
1291
- Date.civil(1970,1,1)+(self.to_f+self.gmt_offset)/86400
1292
- end
1293
-
1294
- def +(other)
1295
- case other
1296
- when Unit:
1297
- other = other.to('d').round.to('s') if ['y', 'decade', 'century'].include? other.units
1298
- begin
1299
- unit_add(other.to('s').scalar)
1300
- rescue RangeError
1301
- self.to_datetime + other
1302
- end
1303
- when DateTime: unit_add(other.to_time)
1304
- else
1305
- unit_add(other)
1306
- end
1307
- end
1308
-
1309
- # usage: Time.in '5 min'
1310
- def self.in(duration)
1311
- Time.now + duration.to_unit
1312
- end
1313
-
1314
- alias :unit_sub :-
1315
-
1316
- def -(other)
1317
- case other
1318
- when Unit:
1319
- other = other.to('d').round.to('s') if ['y', 'decade', 'century'].include? other.units
1320
- begin
1321
- unit_sub(other.to('s').scalar)
1322
- rescue RangeError
1323
- self.to_datetime - other
1324
- end
1325
-
1326
- when DateTime: unit_sub(other.to_time)
1327
- else
1328
- unit_sub(other)
1329
- end
1330
- end
1331
- end
1332
-
1333
- # Math will convert unit objects to radians and then attempt to use the value for
1334
- # trigonometric functions.
1335
- module Math
1336
- alias unit_sqrt sqrt
1337
- def sqrt(n)
1338
- Unit === n ? n**(1/2) : unit_sqrt(n)
1339
- end
1340
-
1341
- alias unit_sin sin
1342
- def sin(n)
1343
- Unit === n ? unit_sin(n.to('radian').scalar) : unit_sin(n)
1344
- end
1345
-
1346
- alias unit_cos cos
1347
- def cos(n)
1348
- Unit === n ? unit_cos(n.to('radian').scalar) : unit_cos(n)
1349
- end
1350
-
1351
- alias unit_sinh sinh
1352
- def sinh(n)
1353
- Unit === n ? unit_sinh(n.to('radian').scalar) : unit_sinh(n)
1354
- end
1355
-
1356
- alias unit_cosh cosh
1357
- def cosh(n)
1358
- Unit === n ? unit_cosh(n.to('radian').scalar) : unit_cosh(n)
1359
- end
1360
-
1361
- alias unit_tan tan
1362
- def tan(n)
1363
- Unit === n ? unit_tan(n.to('radian').scalar) : unit_tan(n)
1364
- end
1365
-
1366
- alias unit_tanh tanh
1367
- def tanh(n)
1368
- Unit === n ? unit_tanh(n.to('radian').scalar) : unit_tanh(n)
1369
- end
1370
-
1371
- alias unit_hypot hypot
1372
- # Convert parameters to consistent units and perform the function
1373
- def hypot(x,y)
1374
- if Unit === x && Unit === y
1375
- (x**2 + y**2)**(1/2)
1376
- else
1377
- unit_hypot(x,y)
1378
- end
1379
- end
1380
-
1381
- alias unit_atan2 atan2
1382
- def atan2(x,y)
1383
- case
1384
- when (Unit === x && Unit === y) && (x !~ y)
1385
- raise ArgumentError, "Incompatible Units"
1386
- when (Unit === x && Unit === y) && (x =~ y)
1387
- unit_atan2(x.base_scalar, y.base_scalar)
1388
- else
1389
- unit_atan2(x,y)
1390
- end
1391
- end
1392
-
1393
- module_function :unit_hypot
1394
- module_function :hypot
1395
- module_function :unit_sqrt
1396
- module_function :sqrt
1397
- module_function :unit_sin
1398
- module_function :sin
1399
- module_function :unit_cos
1400
- module_function :cos
1401
- module_function :unit_sinh
1402
- module_function :sinh
1403
- module_function :unit_cosh
1404
- module_function :cosh
1405
- module_function :unit_tan
1406
- module_function :tan
1407
- module_function :unit_tanh
1408
- module_function :tanh
1409
- module_function :unit_atan2
1410
- module_function :atan2
1411
-
1412
- end
1413
-
1414
- Unit.setup