Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
module ParkingMeterKata
open System
// coins accepted by the system
type Coin =
| TenCents = 10
| TwentyCents = 20
| FiftyCents = 50
| OneEuro = 100
| TwoEuro = 200
// convert a coint into it's amount in Euro's
let amountOfCoin (coin: Coin) =
Convert.ToDecimal (coin |> int) / 100m
// calculate the total amount of a list of coins
let amountOfCoins (coins: seq<Coin>) =
coins |> Seq.map amountOfCoin |> Seq.sum
// defines how many minutes (second column) can be bought for an amount in euros (first column
// it is important that it's in decending order
let priceTable =
[ 8.M, 600M
5.M, 300M
3.M, 120M
2.M, 60M
1.M, 20M ]
// calculate how many minutes an amount in euros will buy you
let getMinutesBought (amount: Decimal) =
// find which price range this amount falls into
let priceRange = priceTable |> Seq.tryFind (fun (price, time) -> amount >= price)
match priceRange with
| Some (price, time) ->
// amount of time we can buy is the ratio of the amount we've given and the cost of range
// times the number of minutes in this price range
(amount / price) * time
| None -> failwith "Not enough"
// calculates wether this is a day where we need to pay
let isExcludeDay (date: DateTime) =
date.DayOfWeek = DayOfWeek.Sunday
|| date.Month = 8
// TODO public holidates
// number of minutes in a day, a "day" is 10 hours long as we pay between 9h and 19h
let minutesInADay = 600M
// calculate what day we've paid up to if it's n days later
let getNthPayingDay n (startDate: DateTime) =
// creates an infinite sequence of the paying days starting at given date
let payingDays (startDate: DateTime) =
// create an infinite sequence of all dates from given date
let allDates = Seq.initInfinite (fun x -> startDate.AddDays( x |> float))
// filter out any dates that aren't paying dates
allDates |> Seq.filter (fun x -> not (isExcludeDay x))
// find the nth element in this list
Seq.nth n (payingDays startDate)
// test to see if given date is outside the paying range
let afterPayingTime (date: DateTime) =
date.Hour > 19
// test to see if we're before the time where we need to pay
let beforePayingTime (date: DateTime) =
date.Hour < 9
// Add the minutes we bought to current time, taking into account opening
// hours and excluded days
let addTime (startDate: DateTime) (minutes: Decimal) =
// first caculate how many days we need to add
let days =
(Math.Floor (minutes / minutesInADay)|> int) +
(if afterPayingTime startDate then 1 else 0)
// then calculate how many minutes during this day we need to add
let remainingMinutes = minutes % minutesInADay |> float
// add on the number of days
let date = if days > 0 then getNthPayingDay days startDate else startDate
// if we're before the time were we need to pay bring the date up to the paying time
let date = if beforePayingTime date then date.Date.AddHours(9.) else date
// add all the minutes we've bought for today (this can't be > minutesInADay)
let dateWithAllMinsAdded = date.AddMinutes(remainingMinutes)
// see if we've passed the hours we're we need to pay
if afterPayingTime dateWithAllMinsAdded then
// find the next paying day and add remaing minutes
let nextDay = getNthPayingDay 1 date
let nextDayAt9 = nextDay.Date.AddHours(9.)
let remainingMins =
dateWithAllMinsAdded - date.Date.AddHours(19.)
nextDayAt9.Add( remainingMins)
else
// otherwise just return this date
dateWithAllMinsAdded
// compose getMinutesBought and addTime to find the end time
let calcuateEndDateFromAmount (time: DateTime) (amount: Decimal) =
getMinutesBought amount
|> addTime time
// compose amountOfCoins and calcuateEndDateFromAmount to find what the end date
// for the coins we've used to pay with
let calcumateEndDateFromCoins (time: DateTime) (coins: seq<Coin>) =
coins |> amountOfCoins |> calcuateEndDateFromAmount time
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.