diff --git a/.gitignore b/.gitignore index f4ef3fc..0566869 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -/calcpace-* \ No newline at end of file +/calcpace-* +calcpace.gemspec diff --git a/README.md b/README.md index 0eff169..05213ec 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ -# Calcpace [![Gem Version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=rb&r=r&ts=1683906897&type=6e&v=1.0.0&x2=0)](https://badge.fury.io/rb/calcpace) +# Calcpace [![Gem Version](https://badge.fury.io/rb/calcpace.svg)](https://badge.fury.io/rb/calcpace) -Calcpace is a Ruby gem designed to assist with calculations related to running and cycling activities. It can calculate pace, total time, and distance, and also convert distances between miles and kilometers. Results are provided in a readable format, with times in HH:MM:SS and distances in X.X format. +Calcpace is a Ruby gem that helps with calculations related to running/cycling activities or general purposes involving distance and time. It can calculate pace, total time, and distance, accepting time in seconds or HH:MM:SS format. It also converts distances between miles and kilometers. The results are provided in a readable format, with times in HH:MM:SS or seconds and distances in X.X format. To prevent precision problems, the gem supports BigDecimal to handle the calculations, if you need, and always returns data using the same distance unit (kilometers or miles) that was used as input. ## Installation ### Add to your Gemfile ```ruby -gem 'calcpace', '~> 1.0.0' +gem 'calcpace', '~> 1.1.1' ``` Then run bundle install. @@ -24,47 +24,49 @@ gem install calcpace ### Calculate Pace -To calculate pace, provide the total time (in HH:MM:SS format) and distance (in X.X format, representing kilometers or miles). +To calculate pace, provide the total time (in HH:MM:SS format) and distance (in X.X format, representing kilometers or miles). You can use the method `pace` or `pace_seconds`. If you want an extra precision in the calculations, you can pass true as parameter to the method `pace_seconds` and receive result in BigDecimal. ```ruby calculate = Calcpace.new calculate.pace('01:00:00', 12) # => "00:05:00" calculate.pace('string', 12) # It must be a time (RuntimeError) +calculate.pace_seconds('01:00:00', 12) # => 300 +calculate.pace_seconds('01:37:21', 12.3, true) # => 0.474878048780487804878048780487804878049e3 +calculate.pace_seconds('01:37:21', 12.3) # => 474.8780487804878 ``` ### Calculate Total Time -To calculate total time, provide the pace (in HH:MM:SS format) and distance (in X.X format, representing kilometers or miles). +To calculate total time, provide the pace (in HH:MM:SS format) and distance (in X.X format, representing kilometers or miles). You can use the method `total_time` or `total_time_seconds`. If you want an extra precision in the calculations, you can pass true as parameter to the method `total_time_seconds` and receive result in BigDecimal. ```ruby calculate = Calcpace.new calculate.total_time('00:05:00', 12) # => "01:00:00" calculate.total_time('00:05:00', 'string') # It must be a XX:XX:XX time (RuntimeError) +calculate.total_time_seconds('01:37:21', 12.3) # => 71844.3 +calculate.total_time_seconds('01:37:21', 12.3, true) # => 0.718443902439024390243902439024390243902e5 ``` ### Calculate Distance -To calculate distance, provide the running time (in HH:MM:SS format) and pace (in HH:MM:SS format). +To calculate distance, provide the running time (in HH:MM:SS format) and pace (in HH:MM:SS format). If you want an extra precision in the calculations, you can pass true as parameter to the method and receive result in BigDecimal. ```ruby -Calcpace.new.distance('01:30:00', '00:05:00') # => 18.0 +calculate = Calcpace.new +calculate.distance('01:37:21', '00:06:17') # => 15.0 +calculate.distance('01:37:21', '00:06:17', true) # => 0.15493368700265251989389920424403183024e2 +calculate.distance('01:37:21', 'string') # It must be a time (RuntimeError) ``` ### Convert Distances -To convert distances, provide the distance and the unit of measurement (either 'km' for kilometers or 'mi' for miles). +To convert distances, provide the distance and the unit of measurement (either 'km' for kilometers or 'mi' for miles). If want to change the default round of the result (2), you can pass a second parameter to the method. ```ruby converter = Calcpace.new converter.convert(10, 'km') # => 6.21 -converter.convert_distance(10, 'mi') # => 16.09 -``` - -If want to change the default round of the result (2), you can pass a second parameter to the method. - -```ruby -converter = Calcpace.new converter.convert(10, 'km', 1) # => 6.2 +converter.convert_distance(10, 'mi') # => 16.09 converter.convert(10, 'mi', 3) # => 16.093 ``` diff --git a/Rakefile.rb b/Rakefile.rb index 13214e6..e53a7ea 100644 --- a/Rakefile.rb +++ b/Rakefile.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true -require "minitest/test_task" +require 'minitest/test_task' Minitest::TestTask.create(:test) do |t| - t.libs << "test" - t.libs << "lib" + t.libs << 'test' + t.libs << 'lib' t.warning = false end -task :default => :test +task default: :test diff --git a/calcpace.gemspec b/calcpace.gemspec index 456c06d..7ae4ff3 100644 --- a/calcpace.gemspec +++ b/calcpace.gemspec @@ -2,9 +2,9 @@ Gem::Specification.new do |s| s.name = 'calcpace' - s.version = '1.0.0' - s.summary = 'Calcpace: calculate time and distances for activities such as running and cycling.' - s.description = 'Calculate pace, total time, distance and easily convert distances (kilometers and miles) for activities like running and cycling. Get readable results in HH:MM:SS or X.X format for time and distance calculations.' + s.version = '1.1.1' + s.summary = 'Calcpace: calculate time, distance, pace, velocity and convert distances in an easy and precise way.' + s.description = 'Calcpace is a Ruby gem that helps with calculations related to running/cycling activities or general purposes involving distance and time. It can calculate pace, total time, and distance. It also converts distances between miles and kilometers and check formats of time and distance. The results are provided in a readable format, with times in HH:MM:SS or seconds and distances in X.X format. If you need, the gem supports BigDecimal to handle the calculations, ' s.authors = ['Joao Gilberto Saraiva'] s.email = 'joaogilberto@tuta.io' s.files = ['lib/calcpace.rb', 'lib/calcpace/calculator.rb', 'lib/calcpace/checker.rb', 'lib/calcpace/converter.rb'] @@ -16,7 +16,7 @@ Gem::Specification.new do |s| s.add_development_dependency 'rubocop', '~> 0.79' s.add_development_dependency 'rubocop-minitest', '~> 0.11' s.required_ruby_version = '>= 2.7.0' - s.post_install_message = "It's time to grab your sneakers or hop on your bike and start exercising! Thank you for installing Calcpace!" + s.post_install_message = "It's time to calculate! Thank you for installing Calcpace." s.metadata = { 'source_code_uri' => 'https://github.com/0jonjo/calcpace' } s.homepage = 'https://github.com/0jonjo/calcpace' s.license = 'MIT' diff --git a/lib/calcpace/calculator.rb b/lib/calcpace/calculator.rb index e71a5d3..fa43346 100644 --- a/lib/calcpace/calculator.rb +++ b/lib/calcpace/calculator.rb @@ -1,21 +1,42 @@ # frozen_string_literal: true +require 'bigdecimal' + module Calculator - def pace(time, distance) + def pace(time, distance, bigdecimal = false) + pace_in_seconds = pace_seconds(time, distance, bigdecimal) + convert_to_clocktime(pace_in_seconds) + end + + def pace_seconds(time, distance, bigdecimal = false) check_time(time) check_distance(distance) - convert_to_clocktime(convert_to_seconds(time) / distance.to_f) + seconds = convert_to_seconds(time) + bigdecimal ? seconds / BigDecimal(distance.to_s) : seconds / distance + end + + def total_time(pace, distance, bigdecimal = false) + total_time_in_seconds = total_time_seconds(pace, distance, bigdecimal) + convert_to_clocktime(total_time_in_seconds) end - def total_time(pace, distance) + def total_time_seconds(pace, distance, bigdecimal = false) check_time(pace) check_distance(distance) - convert_to_clocktime(convert_to_seconds(pace) * distance.to_f) + pace_seconds = convert_to_seconds(pace) + bigdecimal ? pace_seconds * BigDecimal(distance.to_s) : pace_seconds * distance end - def distance(time, pace) + def distance(time, pace, bigdecimal = false) check_time(time) check_time(pace) - convert_to_seconds(time).to_f / convert_to_seconds(pace).round(2) + if bigdecimal + time_seconds = BigDecimal(convert_to_seconds(time).to_s) + pace_seconds = BigDecimal(convert_to_seconds(pace).to_s) + else + time_seconds = convert_to_seconds(time) + pace_seconds = convert_to_seconds(pace) + end + time_seconds / pace_seconds end end diff --git a/lib/calcpace/converter.rb b/lib/calcpace/converter.rb index 0d243a1..58e4ea9 100644 --- a/lib/calcpace/converter.rb +++ b/lib/calcpace/converter.rb @@ -1,6 +1,11 @@ # frozen_string_literal: true +require 'bigdecimal' + module Converter + KM_TO_MI = BigDecimal('0.621371') + MI_TO_KM = BigDecimal('1.60934') + def to_seconds(time) check_time(time) convert_to_seconds(time) @@ -15,7 +20,7 @@ def convert(distance, unit, round_limit = 2) check_distance(distance) check_unit(unit) check_integer(round_limit) - convert_the_distance(distance, unit, round_limit) + convert_the_distance(BigDecimal(distance.to_s), unit, round_limit) end def convert_to_seconds(time) @@ -24,16 +29,16 @@ def convert_to_seconds(time) end def convert_to_clocktime(seconds) - seconds >= 86_400 ? time = '%d %H:%M:%S' : time = '%H:%M:%S' - Time.at(seconds).utc.strftime(time) + format = seconds >= 86_400 ? '%d %H:%M:%S' : '%H:%M:%S' + Time.at(seconds.to_i).utc.strftime(format) end def convert_the_distance(distance, unit, round_limit = 2) case unit when 'km' - (distance * 0.621371).round(round_limit) + (distance * KM_TO_MI).round(round_limit) when 'mi' - (distance * 1.60934).round(round_limit) + (distance * MI_TO_KM).round(round_limit) end end end diff --git a/test/calcpace/test_calculator.rb b/test/calcpace/test_calculator.rb index 10a32ad..69a514e 100644 --- a/test/calcpace/test_calculator.rb +++ b/test/calcpace/test_calculator.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'minitest/autorun' +require 'bigdecimal' require_relative '../../lib/calcpace' class TestCalculator < Minitest::Test @@ -14,6 +15,23 @@ def test_pace assert_raises(RuntimeError) { @checker.pace('00:00:00', 0) } assert_raises(RuntimeError) { @checker.pace('00:00:00', -1) } assert_equal '00:06:00', @checker.pace('01:00:00', 10) + assert_equal '00:07:54', @checker.pace('01:37:21', 12.3) + end + + def test_pace_without_bigdecimal_precision + assert_equal '00:07:54', @checker.pace('01:37:21', 12.3, false) + end + + def test_pace_seconds + assert_raises(RuntimeError) { @checker.pace_seconds('', 10) } + assert_raises(RuntimeError) { @checker.pace_seconds('invalid', 10) } + assert_raises(RuntimeError) { @checker.pace_seconds('00:00:00', 0) } + assert_raises(RuntimeError) { @checker.pace_seconds('00:00:00', -1) } + assert_equal BigDecimal('474.8780487804878'), @checker.pace_seconds('01:37:21', 12.3) + end + + def test_pace_seconds_with_bigdecimal_precision + assert_equal BigDecimal('0.474878048780487804878048780487804878049e3'), @checker.pace_seconds('01:37:21', 12.3, true) end def test_total_time @@ -24,9 +42,27 @@ def test_total_time assert_equal '01:00:00', @checker.total_time('00:05:00', 12) end + def test_total_time_seconds + assert_raises(RuntimeError) { @checker.total_time_seconds('', 10) } + assert_raises(RuntimeError) { @checker.total_time_seconds('invalid', 10) } + assert_raises(RuntimeError) { @checker.total_time_seconds('00:00:00', 0) } + assert_raises(RuntimeError) { @checker.total_time_seconds('00:00:00', -1) } + assert_equal 3600, @checker.total_time_seconds('00:05:00', 12) + assert_equal 71_844.3, @checker.total_time_seconds('01:37:21', 12.3) + end + + def test_total_time_seconds_with_bigdecimal_precision + assert_equal BigDecimal('0.718443e5'), @checker.total_time_seconds('01:37:21', 12.3, true) + end + def test_distance assert_raises(RuntimeError) { @checker.distance('', '00:05:00') } assert_raises(RuntimeError) { @checker.distance('01:00:00', '') } assert_equal 18.0, @checker.distance('01:30:00', '00:05:00') + assert_equal 15.0, @checker.distance('01:37:21', '00:06:17') + end + + def test_distance_with_bigdecimal_precision + assert_equal BigDecimal('0.15493368700265251989389920424403183024e2'), @checker.distance('01:37:21', '00:06:17', true) end end diff --git a/test/calcpace/test_converter.rb b/test/calcpace/test_converter.rb index 57a400d..45d938a 100644 --- a/test/calcpace/test_converter.rb +++ b/test/calcpace/test_converter.rb @@ -23,7 +23,7 @@ def test_convert_to_clocktime def test_to_clocktime assert_equal '01:00:00', @checker.to_clocktime(3600) - assert_equal '02 01:00:00', @checker.to_clocktime(90000) + assert_equal '02 01:00:00', @checker.to_clocktime(90_000) assert_raises(RuntimeError) { @checker.to_clocktime(-1) } assert_raises(RuntimeError) { @checker.to_clocktime(0) } assert_raises(RuntimeError) { @checker.to_clocktime('invalid') }