calcpace 1.15.0 → 1.16.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5eefef398417bfe2d045b2afd7ee78991b443c7c9bc1eabd536e81d03789908c
4
- data.tar.gz: 7b676576e7e3af9b9d4ce75430af80835a35535b2c8b971ed5ef764ddd608853
3
+ metadata.gz: 5fd21092789d45d81f1cc1def219122a63cd74ead051cc59ee30b178198702f1
4
+ data.tar.gz: e67ee4c7a71c194ce57928993ee2e898947465d2647e8ffa5b85277386e652b2
5
5
  SHA512:
6
- metadata.gz: 2b7481ff8c0c4b91f724af723eacfd9357905fa2207adc0871dd745d7fc4a4beac52349f0051703a91288c724dd2b6c00298beb585d08949b156833e9fc70af7
7
- data.tar.gz: 7d58f1c452924bbc9c7a895a5ed5102e1ae298b002984d9708aea23b4f54f7339fa2b4dd6708b678cf5f1dd45d5cfd8cc8dd5c3e869df6b07856f08eaf6c720c
6
+ metadata.gz: ee62a321a8a53d93f36698579ab5bb3a5e87c3ad744191219477492a63daafc100b07fe01421bba40835b8bfef84554698db605ca9279e7619473301b0e366cb
7
+ data.tar.gz: ed4c5799eeb409dbb595e594417cc9efbcb9a6316bdb93dc6f9288c3deae7291aef63ff78415ca172f8ea86789d894fa7c057ecd4887902a9e5e3b0b962e2add
data/CHANGELOG.md CHANGED
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.16.0] - 2026-09-05
11
+
12
+ ### Added
13
+ - `stride_length(pace, cadence, unit: :km)` — metres per step from a pace (clock
14
+ string or seconds per unit) and a cadence in steps per minute counting **both
15
+ feet**; Strava's API reports cadence as one-leg RPM, so callers reading it from
16
+ there must double it first.
17
+ - `cadence_for_stride(pace, stride, unit: :km)` — the inverse: the both-feet
18
+ cadence in steps per minute that a given stride length implies at a given pace.
19
+
10
20
  ## [1.15.0] - 2026-08-30
11
21
 
12
22
  ### Added
@@ -505,7 +515,8 @@ predictors are untouched.
505
515
 
506
516
  See git history for changes in earlier versions.
507
517
 
508
- [Unreleased]: https://github.com/0jonjo/calcpace/compare/v1.15.0...HEAD
518
+ [Unreleased]: https://github.com/0jonjo/calcpace/compare/v1.16.0...HEAD
519
+ [1.16.0]: https://github.com/0jonjo/calcpace/compare/v1.15.0...v1.16.0
509
520
  [1.15.0]: https://github.com/0jonjo/calcpace/compare/v1.14.0...v1.15.0
510
521
  [1.14.0]: https://github.com/0jonjo/calcpace/compare/v1.13.0...v1.14.0
511
522
  [1.13.0]: https://github.com/0jonjo/calcpace/compare/v1.12.1...v1.13.0
data/README.md CHANGED
@@ -417,6 +417,32 @@ pace bands, and age-grading tolerances agree to the metre.
417
417
 
418
418
  ---
419
419
 
420
+ ### Stride & Cadence
421
+
422
+ Pace, cadence and stride length are one identity, so any two of them give the third:
423
+
424
+ ```ruby
425
+ calc.stride_length('05:00', 170) # => 1.18
426
+ calc.stride_length('04:00', 180) # => 1.39
427
+ calc.stride_length('08:02', 170, unit: :mi) # => 1.18
428
+
429
+ calc.cadence_for_stride('05:00', 1.18) # => 169.5
430
+ calc.cadence_for_stride('05:30', 1.15) # => 158.1
431
+ ```
432
+
433
+ `stride_length` returns metres per step (2 decimals); `cadence_for_stride` is its
434
+ inverse and returns steps per minute (1 decimal). Pace takes the same forms as
435
+ everywhere else — a clock string (`'05:00'`, `'00:05:00'`) or seconds per unit
436
+ (`300`) — and `unit:` says which unit that pace is per: `:km` (default) or `:mi`.
437
+ `8:02/mi` is `5:00/km` rounded down to the second (exactly 8:02.8), so the two strides
438
+ agree to the centimetre at this cadence.
439
+
440
+ Cadence is steps per minute counting **both feet** — the number a watch shows during a
441
+ run, typically 160–185 spm. Strava's API reports cadence as one-leg RPM, so a value
442
+ read from there must be doubled before it is passed in.
443
+
444
+ ---
445
+
420
446
  ### Fitness Predictor (race times from VO2max)
421
447
 
