11# Ruby Units
22
3- [ ![ Maintainability] ( https://api.codeclimate.com/v1/badges/4e858d14a07dd453f748/maintainability.svg )] ( https://codeclimate.com/github/olbrich/ruby-units/maintainability )
4- [ ![ CodeClimate Status] ( https://api.codeclimate.com/v1/badges/4e858d14a07dd453f748/test_coverage.svg )] ( https://codeclimate.com/github/olbrich/ruby-units/test_coverage )
5- [ ![ FOSSA Status] ( https://app.fossa.io/api/projects/git%2Bgithub.com%2Folbrich%2Fruby-units.svg?type=shield )] ( https://app.fossa.io/projects/git%2Bgithub.com%2Folbrich%2Fruby-units?ref=badge_shield )
6-
73Kevin C. Olbrich, Ph.D.
84
95Project page:
@@ -146,21 +142,89 @@ Unit.new("100 kg").to_s(:lbs) # returns 220 lbs, 7 oz
146142Unit .new (" 100 kg" ).to_s(:stone ) # returns 15 stone, 10 lb
147143```
148144
145+ You can also use Ruby's standard string formatting operator (` % ` ) with units:
146+
147+ ``` ruby
148+ ' %0.2f' % ' 1 mm' .to_unit # "1.00 mm"
149+ ' %0.2f in' % ' 1 mm' .to_unit # "0.04 in" - format and convert
150+ ' %.2e' % ' 1000 m' .to_unit # "1.00e+03 m" - scientific notation
151+ ```
152+
153+ Strings can be converted to units using the ` convert_to ` method:
154+
155+ ``` ruby
156+ ' 10 mm' .convert_to(' cm' ) # converts directly to centimeters
157+ ' 100 km/h' .convert_to(' m/s' ) # converts compound units
158+ ```
159+
149160### Time Helpers
150161
151- ` Time ` , ` Date ` , and ` DateTime ` objects can have time units added or subtracted.
162+ Ruby-units extends the ` Time ` , ` Date ` , and ` DateTime ` classes to support unit-based arithmetic,
163+ allowing you to add or subtract durations from time objects naturally.
164+
165+ #### Adding and Subtracting Durations
152166
153167``` ruby
154- Time .now + Unit .new (" 10 min" )
168+ Time .now + Unit .new (" 10 min" ) # => 10 minutes from now
169+ Time .now - Unit .new (" 2 hours" ) # => 2 hours ago
170+
171+ Date .today + Unit .new (" 1 week" ) # => 7 days from today
172+ Date .today - Unit .new (" 30 days" ) # => 30 days ago
155173```
156174
157- Several helpers have also been defined. Note: If you include the 'Chronic' gem,
158- you can specify times in natural language.
175+ ** Important:** When adding or subtracting large time units (years, decades, centuries),
176+ the duration is first converted to days and rounded to maintain calendar accuracy.
177+ This means ` 1 year ` is treated as approximately 365 days rather than an exact number of seconds.
159178
160179``` ruby
161- Unit .new (' min' ).since(DateTime .parse(' 9/18/06 3:00pm' ))
180+ Time .now + Unit .new (" 1 year" ) # => Approximately 365 days from now
181+ Time .now - Unit .new (" 1 decade" ) # => Approximately 3650 days ago
182+ ```
183+
184+ For more precise durations, use smaller units (hours, minutes, seconds):
185+
186+ ``` ruby
187+ Time .now + Unit .new (" 24 hours" ) # => Exactly 24 hours from now
188+ ```
189+
190+ #### Converting Time and Date to Units
191+
192+ You can convert ` Time ` objects to units representing the duration since the Unix epoch:
193+
194+ ``` ruby
195+ Time .now.to_unit # => Duration in seconds since epoch
196+ Time .now.to_unit(' hours' ) # => Duration in hours since epoch
197+ Time .now.to_unit(' days' ) # => Duration in days since epoch
198+ ```
199+
200+ You can convert ` Date ` objects to units representing days since the Julian calendar start:
201+
202+ ``` ruby
203+ Date .today.to_unit # => Duration in days since Julian calendar start
204+ Date .today.to_unit(' week' ) # => Duration in weeks since Julian calendar start
205+ Date .today.to_unit(' year' ) # => Duration in years since Julian calendar start
206+ ```
207+
208+ #### Creating Time from Units
209+
210+ Use ` Time.at ` to create a Time object from a duration unit:
211+
212+ ``` ruby
213+ Time .at(Unit .new (" 1000 seconds" )) # => Time 1000 seconds after epoch
214+ Time .at(Unit .new (" 1 hour" ), 500 , :ms ) # => Time 1 hour + 500 milliseconds after epoch
162215```
163216
217+ #### Convenience Methods
218+
219+ The ` Time.in ` method provides a shorthand for calculating future times:
220+
221+ ``` ruby
222+ Time .in(' 5 min' ) # => 5 minutes from now
223+ Time .in(' 2 hours' ) # => 2 hours from now
224+ ```
225+
226+ #### Duration Formats
227+
164228Durations may be entered as 'HH:MM: SS , usec' and will be returned in 'hours'.
165229
166230``` ruby
@@ -172,6 +236,21 @@ Unit.new('0:30:30') #=> 0.5 h + 30 sec
172236If only one ":" is present, it is interpreted as the separator between hours and
173237minutes.
174238
239+ #### Compatibility with Chronic
240+
241+ Several helpers are available for working with natural language time parsing.
242+ Note: If you include the 'Chronic' gem, you can specify times in natural language.
243+
244+ ``` ruby
245+ Unit .new (' min' ).since(DateTime .parse(' 9/18/06 3:00pm' ))
246+ ```
247+
248+ #### Range Errors and DateTime Fallback
249+
250+ If time arithmetic would result in a date outside the valid range for the ` Time ` class
251+ (typically 1970-2038 on 32-bit systems), ruby-units automatically falls back to using
252+ ` DateTime ` objects to handle the calculation.
253+
175254### Ranges
176255
177256``` ruby
@@ -180,10 +259,68 @@ minutes.
180259
181260works so long as the starting point has an integer scalar
182261
183- ### Math functions
262+ ### Math Functions
263+
264+ Ruby-units extends the ` Math ` module to support Unit objects seamlessly. All trigonometric
265+ and mathematical functions work with units, handling conversions automatically.
266+
267+ #### Supported Functions
268+
269+ ** Trigonometric Functions** (angles converted to radians automatically):
270+ - ` sin ` , ` cos ` , ` tan ` - Standard trigonometric functions
271+ - ` sinh ` , ` cosh ` , ` tanh ` - Hyperbolic trigonometric functions
272+
273+ ** Inverse Trigonometric Functions** (return angles in radians as Unit objects):
274+ - ` asin ` , ` acos ` , ` atan ` - Inverse trigonometric functions
275+ - ` atan2 ` - Two-argument arctangent for full quadrant determination
276+
277+ ** Root Functions** (preserve dimensional analysis):
278+ - ` sqrt ` - Square root (e.g., √(4 m²) = 2 m)
279+ - ` cbrt ` - Cube root (e.g., ³√(27 m³) = 3 m)
280+
281+ ** Other Functions** :
282+ - ` hypot ` - Euclidean distance calculation with units
283+ - ` log ` , ` log10 ` - Logarithmic functions (extract scalar from units)
184284
185- All Trig math functions (sin, cos, sinh, hypot...) can take a unit as their
186- parameter. It will be converted to radians and then used if possible.
285+ #### Examples
286+
287+ ``` ruby
288+ # Trigonometric functions with angle units
289+ Math .sin(Unit .new (" 90 deg" )) # => 1.0
290+ Math .cos(Unit .new (" 180 deg" )) # => -1.0
291+ Math .tan(Unit .new (" 45 deg" )) # => 1.0
292+
293+ # Works with different angle units
294+ Math .sin(Unit .new (" 1.571 rad" )) # => 1.0 (approximately π/2)
295+ Math .cos(Unit .new (" 3.14159 rad" )) # => -1.0 (approximately π)
296+
297+ # Inverse functions return Unit objects in radians
298+ Math .asin(0.5 ) # => Unit.new("0.524 rad") (30°)
299+ Math .atan(1 ) # => Unit.new("0.785 rad") (45°)
300+ Math .acos(0 ) # => Unit.new("1.571 rad") (90°)
301+
302+ # Root functions preserve dimensional analysis
303+ Math .sqrt(Unit .new (" 4 m^2" )) # => Unit.new("2 m")
304+ Math .cbrt(Unit .new (" 27 m^3" )) # => Unit.new("3 m")
305+ Math .sqrt(Unit .new (" 9 kg*m/s^2" )) # => Unit.new("3 kg^(1/2)*m^(1/2)/s")
306+
307+ # Hypot for distance calculations (Pythagorean theorem)
308+ Math .hypot(Unit .new (" 3 m" ), Unit .new (" 4 m" )) # => Unit.new("5 m")
309+ Math .hypot(Unit .new (" 30 cm" ), Unit .new (" 40 cm" )) # => Unit.new("50 cm")
310+
311+ # atan2 for converting Cartesian to polar coordinates
312+ Math .atan2(Unit .new (" 1 m" ), Unit .new (" 1 m" )) # => Unit.new("0.785 rad") (45°)
313+ Math .atan2(Unit .new (" 1 m" ), Unit .new (" 0 m" )) # => Unit.new("1.571 rad") (90°)
314+
315+ # Logarithmic functions (units must be compatible for input)
316+ Math .log10(Unit .new (" 100" )) # => 2.0
317+ Math .log(Unit .new (" 2.718" )) # => 1.0 (natural log, approximately)
318+ Math .log(Unit .new (" 8" ), 2 ) # => 3.0 (log base 2)
319+ ```
320+
321+ ** Note:** Trigonometric functions expect angular units or dimensionless numbers. If you pass
322+ a Unit with dimensions (like meters), it will be converted to radians, which may produce
323+ unexpected results.
187324
188325### Temperatures
189326
@@ -238,13 +375,37 @@ It is possible to define new units or redefine existing ones.
238375
239376#### Define New Unit
240377
241- The easiest approach is to define a unit in terms of other units.
378+ The easiest approach is to define a unit in terms of other units using the block form .
242379
243380``` ruby
244381Unit .define(" foobar" ) do |foobar |
245382 foobar.definition = Unit .new (" 1 foo" ) * Unit .new (" 1 bar" ) # anything that results in a Unit object
246- foobar.aliases = %w{foobar fb} # array of synonyms for the unit
247- foobar.display_name = " Foobar" # How unit is displayed when output
383+ foobar.aliases = %w{foobar fb} # array of synonyms for the unit
384+ foobar.display_name = " Foobar" # How unit is displayed when output
385+ end
386+ ```
387+
388+ You can also create a unit definition directly and pass it to ` Unit.define ` :
389+
390+ ``` ruby
391+ unit_definition = Unit ::Definition .new (" foobar" ) do |foobar |
392+ foobar.definition = Unit .new (" 1 baz" )
393+ foobar.aliases = %w{foobar fb}
394+ foobar.display_name = " Foobar"
395+ end
396+ Unit .define(unit_definition)
397+ ```
398+
399+ For more control, you can set the unit attributes explicitly:
400+
401+ ``` ruby
402+ Unit .define(" electron-volt" ) do |ev |
403+ ev.aliases = %w{eV electron-volt electron_volt}
404+ ev.scalar = 1.602e-19
405+ ev.kind = :energy
406+ ev.numerator = %w{<kilogram> <meter> <meter>}
407+ ev.denominator = %w{<second> <second>}
408+ ev.display_name = " electron-volt"
248409end
249410```
250411
@@ -306,14 +467,44 @@ Configuration options can be set like:
306467``` ruby
307468RubyUnits .configure do |config |
308469 config.format = :rational
309- config.separator = false
470+ config.separator = :none
471+ config.default_precision = 0.001
310472end
311473```
312474
313- | Option | Description | Valid Values | Default |
314- | --------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------- | ----------- |
315- | format | Only used for output formatting. ` :rational ` is formatted like ` 3 m/s^2 ` . ` :exponential ` is formatted like ` 3 m*s^-2 ` . | ` :rational, :exponential ` | ` :rational ` |
316- | separator | Use a space separator for output. ` true ` is formatted like ` 3 m/s ` , ` false ` is like ` 3m/s ` . | ` true, false ` | ` true ` |
475+ #### Configuration Options
476+
477+ | Option | Description | Valid Values | Default |
478+ | ---------------------| ------------------------------------------------------------------------------------------------------------------------| -----------------------------| -------------|
479+ | ` format ` | Only used for output formatting. ` :rational ` is formatted like ` 3 m/s^2 ` . ` :exponential ` is formatted like ` 3 m*s^-2 ` . | ` :rational ` , ` :exponential ` | ` :rational ` |
480+ | ` separator ` | Use a space separator for output. ` :space ` is formatted like ` 3 m/s ` , ` :none ` is like ` 3m/s ` . | ` :space ` , ` :none ` | ` :space ` |
481+ | ` default_precision ` | The precision used when rationalizing fractional values in unit output. | Any positive number | ` 0.0001 ` |
482+
483+ #### Examples
484+
485+ ``` ruby
486+ # Change output format to exponential notation
487+ RubyUnits .configure do |config |
488+ config.format = :exponential
489+ end
490+ # => "1 m*s^-2"
491+
492+ # Remove spaces between numbers and units
493+ RubyUnits .configure do |config |
494+ config.separator = :none
495+ end
496+ # => "1m/s"
497+
498+ # Adjust precision for rational number conversion
499+ RubyUnits .configure do |config |
500+ config.default_precision = 0.001
501+ end
502+
503+ # Reset to defaults
504+ RubyUnits .reset
505+ ```
506+
507+ ** Note:** Boolean values (` true ` /` false ` ) for ` separator ` are deprecated but still supported for backward compatibility. Use ` :space ` instead of ` true ` and ` :none ` instead of ` false ` .
317508
318509### NOTES
319510
0 commit comments