calcpace 1.5.5 → 1.7.0
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.
- checksums.yaml +4 -4
- data/.github/workflows/tests.yml +2 -0
- data/.rubocop.yml +67 -0
- data/.ruby-version +1 -1
- data/.tool-versions +2 -0
- data/CHANGELOG.md +48 -0
- data/README.md +59 -8
- data/calcpace.gemspec +4 -1
- data/lib/calcpace/calculator.rb +29 -6
- data/lib/calcpace/checker.rb +38 -6
- data/lib/calcpace/converter.rb +68 -10
- data/lib/calcpace/converter_chain.rb +47 -0
- data/lib/calcpace/errors.rb +23 -2
- data/lib/calcpace/pace_calculator.rb +98 -0
- data/lib/calcpace/version.rb +1 -1
- data/lib/calcpace.rb +28 -2
- metadata +9 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ba8948e4e291f5381e58be3bc289ad8f54aa480a1cef744fc121dbc43449b523
|
|
4
|
+
data.tar.gz: a9e4b3da8526c245adfaed895f460b820d737ca4a3eb805a4e2c9b1510f1a6ca
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 61cea9354aa1343b9db15e92a1a11c06f053158ed72a48c5d1062e4b7d733888a7a8eaf3b88c7696cb879b7a92d196891c01e85369947dd4dead497b322b69b9
|
|
7
|
+
data.tar.gz: fcf1db5f88479865c65ce64d7c788a3762ea01f39c28f10fa3604e268bce1737589cec4eb8b66f94a10f70a1acb12b7ab73b082990e0bc9a1a0c11d0461159d6
|
data/.github/workflows/tests.yml
CHANGED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# RuboCop configuration for Calcpace gem
|
|
4
|
+
# Following Ruby best practices and style guidelines
|
|
5
|
+
|
|
6
|
+
AllCops:
|
|
7
|
+
TargetRubyVersion: 2.7
|
|
8
|
+
NewCops: enable
|
|
9
|
+
Exclude:
|
|
10
|
+
- 'vendor/**/*'
|
|
11
|
+
- 'tmp/**/*'
|
|
12
|
+
- 'node_modules/**/*'
|
|
13
|
+
|
|
14
|
+
# Prefer double quotes for consistency
|
|
15
|
+
Style/StringLiterals:
|
|
16
|
+
Enabled: false
|
|
17
|
+
|
|
18
|
+
# Allow longer lines in tests
|
|
19
|
+
Layout/LineLength:
|
|
20
|
+
Max: 120
|
|
21
|
+
Exclude:
|
|
22
|
+
- 'test/**/*'
|
|
23
|
+
|
|
24
|
+
# Prefer modern hash syntax
|
|
25
|
+
Style/HashSyntax:
|
|
26
|
+
EnforcedStyle: ruby19
|
|
27
|
+
|
|
28
|
+
# Documentation is important for public APIs
|
|
29
|
+
Style/Documentation:
|
|
30
|
+
Enabled: true
|
|
31
|
+
Exclude:
|
|
32
|
+
- 'test/**/*'
|
|
33
|
+
|
|
34
|
+
# Prefer explicit return in some cases for clarity
|
|
35
|
+
Style/RedundantReturn:
|
|
36
|
+
Enabled: false
|
|
37
|
+
|
|
38
|
+
# Allow compact module/class definitions
|
|
39
|
+
Style/ClassAndModuleChildren:
|
|
40
|
+
Enabled: false
|
|
41
|
+
|
|
42
|
+
# Metrics
|
|
43
|
+
Metrics/MethodLength:
|
|
44
|
+
Max: 20
|
|
45
|
+
Exclude:
|
|
46
|
+
- 'test/**/*'
|
|
47
|
+
|
|
48
|
+
Metrics/AbcSize:
|
|
49
|
+
Max: 20
|
|
50
|
+
Exclude:
|
|
51
|
+
- 'test/**/*'
|
|
52
|
+
|
|
53
|
+
Metrics/BlockLength:
|
|
54
|
+
Exclude:
|
|
55
|
+
- 'test/**/*'
|
|
56
|
+
- '*.gemspec'
|
|
57
|
+
|
|
58
|
+
# Allow both single and double quotes for strings
|
|
59
|
+
Style/StringLiteralsInInterpolation:
|
|
60
|
+
Enabled: false
|
|
61
|
+
|
|
62
|
+
# Naming
|
|
63
|
+
Naming/MethodParameterName:
|
|
64
|
+
MinNameLength: 1
|
|
65
|
+
AllowedNames:
|
|
66
|
+
- _
|
|
67
|
+
- e
|
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.4.
|
|
1
|
+
3.4.4
|
data/.tool-versions
ADDED
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.7.0] - Unreleased
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- RuboCop configuration for code style consistency
|
|
12
|
+
- CHANGELOG.md for tracking project changes
|
|
13
|
+
- Comprehensive YARD documentation for all public methods
|
|
14
|
+
- Race pace calculator for standard distances (5K, 10K, half-marathon, marathon)
|
|
15
|
+
- `race_time` and `race_time_clock` methods for calculating finish times
|
|
16
|
+
- `race_pace` and `race_pace_clock` methods for calculating required paces
|
|
17
|
+
- `list_races` method to see available race distances
|
|
18
|
+
- `UnsupportedUnitError` for better error handling
|
|
19
|
+
- Comprehensive test suite with edge cases and error scenarios
|
|
20
|
+
- Test helper utilities for better test organization
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- Improved error messages with more context throughout the gem
|
|
24
|
+
- Enhanced validation for edge cases
|
|
25
|
+
- Better method organization and code structure
|
|
26
|
+
- Optimized `convert_to_seconds` method using case statement
|
|
27
|
+
- Improved error handling in `constant` method with nested rescue
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
- Minor code style inconsistencies
|
|
31
|
+
- Typo in README: `converto_to_clocktime` → `convert_to_clocktime`
|
|
32
|
+
|
|
33
|
+
## [1.6.0] - Previous Release
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
- Custom error classes for better error handling
|
|
37
|
+
- `NonPositiveInputError` for invalid numeric inputs
|
|
38
|
+
- `InvalidTimeFormatError` for invalid time format inputs
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
- Improved error handling throughout the gem
|
|
42
|
+
|
|
43
|
+
## [1.5.0] and earlier
|
|
44
|
+
|
|
45
|
+
See git history for changes in earlier versions.
|
|
46
|
+
|
|
47
|
+
[Unreleased]: https://github.com/0jonjo/calcpace/compare/v1.6.0...HEAD
|
|
48
|
+
[1.6.0]: https://github.com/0jonjo/calcpace/releases/tag/v1.6.0
|
data/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Calcpace [](https://badge.fury.io/rb/calcpace)
|
|
2
2
|
|
|
3
3
|
Calcpace is a Ruby gem designed for calculations and conversions related to distance and time. It can calculate velocity, pace, total time, and distance, accepting time in various formats, including HH:MM:SS. The gem supports conversion to 42 different units, including kilometers, miles, meters, and feet. It also provides methods to validate input.
|
|
4
4
|
|
|
@@ -7,7 +7,7 @@ Calcpace is a Ruby gem designed for calculations and conversions related to dist
|
|
|
7
7
|
### Add to your Gemfile
|
|
8
8
|
|
|
9
9
|
```ruby
|
|
10
|
-
gem 'calcpace', '~> 1.
|
|
10
|
+
gem 'calcpace', '~> 1.7.0'
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
Then run:
|
|
@@ -87,15 +87,15 @@ To learn more about BigDecimal, check the [documentation](https://ruby-doc.org/s
|
|
|
87
87
|
|
|
88
88
|
### Convert Distances and Velocities
|
|
89
89
|
|
|
90
|
-
Use the `convert` method to convert a distance or velocity. The first parameter is the value to be converted, and the second parameter is the unit to which the value will be converted. The unit
|
|
90
|
+
Use the `convert` method to convert a distance or velocity. The first parameter is the value to be converted, and the second parameter is the unit to which the value will be converted. The unit can be a string (e.g. 'km to meters') or a symbol (e.g. :km_to_meters). The gem supports 42 different units, including kilometers, miles, meters, knots, and feet.
|
|
91
91
|
|
|
92
92
|
Here are some examples:
|
|
93
93
|
|
|
94
94
|
```ruby
|
|
95
95
|
converter.convert(10, :km_to_meters) # => 1000
|
|
96
|
-
converter.convert(10,
|
|
96
|
+
converter.convert(10, 'mi to km') # => 16.0934
|
|
97
97
|
converter.convert(1, :nautical_mi_to_km) # => 1.852
|
|
98
|
-
converter.convert(1,
|
|
98
|
+
converter.convert(1, 'km h to m s') # => 0.277778
|
|
99
99
|
converter.convert(1, :m_s_to_mi_h) # => 2.23694
|
|
100
100
|
```
|
|
101
101
|
|
|
@@ -118,14 +118,65 @@ converter.convert(1, :m_s_to_mi_h) # => 2.23694
|
|
|
118
118
|
| :km_h_to_mi_h | Kilometers per Hour to Miles per Hour |
|
|
119
119
|
| :mi_h_to_km_h | Miles per Hour to Kilometers per Hour |
|
|
120
120
|
|
|
121
|
-
You can list all the available units [here](/lib/calcpace/converter.rb), or using `list` methods
|
|
121
|
+
You can list all the available units [here](/lib/calcpace/converter.rb), or using `list` methods. The return will be a hash with the unit and a description of the unit.
|
|
122
122
|
|
|
123
123
|
```ruby
|
|
124
124
|
converter.list_all
|
|
125
|
+
# => {:km_to_mi=>"KM to MI", :mi_to_km=>"MI to KM", ...}
|
|
126
|
+
|
|
125
127
|
converter.list_distance
|
|
128
|
+
# => {:km_to_mi=>"KM to MI", :mi_to_km=>"MI to KM", ...}
|
|
129
|
+
|
|
126
130
|
converter.list_speed
|
|
131
|
+
# => {:m_s_to_km_h=>"M S to KM H", :km_h_to_m_s=>"KM H to M S", ...}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Chain Conversions
|
|
135
|
+
|
|
136
|
+
Perform multiple conversions in sequence with the converter chain feature:
|
|
137
|
+
|
|
138
|
+
```ruby
|
|
139
|
+
calc = Calcpace.new
|
|
140
|
+
|
|
141
|
+
# Convert kilometers to miles to feet in one call
|
|
142
|
+
calc.convert_chain(1, [:km_to_mi, :mi_to_feet])
|
|
143
|
+
# => 3280.84 (1 km = 0.621 mi = 3280.84 feet)
|
|
144
|
+
|
|
145
|
+
# Convert with description for debugging
|
|
146
|
+
calc.convert_chain_with_description(100, [:meters_to_km, :km_to_mi])
|
|
147
|
+
# => { result: 0.0621371, description: "100 → meters_to_km → km_to_mi → 0.0621" }
|
|
148
|
+
|
|
149
|
+
# Speed conversions
|
|
150
|
+
calc.convert_chain(10, [:m_s_to_km_h, :km_h_to_mi_h])
|
|
151
|
+
# => 22.3694 (10 m/s = 36 km/h = 22.37 mi/h)
|
|
127
152
|
```
|
|
128
153
|
|
|
154
|
+
### Race Pace Calculator
|
|
155
|
+
|
|
156
|
+
Calcpace includes a race pace calculator for standard race distances (5K, 10K, half-marathon, and marathon):
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
calc = Calcpace.new
|
|
160
|
+
|
|
161
|
+
# Calculate finish time for a race given a pace
|
|
162
|
+
calc.race_time(300, '5k') # => 1500.0 (5:00/km pace for 5K = 25:00)
|
|
163
|
+
calc.race_time_clock('05:00', 'marathon') # => '03:30:58' (5:00/km pace for marathon)
|
|
164
|
+
|
|
165
|
+
# Calculate required pace for a target finish time
|
|
166
|
+
calc.race_pace('00:30:00', '5k') # => 360.0 (need 6:00/km to finish 5K in 30:00)
|
|
167
|
+
calc.race_pace_clock('04:00:00', 'marathon') # => '00:05:41' (need 5:41/km for 4-hour marathon)
|
|
168
|
+
|
|
169
|
+
# List available race distances
|
|
170
|
+
calc.list_races
|
|
171
|
+
# => { '5k' => 5.0, '10k' => 10.0, 'half_marathon' => 21.0975, 'marathon' => 42.195 }
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Supported race distances:
|
|
175
|
+
- `5k` - 5 kilometers
|
|
176
|
+
- `10k` - 10 kilometers
|
|
177
|
+
- `half_marathon` - 21.0975 kilometers
|
|
178
|
+
- `marathon` - 42.195 kilometers
|
|
179
|
+
|
|
129
180
|
### Other Useful Methods
|
|
130
181
|
|
|
131
182
|
Calcpace also provides other useful methods:
|
|
@@ -134,7 +185,7 @@ Calcpace also provides other useful methods:
|
|
|
134
185
|
converter = Calcpace.new
|
|
135
186
|
converter.convert_to_seconds('01:00:00') # => 3600
|
|
136
187
|
converter.convert_to_clocktime(3600) # => '01:00:00'
|
|
137
|
-
converter.
|
|
188
|
+
converter.convert_to_clocktime(100000) # => '1 03:46:40'
|
|
138
189
|
converter.check_time('01:00:00') # => nil
|
|
139
190
|
```
|
|
140
191
|
|
|
@@ -151,7 +202,7 @@ For example:
|
|
|
151
202
|
begin
|
|
152
203
|
calculate.pace(945, -1)
|
|
153
204
|
rescue Calcpace::NonPositiveInputError => e
|
|
154
|
-
puts e.message # => "
|
|
205
|
+
puts e.message # => "Distance must be a positive number"
|
|
155
206
|
end
|
|
156
207
|
|
|
157
208
|
begin
|
data/calcpace.gemspec
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative 'lib/calcpace/version'
|
|
2
4
|
|
|
3
5
|
Gem::Specification.new do |spec|
|
|
@@ -13,7 +15,8 @@ Gem::Specification.new do |spec|
|
|
|
13
15
|
spec.license = 'MIT'
|
|
14
16
|
spec.required_ruby_version = '>= 2.7.0'
|
|
15
17
|
|
|
16
|
-
spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/main/CHANGELOG.md"
|
|
18
|
+
spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/main/CHANGELOG.md"
|
|
19
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
17
20
|
|
|
18
21
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
19
22
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
data/lib/calcpace/calculator.rb
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Module to calculate time, distance, pace and velocity
|
|
4
|
+
#
|
|
5
|
+
# This module provides methods to perform calculations related to running and pace.
|
|
6
|
+
# All methods that accept numeric inputs validate that values are positive.
|
|
7
|
+
# Methods prefixed with 'checked_' accept time strings in HH:MM:SS or MM:SS format.
|
|
8
|
+
# Methods prefixed with 'clock_' return results in time format (HH:MM:SS).
|
|
4
9
|
module Calculator
|
|
10
|
+
# Calculates velocity (distance per unit time)
|
|
11
|
+
#
|
|
12
|
+
# @param time [Numeric] time in any unit (e.g., seconds, hours)
|
|
13
|
+
# @param distance [Numeric] distance in any unit (e.g., meters, kilometers)
|
|
14
|
+
# @return [Float] velocity (distance/time)
|
|
15
|
+
# @raise [Calcpace::NonPositiveInputError] if time or distance is not positive
|
|
16
|
+
#
|
|
17
|
+
# @example
|
|
18
|
+
# velocity(3600, 12000) #=> 3.333... (12000 meters / 3600 seconds = 3.33 m/s)
|
|
5
19
|
def velocity(time, distance)
|
|
6
|
-
validate_positive(time, distance)
|
|
20
|
+
validate_positive({ time: time, distance: distance })
|
|
7
21
|
distance.to_f / time
|
|
8
22
|
end
|
|
9
23
|
|
|
@@ -16,8 +30,17 @@ module Calculator
|
|
|
16
30
|
convert_to_clocktime(checked_velocity(time, distance))
|
|
17
31
|
end
|
|
18
32
|
|
|
33
|
+
# Calculates pace (time per unit distance)
|
|
34
|
+
#
|
|
35
|
+
# @param time [Numeric] time in any unit (e.g., seconds, minutes)
|
|
36
|
+
# @param distance [Numeric] distance in any unit (e.g., kilometers, miles)
|
|
37
|
+
# @return [Float] pace (time/distance)
|
|
38
|
+
# @raise [Calcpace::NonPositiveInputError] if time or distance is not positive
|
|
39
|
+
#
|
|
40
|
+
# @example
|
|
41
|
+
# pace(3600, 12) #=> 300.0 (3600 seconds / 12 km = 300 seconds/km = 5:00/km)
|
|
19
42
|
def pace(time, distance)
|
|
20
|
-
validate_positive(time, distance)
|
|
43
|
+
validate_positive({ time: time, distance: distance })
|
|
21
44
|
time.to_f / distance
|
|
22
45
|
end
|
|
23
46
|
|
|
@@ -31,7 +54,7 @@ module Calculator
|
|
|
31
54
|
end
|
|
32
55
|
|
|
33
56
|
def time(velocity, distance)
|
|
34
|
-
validate_positive(velocity, distance)
|
|
57
|
+
validate_positive({ velocity: velocity, distance: distance })
|
|
35
58
|
velocity * distance
|
|
36
59
|
end
|
|
37
60
|
|
|
@@ -45,7 +68,7 @@ module Calculator
|
|
|
45
68
|
end
|
|
46
69
|
|
|
47
70
|
def distance(time, velocity)
|
|
48
|
-
validate_positive(time, velocity)
|
|
71
|
+
validate_positive({ time: time, velocity: velocity })
|
|
49
72
|
time.to_f / velocity
|
|
50
73
|
end
|
|
51
74
|
|
|
@@ -57,8 +80,8 @@ module Calculator
|
|
|
57
80
|
|
|
58
81
|
private
|
|
59
82
|
|
|
60
|
-
def validate_positive(
|
|
61
|
-
values.each { |value| check_positive(value) }
|
|
83
|
+
def validate_positive(values)
|
|
84
|
+
values.each { |name, value| check_positive(value, name.capitalize) }
|
|
62
85
|
end
|
|
63
86
|
|
|
64
87
|
def validate_time(time)
|
data/lib/calcpace/checker.rb
CHANGED
|
@@ -2,19 +2,51 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'errors'
|
|
4
4
|
|
|
5
|
-
# Module to
|
|
5
|
+
# Module to validate input values and formats
|
|
6
|
+
#
|
|
7
|
+
# This module provides validation methods for numeric inputs and time format strings
|
|
8
|
+
# used throughout the Calcpace gem.
|
|
6
9
|
module Checker
|
|
7
|
-
|
|
10
|
+
# Validates that a number is positive (greater than zero)
|
|
11
|
+
#
|
|
12
|
+
# @param number [Numeric] the number to validate
|
|
13
|
+
# @param name [String] the name of the parameter for error messages
|
|
14
|
+
# @raise [Calcpace::NonPositiveInputError] if number is not positive
|
|
15
|
+
# @return [void]
|
|
16
|
+
#
|
|
17
|
+
# @example
|
|
18
|
+
# check_positive(10, 'Distance') #=> nil (valid)
|
|
19
|
+
# check_positive(-5, 'Time') #=> raises NonPositiveInputError
|
|
20
|
+
# check_positive(0, 'Speed') #=> raises NonPositiveInputError
|
|
21
|
+
def check_positive(number, name = 'Input')
|
|
8
22
|
return if number.is_a?(Numeric) && number.positive?
|
|
9
23
|
|
|
10
24
|
raise Calcpace::NonPositiveInputError,
|
|
11
|
-
|
|
25
|
+
"#{name} must be a positive number"
|
|
12
26
|
end
|
|
13
27
|
|
|
28
|
+
# Validates that a time string is in the correct format
|
|
29
|
+
#
|
|
30
|
+
# Accepted formats:
|
|
31
|
+
# - HH:MM:SS (hours:minutes:seconds) - e.g., "01:30:45"
|
|
32
|
+
# - MM:SS (minutes:seconds) - e.g., "05:30"
|
|
33
|
+
# - H:MM:SS or M:SS (single digit hours/minutes) - e.g., "1:30:45"
|
|
34
|
+
#
|
|
35
|
+
# @param time_string [String] the time string to validate
|
|
36
|
+
# @raise [Calcpace::InvalidTimeFormatError] if format is invalid
|
|
37
|
+
# @return [void]
|
|
38
|
+
#
|
|
39
|
+
# @example
|
|
40
|
+
# check_time('01:30:45') #=> nil (valid)
|
|
41
|
+
# check_time('5:30') #=> nil (valid)
|
|
42
|
+
# check_time('invalid') #=> raises InvalidTimeFormatError
|
|
14
43
|
def check_time(time_string)
|
|
15
|
-
|
|
16
|
-
|
|
44
|
+
# Check if string is valid and matches expected patterns
|
|
45
|
+
return if time_string.is_a?(String) &&
|
|
46
|
+
(time_string =~ /\A\d{1,2}:\d{2}:\d{2}\z/ ||
|
|
47
|
+
time_string =~ /\A\d{1,2}:\d{2}\z/)
|
|
17
48
|
|
|
18
|
-
raise Calcpace::InvalidTimeFormatError,
|
|
49
|
+
raise Calcpace::InvalidTimeFormatError,
|
|
50
|
+
'It must be a valid time in the XX:XX:XX or XX:XX format'
|
|
19
51
|
end
|
|
20
52
|
end
|
data/lib/calcpace/converter.rb
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Module to convert units
|
|
3
|
+
# Module to convert between different units of distance and speed
|
|
4
|
+
#
|
|
5
|
+
# This module provides conversion methods for 42 different unit pairs,
|
|
6
|
+
# including distance units (kilometers, miles, meters, feet, etc.) and
|
|
7
|
+
# speed units (m/s, km/h, mi/h, knots, etc.).
|
|
4
8
|
module Converter
|
|
5
9
|
module Distance
|
|
6
10
|
KM_TO_MI = 0.621371
|
|
@@ -50,44 +54,98 @@ module Converter
|
|
|
50
54
|
NAUTICAL_MI_H_TO_MI_H = 1.15078
|
|
51
55
|
end
|
|
52
56
|
|
|
57
|
+
# Converts a value from one unit to another
|
|
58
|
+
#
|
|
59
|
+
# @param value [Numeric] the value to convert
|
|
60
|
+
# @param unit [Symbol, String] the conversion unit (e.g., :km_to_mi or 'km to mi')
|
|
61
|
+
# @return [Float] the converted value
|
|
62
|
+
# @raise [Calcpace::NonPositiveInputError] if value is not positive
|
|
63
|
+
# @raise [Calcpace::UnsupportedUnitError] if the unit is not supported
|
|
64
|
+
#
|
|
65
|
+
# @example
|
|
66
|
+
# convert(10, :km_to_mi) #=> 6.21371 (10 km = 6.21 miles)
|
|
67
|
+
# convert(5, 'mi to km') #=> 8.0467 (5 miles = 8.05 km)
|
|
53
68
|
def convert(value, unit)
|
|
54
|
-
check_positive(value)
|
|
69
|
+
check_positive(value, 'Value')
|
|
55
70
|
unit_constant = constant(unit)
|
|
56
71
|
value * unit_constant
|
|
57
72
|
end
|
|
58
73
|
|
|
74
|
+
# Converts a time string to total seconds
|
|
75
|
+
#
|
|
76
|
+
# @param time [String] time string in HH:MM:SS or MM:SS format
|
|
77
|
+
# @return [Integer] total seconds
|
|
78
|
+
#
|
|
79
|
+
# @example
|
|
80
|
+
# convert_to_seconds('01:30:00') #=> 5400 (1 hour 30 minutes)
|
|
81
|
+
# convert_to_seconds('05:30') #=> 330 (5 minutes 30 seconds)
|
|
59
82
|
def convert_to_seconds(time)
|
|
60
83
|
parts = time.split(':').map(&:to_i)
|
|
61
|
-
|
|
84
|
+
case parts.length
|
|
85
|
+
when 2
|
|
62
86
|
minute, seconds = parts
|
|
63
87
|
(minute * 60) + seconds
|
|
64
|
-
|
|
88
|
+
when 3
|
|
65
89
|
hour, minute, seconds = parts
|
|
66
90
|
(hour * 3600) + (minute * 60) + seconds
|
|
91
|
+
else
|
|
92
|
+
0
|
|
67
93
|
end
|
|
68
94
|
end
|
|
69
95
|
|
|
96
|
+
# Converts seconds to a clocktime string
|
|
97
|
+
#
|
|
98
|
+
# @param seconds [Numeric] total seconds
|
|
99
|
+
# @return [String] time in HH:MM:SS format, or "D HH:MM:SS" for durations over 24 hours
|
|
100
|
+
#
|
|
101
|
+
# @example
|
|
102
|
+
# convert_to_clocktime(3600) #=> '01:00:00' (1 hour)
|
|
103
|
+
# convert_to_clocktime(100000) #=> '1 03:46:40' (1 day, 3 hours, 46 minutes, 40 seconds)
|
|
70
104
|
def convert_to_clocktime(seconds)
|
|
71
105
|
days = seconds / 86_400
|
|
72
106
|
format = days.to_i.positive? ? "#{days} %H:%M:%S" : '%H:%M:%S'
|
|
73
107
|
Time.at(seconds).utc.strftime(format)
|
|
74
108
|
end
|
|
75
109
|
|
|
76
|
-
|
|
77
|
-
|
|
110
|
+
# Retrieves the conversion constant for a given unit
|
|
111
|
+
#
|
|
112
|
+
# @param unit [Symbol, String] the unit conversion (e.g., :km_to_mi or 'km to mi')
|
|
113
|
+
# @return [Float] the conversion factor
|
|
114
|
+
# @raise [Calcpace::UnsupportedUnitError] if the unit is not supported
|
|
115
|
+
#
|
|
116
|
+
# @example
|
|
117
|
+
# constant(:km_to_mi) #=> 0.621371
|
|
118
|
+
# constant('km to mi') #=> 0.621371
|
|
119
|
+
def constant(unit)
|
|
120
|
+
unit = format_unit(unit) if unit.is_a?(String)
|
|
121
|
+
Distance.const_get(unit.to_s.upcase)
|
|
78
122
|
rescue NameError
|
|
79
|
-
|
|
123
|
+
begin
|
|
124
|
+
Speed.const_get(unit.to_s.upcase)
|
|
125
|
+
rescue NameError
|
|
126
|
+
raise Calcpace::UnsupportedUnitError, unit
|
|
127
|
+
end
|
|
80
128
|
end
|
|
81
129
|
|
|
82
130
|
def list_all
|
|
83
|
-
(Distance.constants + Speed.constants)
|
|
131
|
+
format_list(Distance.constants + Speed.constants)
|
|
84
132
|
end
|
|
85
133
|
|
|
86
134
|
def list_speed
|
|
87
|
-
Speed.constants
|
|
135
|
+
format_list(Speed.constants)
|
|
88
136
|
end
|
|
89
137
|
|
|
90
138
|
def list_distance
|
|
91
|
-
Distance.constants
|
|
139
|
+
format_list(Distance.constants)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
private
|
|
143
|
+
|
|
144
|
+
def format_unit(unit)
|
|
145
|
+
unit.downcase.gsub(' ', '_').to_sym
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def format_list(constants)
|
|
149
|
+
constants.to_h { |c| [c.downcase.to_sym, c.to_s.gsub('_', ' ').gsub(' TO ', ' to ')] }
|
|
92
150
|
end
|
|
93
151
|
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Module for chaining multiple unit conversions
|
|
4
|
+
#
|
|
5
|
+
# This module allows performing multiple conversions in sequence,
|
|
6
|
+
# which is useful for complex unit transformations.
|
|
7
|
+
module ConverterChain
|
|
8
|
+
# Performs a chain of conversions on a value
|
|
9
|
+
#
|
|
10
|
+
# @param value [Numeric] the initial value to convert
|
|
11
|
+
# @param conversions [Array<Symbol, String>] array of conversion units
|
|
12
|
+
# @return [Float] the final converted value
|
|
13
|
+
# @raise [Calcpace::NonPositiveInputError] if value is not positive
|
|
14
|
+
# @raise [Calcpace::UnsupportedUnitError] if any conversion unit is not supported
|
|
15
|
+
#
|
|
16
|
+
# @example Convert kilometers to miles to feet
|
|
17
|
+
# convert_chain(1, [:km_to_mi, :mi_to_feet])
|
|
18
|
+
# #=> 3280.84 (1 km = 0.621 mi = 3280.84 feet)
|
|
19
|
+
#
|
|
20
|
+
# @example Using string format
|
|
21
|
+
# convert_chain(100, ['meters to km', 'km to mi'])
|
|
22
|
+
# #=> 0.0621371 (100 m = 0.1 km = 0.0621 mi)
|
|
23
|
+
def convert_chain(value, conversions)
|
|
24
|
+
check_positive(value, 'Value')
|
|
25
|
+
conversions.reduce(value) do |result, conversion|
|
|
26
|
+
result * constant(conversion)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Performs a chain of conversions and returns a description
|
|
31
|
+
#
|
|
32
|
+
# @param value [Numeric] the initial value to convert
|
|
33
|
+
# @param conversions [Array<Symbol, String>] array of conversion units
|
|
34
|
+
# @return [Hash] hash with :result and :description keys
|
|
35
|
+
#
|
|
36
|
+
# @example
|
|
37
|
+
# convert_chain_with_description(1, [:km_to_mi, :mi_to_feet])
|
|
38
|
+
# #=> { result: 3280.84, description: "1.0 → km_to_mi → mi_to_feet → 3280.84" }
|
|
39
|
+
def convert_chain_with_description(value, conversions)
|
|
40
|
+
initial_value = value
|
|
41
|
+
result = convert_chain(value, conversions)
|
|
42
|
+
conversion_names = conversions.map(&:to_s).join(' → ')
|
|
43
|
+
description = "#{initial_value} → #{conversion_names} → #{result.round(4)}"
|
|
44
|
+
|
|
45
|
+
{ result: result, description: description }
|
|
46
|
+
end
|
|
47
|
+
end
|
data/lib/calcpace/errors.rb
CHANGED
|
@@ -1,9 +1,30 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# Calcpace custom error classes for better error handling
|
|
3
4
|
class Calcpace
|
|
5
|
+
# Base error class for all Calcpace errors
|
|
4
6
|
class Error < StandardError; end
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
# Raised when time string format is invalid
|
|
9
|
+
# Expected formats: HH:MM:SS or MM:SS
|
|
10
|
+
class InvalidTimeFormatError < Error
|
|
11
|
+
def initialize(msg = 'Invalid time format. Expected HH:MM:SS or MM:SS format.')
|
|
12
|
+
super
|
|
13
|
+
end
|
|
14
|
+
end
|
|
7
15
|
|
|
8
|
-
|
|
16
|
+
# Raised when a numeric input is not positive (zero or negative)
|
|
17
|
+
class NonPositiveInputError < Error
|
|
18
|
+
def initialize(msg = 'Input must be a positive number.')
|
|
19
|
+
super
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Raised when an unsupported unit conversion is requested
|
|
24
|
+
class UnsupportedUnitError < Error
|
|
25
|
+
def initialize(unit = nil)
|
|
26
|
+
msg = unit ? "Unsupported unit conversion: #{unit}" : 'Unsupported unit conversion'
|
|
27
|
+
super(msg)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
9
30
|
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Module for calculating race times and paces for standard distances
|
|
4
|
+
#
|
|
5
|
+
# This module provides convenience methods for calculating finish times
|
|
6
|
+
# and paces for common race distances like 5K, 10K, half-marathon, and marathon.
|
|
7
|
+
module PaceCalculator
|
|
8
|
+
# Standard race distances in kilometers
|
|
9
|
+
RACE_DISTANCES = {
|
|
10
|
+
'5k' => 5.0,
|
|
11
|
+
'10k' => 10.0,
|
|
12
|
+
'half_marathon' => 21.0975,
|
|
13
|
+
'marathon' => 42.195
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
# Calculates the finish time for a race given a pace per kilometer
|
|
17
|
+
#
|
|
18
|
+
# @param pace_per_km [Numeric, String] pace in seconds per km or time string (MM:SS)
|
|
19
|
+
# @param race [String, Symbol] race distance ('5k', '10k', 'half_marathon', 'marathon')
|
|
20
|
+
# @return [Float] total time in seconds
|
|
21
|
+
# @raise [ArgumentError] if race distance is not recognized
|
|
22
|
+
#
|
|
23
|
+
# @example
|
|
24
|
+
# race_time(300, :5k) #=> 1500.0 (5:00/km pace for 5K = 25:00)
|
|
25
|
+
# race_time('05:00', :marathon) #=> 12658.5 (5:00/km pace for marathon = 3:30:58)
|
|
26
|
+
def race_time(pace_per_km, race)
|
|
27
|
+
distance = race_distance(race)
|
|
28
|
+
pace_seconds = pace_per_km.is_a?(String) ? convert_to_seconds(pace_per_km) : pace_per_km
|
|
29
|
+
check_positive(pace_seconds, 'Pace')
|
|
30
|
+
distance * pace_seconds
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Calculates the finish time for a race and returns it as a clock time string
|
|
34
|
+
#
|
|
35
|
+
# @param pace_per_km [Numeric, String] pace in seconds per km or time string (MM:SS)
|
|
36
|
+
# @param race [String, Symbol] race distance ('5k', '10k', 'half_marathon', 'marathon')
|
|
37
|
+
# @return [String] finish time in HH:MM:SS format
|
|
38
|
+
#
|
|
39
|
+
# @example
|
|
40
|
+
# race_time_clock('05:00', :marathon) #=> '03:30:58'
|
|
41
|
+
# race_time_clock(300, :half_marathon) #=> '01:45:17'
|
|
42
|
+
def race_time_clock(pace_per_km, race)
|
|
43
|
+
convert_to_clocktime(race_time(pace_per_km, race))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Calculates the required pace per kilometer to finish a race in a target time
|
|
47
|
+
#
|
|
48
|
+
# @param target_time [Numeric, String] target finish time in seconds or time string (HH:MM:SS)
|
|
49
|
+
# @param race [String, Symbol] race distance ('5k', '10k', 'half_marathon', 'marathon')
|
|
50
|
+
# @return [Float] required pace in seconds per kilometer
|
|
51
|
+
#
|
|
52
|
+
# @example
|
|
53
|
+
# race_pace('03:30:00', :marathon) #=> 297.48... (4:57/km to finish in 3:30)
|
|
54
|
+
# race_pace(1800, :5k) #=> 360.0 (6:00/km to finish in 30:00)
|
|
55
|
+
def race_pace(target_time, race)
|
|
56
|
+
distance = race_distance(race)
|
|
57
|
+
time_seconds = target_time.is_a?(String) ? convert_to_seconds(target_time) : target_time
|
|
58
|
+
check_positive(time_seconds, 'Time')
|
|
59
|
+
time_seconds / distance
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Calculates the required pace and returns it as a clock time string
|
|
63
|
+
#
|
|
64
|
+
# @param target_time [Numeric, String] target finish time in seconds or time string (HH:MM:SS)
|
|
65
|
+
# @param race [String, Symbol] race distance ('5k', '10k', 'half_marathon', 'marathon')
|
|
66
|
+
# @return [String] required pace in MM:SS format
|
|
67
|
+
#
|
|
68
|
+
# @example
|
|
69
|
+
# race_pace_clock('03:30:00', :marathon) #=> '00:04:57'
|
|
70
|
+
def race_pace_clock(target_time, race)
|
|
71
|
+
convert_to_clocktime(race_pace(target_time, race))
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Lists all available standard race distances
|
|
75
|
+
#
|
|
76
|
+
# @return [Hash] hash of race names and distances in kilometers
|
|
77
|
+
#
|
|
78
|
+
# @example
|
|
79
|
+
# list_races #=> { '5k' => 5.0, '10k' => 10.0, ... }
|
|
80
|
+
def list_races
|
|
81
|
+
RACE_DISTANCES.dup
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
# Gets the distance for a standard race
|
|
87
|
+
#
|
|
88
|
+
# @param race [String, Symbol] race name
|
|
89
|
+
# @return [Float] distance in kilometers
|
|
90
|
+
# @raise [ArgumentError] if race is not recognized
|
|
91
|
+
def race_distance(race)
|
|
92
|
+
key = race.to_s.downcase
|
|
93
|
+
RACE_DISTANCES.fetch(key) do
|
|
94
|
+
raise ArgumentError,
|
|
95
|
+
"Unknown race: #{race}. Available races: #{RACE_DISTANCES.keys.join(', ')}"
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
data/lib/calcpace/version.rb
CHANGED
data/lib/calcpace.rb
CHANGED
|
@@ -3,13 +3,39 @@
|
|
|
3
3
|
require_relative 'calcpace/calculator'
|
|
4
4
|
require_relative 'calcpace/checker'
|
|
5
5
|
require_relative 'calcpace/converter'
|
|
6
|
+
require_relative 'calcpace/converter_chain'
|
|
6
7
|
require_relative 'calcpace/errors'
|
|
8
|
+
require_relative 'calcpace/pace_calculator'
|
|
7
9
|
|
|
8
|
-
#
|
|
10
|
+
# Calcpace - A Ruby gem for pace, distance, and time calculations
|
|
11
|
+
#
|
|
12
|
+
# Calcpace provides methods to calculate and convert values related to pace,
|
|
13
|
+
# distance, time, and speed. It supports various time formats and 42 different
|
|
14
|
+
# unit conversions.
|
|
15
|
+
#
|
|
16
|
+
# @example Basic velocity calculation
|
|
17
|
+
# calc = Calcpace.new
|
|
18
|
+
# calc.velocity(3600, 12000) #=> 3.333... (12000m in 3600s = 3.33 m/s)
|
|
19
|
+
#
|
|
20
|
+
# @example Time string calculations
|
|
21
|
+
# calc = Calcpace.new
|
|
22
|
+
# calc.checked_pace('01:00:00', 10) #=> 360.0 (1 hour / 10 km = 6:00/km pace)
|
|
23
|
+
# calc.clock_pace('01:00:00', 10) #=> '00:06:00'
|
|
24
|
+
#
|
|
25
|
+
# @example Unit conversions
|
|
26
|
+
# calc = Calcpace.new
|
|
27
|
+
# calc.convert(10, :km_to_mi) #=> 6.21371 (10 km = 6.21 miles)
|
|
28
|
+
# calc.convert(1, 'm_s_to_km_h') #=> 3.6 (1 m/s = 3.6 km/h)
|
|
29
|
+
#
|
|
30
|
+
# @see https://github.com/0jonjo/calcpace
|
|
9
31
|
class Calcpace
|
|
10
32
|
include Calculator
|
|
11
33
|
include Checker
|
|
12
34
|
include Converter
|
|
35
|
+
include ConverterChain
|
|
36
|
+
include PaceCalculator
|
|
13
37
|
|
|
14
|
-
|
|
38
|
+
# Creates a new Calcpace instance
|
|
39
|
+
#
|
|
40
|
+
# @return [Calcpace] a new instance ready to perform calculations
|
|
15
41
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: calcpace
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- João Gilberto Saraiva
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies: []
|
|
12
12
|
description: Calcpace provides methods to calculate and convert values related to
|
|
13
13
|
pace, distance, time, and speed. It supports various time formats and unit conversions.
|
|
@@ -19,7 +19,10 @@ extra_rdoc_files: []
|
|
|
19
19
|
files:
|
|
20
20
|
- ".github/workflows/tests.yml"
|
|
21
21
|
- ".gitignore"
|
|
22
|
+
- ".rubocop.yml"
|
|
22
23
|
- ".ruby-version"
|
|
24
|
+
- ".tool-versions"
|
|
25
|
+
- CHANGELOG.md
|
|
23
26
|
- Gemfile
|
|
24
27
|
- Gemfile.lock
|
|
25
28
|
- README.md
|
|
@@ -29,7 +32,9 @@ files:
|
|
|
29
32
|
- lib/calcpace/calculator.rb
|
|
30
33
|
- lib/calcpace/checker.rb
|
|
31
34
|
- lib/calcpace/converter.rb
|
|
35
|
+
- lib/calcpace/converter_chain.rb
|
|
32
36
|
- lib/calcpace/errors.rb
|
|
37
|
+
- lib/calcpace/pace_calculator.rb
|
|
33
38
|
- lib/calcpace/version.rb
|
|
34
39
|
homepage: https://github.com/0jonjo/calcpace
|
|
35
40
|
licenses:
|
|
@@ -37,6 +42,7 @@ licenses:
|
|
|
37
42
|
metadata:
|
|
38
43
|
source_code_uri: https://github.com/0jonjo/calcpace
|
|
39
44
|
changelog_uri: https://github.com/0jonjo/calcpace/blob/main/CHANGELOG.md
|
|
45
|
+
rubygems_mfa_required: 'true'
|
|
40
46
|
rdoc_options: []
|
|
41
47
|
require_paths:
|
|
42
48
|
- lib
|
|
@@ -51,7 +57,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
51
57
|
- !ruby/object:Gem::Version
|
|
52
58
|
version: '0'
|
|
53
59
|
requirements: []
|
|
54
|
-
rubygems_version: 3.6.
|
|
60
|
+
rubygems_version: 3.6.7
|
|
55
61
|
specification_version: 4
|
|
56
62
|
summary: A Ruby gem for pace, distance, and time calculations.
|
|
57
63
|
test_files: []
|