422
448
  The inverse of `estimate_vo2max`: what a given fitness is worth over a race.
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Module relating pace, cadence and stride length
4
+ #
5
+ # The three quantities are one identity, not three measurements:
6
+ # speed (m/min) = unit_metres / pace_seconds * 60
7
+ # stride (m) = speed / cadence
8
+ # so any two of them give the third. Cadence here is steps per minute counting
9
+ # BOTH feet (spm) — the number a watch shows during a run. Strava's API reports
10
+ # cadence as one-leg RPM, so a caller reading it from there must double it
11
+ # before passing it in.
12
+ module StrideCalculator
13
+ # Calculates stride length from pace and cadence
14
+ #
15
+ # @param pace [Numeric, String] pace in seconds per unit or time string (MM:SS or HH:MM:SS)
16
+ # @param cadence [Numeric] cadence in steps per minute, both feet (must be > 0)
17
+ # @param unit [Symbol, String] unit the pace is expressed in — :km (default) or :mi
18
+ # @return [Float] stride length in metres per step, rounded to 2 decimals
19
+ # @raise [Calcpace::InvalidTimeFormatError] if pace is a string that is not a valid clock
20
+ # @raise [Calcpace::NonPositiveInputError] if pace or cadence is not positive
21
+ # @raise [Calcpace::UnsupportedUnitError] if unit is not :km or :mi
22
+ #
23
+ # @example
24
+ # # 300 s/km → 200 m/min; 200/170
25
+ # calc.stride_length('05:00', 170) #=> 1.18
26
+ # # numeric seconds per km
27
+ # calc.stride_length(300, 170) #=> 1.18
28
+ # # 8:02/mi is 5:00/km rounded down to the second
29
+ # calc.stride_length('08:02', 170, unit: :mi) #=> 1.18
30
+ def stride_length(pace, cadence, unit: :km)
31
+ speed = speed_meters_per_minute(pace, unit)
32
+ check_positive(cadence, 'Cadence')
33
+
34
+ (speed / cadence).round(2)
35
+ end
36
+
37
+ # Calculates the cadence a given stride length implies at a given pace
38
+ #
39
+ # The inverse of #stride_length: it answers what turnover a runner needs to
40
+ # hold a pace with the stride they actually have.
41
+ #
42
+ # @param pace [Numeric, String] pace in seconds per unit or time string (MM:SS or HH:MM:SS)
43
+ # @param stride [Numeric] stride length in metres per step (must be > 0)
44
+ # @param unit [Symbol, String] unit the pace is expressed in — :km (default) or :mi
45
+ # @return [Float] cadence in steps per minute, both feet, rounded to 1 decimal
46
+ # @raise [Calcpace::InvalidTimeFormatError] if pace is a string that is not a valid clock
47
+ # @raise [Calcpace::NonPositiveInputError] if pace or stride is not positive
48
+ # @raise [Calcpace::UnsupportedUnitError] if unit is not :km or :mi
49
+ #
50
+ # @example
51
+ # # 200 m/min / 1.18 m
52
+ # calc.cadence_for_stride('05:00', 1.18) #=> 169.5
53
+ # calc.cadence_for_stride('05:30', 1.15) #=> 158.1
54
+ def cadence_for_stride(pace, stride, unit: :km)
55
+ speed = speed_meters_per_minute(pace, unit)
56
+ check_positive(stride, 'Stride')
57
+
58
+ (speed / stride).round(1)
59
+ end
60
+
61
+ private
62
+
63
+ # Turns a pace (clock string or seconds per unit) into a running speed
64
+ #
65
+ # A String pace is checked as a clock before it is parsed, the way
66
+ # Calculator#validate_time, Vo2maxEstimator and AgeGrading check theirs — so
67
+ # '05:xx' is a format error, not a silently truncated pace. The unit is
68
+ # resolved first, so a bad unit wins over a bad pace, as it does in
69
+ # FitnessPredictor#race_times_from_vo2max.
70
+ #
71
+ # @param pace [Numeric, String] pace in seconds per unit or time string
72
+ # @param unit [Symbol, String] :km or :mi
73
+ # @return [Float] speed in metres per minute
74
+ def speed_meters_per_minute(pace, unit)
75
+ meters = pace_unit_meters(unit)
76
+ pace_seconds = pace_seconds_from(pace)
77
+ check_positive(pace_seconds, 'Pace')
78
+
79
+ meters / pace_seconds * 60.0
80
+ end
81
+
82
+ # @param pace [Numeric, String] pace in seconds per unit or time string
83
+ # @return [Numeric] the pace in seconds
84
+ # @raise [Calcpace::InvalidTimeFormatError] if a string pace is not a valid clock
85
+ def pace_seconds_from(pace)
86
+ return pace unless pace.is_a?(String)
87
+
88
+ check_time(pace)
89
+ convert_to_seconds(pace)
90
+ end
91
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Calcpace
4
- VERSION = '1.15.0'
4
+ VERSION = '1.16.0'
5
5
  end
data/lib/calcpace.rb CHANGED
@@ -13,6 +13,7 @@ require_relative 'calcpace/pace_calculator'
13
13
  require_relative 'calcpace/pace_converter'
14
14
  require_relative 'calcpace/race_predictor'
15
15
  require_relative 'calcpace/race_splits'
16
+ require_relative 'calcpace/stride_calculator'
16
17
  require_relative 'calcpace/track_calculator'
17
18
  require_relative 'calcpace/training_zones'
18
19
  require_relative 'calcpace/vo2max_estimator'
@@ -51,6 +52,7 @@ class Calcpace
51
52
  include PaceConverter
52
53
  include RacePredictor
53
54
  include RaceSplits
55
+ include StrideCalculator
54
56
  include TrackCalculator
55
57
  include TrainingZones
56
58
  include Vo2maxEstimator
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: calcpace
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.15.0
4
+ version: 1.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - João Gilberto Saraiva
@@ -49,6 +49,7 @@ files:
49
49
  - lib/calcpace/pace_converter.rb
50
50
  - lib/calcpace/race_predictor.rb
51
51
  - lib/calcpace/race_splits.rb
52
+ - lib/calcpace/stride_calculator.rb
52
53
  - lib/calcpace/track_calculator.rb
53
54
  - lib/calcpace/training_zones.rb
54
55
  - lib/calcpace/version.rb