ruby-units 1.0.1 → 1.0.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/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