# frozen_string_literal: true module Runby # Helper class which parses strings and returns new RunbyTime(s) class RunbyTimeParser def self.parse(str) time = str.to_s.strip.chomp if time_string?(time) parts = TimeParts.new(time.split(':').reverse) elsif integer?(time) parts = extract_minutes_from_integer time elsif decimal?(time) parts = extract_minutes_from_decimal time else raise 'Invalid time format' end RunbyTime.new(parts) end def self.decimal?(str) str.match?(/^\d+[,. ]\d+$/) end def self.integer?(str) str.match?(/^\d+$/) end def self.time_string?(str) str.match?(/^\d?\d(:\d\d)+$/) end def self.extract_minutes_from_decimal(decimal_str) decimal_parts = decimal_str.split(/[,. ]/) minutes = decimal_parts[0].to_i seconds = (decimal_parts[1].to_i / 10.0 * 60).to_i TimeParts.new([seconds, minutes]) end def self.extract_minutes_from_integer(integer_str) TimeParts.new([0, integer_str.to_i]) end # Encapsulates the parts of a time string class TimeParts def initialize(parts_array) @keys = { seconds: 0, minutes: 1, hours: 2 } @parts = Array.new(@keys.count, 0) Range.new(0, parts_array.count - 1).each { |i| @parts[i] = parts_array[i] } validate freeze end def [](key) i = @keys[key] @parts[i].to_i end def format time_f = +'' @parts.reverse_each do |part| time_f << ':' << part.to_s.rjust(2, '0') end # Remove leading ':' time_f.slice!(0) # Remove leading '00:00...' time_f.sub!(/^(?:00:)+(\d\d:\d\d)/, '\1') if time_f.length > 5 # If the time looks like 00:48, only show 0:48 time_f.slice!(0) if time_f.slice(0) == '0' time_f end def validate raise 'Hours must be less than 24' if self[:hours] > 23 raise 'Minutes must be less than 60 if hours are supplied' if self[:hours].positive? && self[:minutes] > 59 raise 'Minutes must be less than 99 if no hours are supplied' if self[:hours].zero? && self[:minutes] > 99 raise 'Seconds must be less than 60' if self[:seconds] > 59 end end end end