Sprint-o-Mat 2020.2 is Released: Run in KM

From the beginning of Sprint-o-Mat, I always internally represented distances as an enum with an associated value:

public enum Distance {
  case mile(distance: Double)
  case km(distance: Double)
 
  static let kmPerMile = 1.60934
}

So, adding kilometer support in Sprint-o-Mat was mostly about adding in some UI.

I added a button to flip the units, but I didn’t want a straight conversion. I wanted it to round to the closest tenth.

public func flipUnitsAndRound() -> Distance {
  switch self {
  case .mile(let d):
    return .km(distance: round(d * Distance.kmPerMile * 10) / 10)
  case .km(let d):
    return .mile(distance: round(d / Distance.kmPerMile * 10) / 10)
  }
}

I do the same for paces, and round them to the nearest 15 seconds.

With this done, it was mostly hunting down all of the places I was lazy—mostly inside of my HealthKit code that is getting running distance from the Watch in miles.

I left that as is, but made all of the mathematical operators for Distance work for mixed miles and kilometers, so I could could construct a .km Distance and then += .mile distances onto it, and the conversion would be automatic, keeping the units of the left-hand side.

public func + (left: Distance, right: Distance) -> Distance {
  switch (left, right) {
    case (.mile(let ld), .mile(let rd)):
      return .mile(distance: ld + rd)
    case (.km(let ld), .km(let rd)):
      return .km(distance: ld + rd)
    case (.km(let ld), .mile(let rd)):
      return .km(distance: ld + rd * Distance.kmPerMile)
    case (.mile(let ld), .km(let rd)):
         return .mile(distance: ld + rd / Distance.kmPerMile)
     }
}

func += (left: inout Distance, right: Distance) {
  left = left + right
}

If you run in kilometers, check out the new version.