Class: Runby::Pace

Inherits:
Object
  • Object
show all
Includes:
Comparable
Defined in:
lib/runby_pace/pace.rb

Overview

Represents a pace consisting of a distance and a time in which that distance was covered

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(time_or_pace, distance = '1K') ⇒ Pace

Returns a new instance of Pace.



15
16
17
18
19
20
21
22
23
24
25
# File 'lib/runby_pace/pace.rb', line 15

def initialize(time_or_pace, distance = '1K')
  case time_or_pace
  when RunbyTime
    init_from_time time_or_pace, distance
  when String
    init_from_string time_or_pace, distance
  else
    raise 'Invalid Time or Pace'
  end
  freeze
end

Instance Attribute Details

#distanceObject (readonly)

Returns the value of attribute distance.



8
9
10
# File 'lib/runby_pace/pace.rb', line 8

def distance
  @distance
end

#timeObject (readonly)

Returns the value of attribute time.



8
9
10
# File 'lib/runby_pace/pace.rb', line 8

def time
  @time
end

Class Method Details

.new(time_or_pace, distance = '1K') ⇒ Object



10
11
12
13
# File 'lib/runby_pace/pace.rb', line 10

def self.new(time_or_pace, distance = '1K')
  return time_or_pace if time_or_pace.is_a? Pace
  super
end

.parse(str) ⇒ Object

Parameters:

  • str (String)

    is either a long-form pace such as “10:00 per mile” or a short-form pace like “10:00 p/mi”



58
59
60
61
62
63
64
65
# File 'lib/runby_pace/pace.rb', line 58

def self.parse(str)
  str = str.to_s.strip.chomp
  match = str.match %r{^(?<time>[:\d]*) ?(?: per |p\/)(?<distance>(?:[\d.]+ ?)?\w+)$}
  raise "Invalid pace format (#{str})" unless match
  time = Runby::RunbyTime.new(match[:time])
  distance = Runby::Distance.new(match[:distance])
  Pace.new time, distance
end

.try_parse(str) ⇒ Object



67
68
69
70
71
72
73
74
75
76
77
# File 'lib/runby_pace/pace.rb', line 67

def self.try_parse(str)
  pace = nil
  error_message = nil
  warning_message = nil
  begin
    pace = Pace.parse str
  rescue StandardError => ex
    error_message = ex.message
  end
  { pace: pace, error: error_message, warning: warning_message }
end

Instance Method Details

#+(other) ⇒ Object

Parameters:



116
117
118
119
120
121
122
# File 'lib/runby_pace/pace.rb', line 116

def +(other)
  if other.is_a?(Pace)
    Pace.new(@time + other.convert_to(@distance).time, @distance)
  elsif other.is_a?(RunbyTime)
    Pace.new(@time + other, @distance)
  end
end

#-(other) ⇒ Object

Parameters:



107
108
109
110
111
112
113
# File 'lib/runby_pace/pace.rb', line 107

def -(other)
  if other.is_a?(Pace)
    Pace.new(@time - other.convert_to(@distance).time, @distance)
  elsif other.is_a?(RunbyTime)
    Pace.new(@time - other, @distance)
  end
end

#<=>(other) ⇒ Object



79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/runby_pace/pace.rb', line 79

def <=>(other)
  raise "Unable to compare Runby::Pace to #{other.class}(#{other})" unless [Pace, RunbyTime, String].include? other.class
  if other.is_a? Pace
    meters_per_minute.round(2) <=> other.meters_per_minute.round(2)
  elsif other.is_a? RunbyTime
    @time <=> other
  elsif other.is_a? String
    return 0 if to_s == other || to_s(format: :long) == other
    return 0 if @time == other
    self <=> try_parse(other)[:pace]
  end
end

#almost_equals?(other_pace, tolerance_time = '00:01') ⇒ Boolean

Returns:

  • (Boolean)


92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/runby_pace/pace.rb', line 92

def almost_equals?(other_pace, tolerance_time = '00:01')
  if other_pace.is_a?(RunbyTime)
    return almost_equals?(Pace.new(other_pace, @distance), tolerance_time)
  end
  if other_pace.is_a?(String)
    return almost_equals?(Pace.new(other_pace, @distance), tolerance_time) if other_pace.match?(/^\d?\d:\d\d$/)
    other_pace = Pace.parse(other_pace)
  end
  tolerance = RunbyTime.new(tolerance_time)
  fast_end = (self - tolerance)
  slow_end = (self + tolerance)
  slow_end <= other_pace && other_pace <= fast_end
end

#as_speedObject



44
45
46
47
48
49
# File 'lib/runby_pace/pace.rb', line 44

def as_speed
  total_minutes = @time.total_minutes
  multiplier = total_minutes.positive? ? (60 / total_minutes).round(2) : 0
  distance = Runby::Distance.new(@distance.uom, multiplier)
  Runby::Speed.new distance
end

#convert_to(target_distance) ⇒ Object



27
28
29
30
31
32
# File 'lib/runby_pace/pace.rb', line 27

def convert_to(target_distance)
  target_distance = Distance.new(target_distance) unless target_distance.is_a?(Distance)
  return self if @distance == target_distance
  conversion_factor = target_distance / @distance
  Pace.new @time * conversion_factor, target_distance
end

#distance_covered_over_time(time) ⇒ Object



124
125
126
127
128
129
130
131
132
# File 'lib/runby_pace/pace.rb', line 124

def distance_covered_over_time(time)
  time = Runby.sanitize(time).as(RunbyTime)
  if time.total_minutes.zero? || @distance.multiplier.zero?
    return Runby::Distance.new(@distance.uom, 0)
  end
  divisor = @time.total_minutes / time.total_minutes / @distance.multiplier
  distance_covered = Runby::Distance.new(@distance.uom, 1 / divisor)
  distance_covered
end

#meters_per_minuteObject



51
52
53
54
55
# File 'lib/runby_pace/pace.rb', line 51

def meters_per_minute
  total_minutes = @time.total_minutes
  return 0 unless total_minutes.positive?
  @distance.meters / total_minutes
end

#to_s(format: :short) ⇒ Object



34
35
36
37
38
39
40
41
42
# File 'lib/runby_pace/pace.rb', line 34

def to_s(format: :short)
  leading_one_regex = /^1 ?/
  distance_s = @distance.to_s(format: format).gsub(leading_one_regex, '')
  case format
  when :short then "#{time} p/#{distance_s}"
  when :long then "#{time} per #{distance_s}"
  else raise "Invalid string format #{format}"
  end
end