Skip to content

Instantly share code, notes, and snippets.

@BrandonBrowning
Created July 5, 2014 19:05
Show Gist options
  • Save BrandonBrowning/26d0816b57017ec2b9d9 to your computer and use it in GitHub Desktop.
Save BrandonBrowning/26d0816b57017ec2b9d9 to your computer and use it in GitHub Desktop.
Time Interval Parser
// Turns nice amount per time interval phrases to costs per month
// Fully typed and reusable parsers for all parts of the process
// Ex: $5.10 per day -> $155.227850 per month
// Ex: $20.50 per 8 hours -> $1871.865250 per month
module TimeInterval
open System
open FParsec
type 'a parser = Parser<'a, unit>
type IntervalUnit = Second | Minute | Hour | Day | Week | Month | Year
type Interval = float * IntervalUnit // 2 weeks (2, Week)
type CostPerIntervalLine = float * Interval // $1000 per 2 weeks (1000.0, (2, Week))
let daysPerYear = 365.242
let avgDaysPerMonth = daysPerYear / 12.0
let month = TimeSpan.FromDays avgDaysPerMonth
let currencies = ["$"]
let constant x junk = x // somehow not in stdlib
let pStringsToType (t: 'a) (strs: string seq): 'a parser =
Seq.map pstringCI strs |> choice |>> constant t
let parseIntervalUnit =
choice [
pStringsToType Second ["second"; "seconds"];
pStringsToType Minute ["minute"; "minutes"];
pStringsToType Hour ["hour"; "hours"];
pStringsToType Day ["day"; "days"];
pStringsToType Week ["week"; "weeks"];
pStringsToType Month ["month"; "months"];
pStringsToType Year ["year"; "years"]
]
let parseInterval =
choice [
parseIntervalUnit |>> fun interval -> (1.0, interval); // week
pfloat .>>. (pchar ' ' >>. parseIntervalUnit); // 5 weeks
]
let parseCurrencySymbol = currencies |> Seq.map pstring |> choice // $
let parseCurrency = optional parseCurrencySymbol >>. pfloat // $5.50
let parseCPI = parseCurrency .>>. (pstring " per " >>. parseInterval) // $0.50 per minute
let intervalToTimespan = function
| x, Second -> TimeSpan.FromSeconds x
| x, Minute -> TimeSpan.FromMinutes x
| x, Hour -> TimeSpan.FromHours x
| x, Day -> TimeSpan.FromDays x
| x, Week -> TimeSpan.FromDays <| x * 7.0
| x, Month -> TimeSpan.FromDays <| x * avgDaysPerMonth
| x, Year -> TimeSpan.FromDays <| x * daysPerYear
// (5.0, (100, Second)) -> 131487.12 (per month)
let cpiToPerMonth (rate, interval) =
let per = fst interval
let intervalSpan = intervalToTimespan interval
let ratio = float month.Ticks / float intervalSpan.Ticks
rate * ratio
// "$5 per 100 seconds" -> prints "$131487.120000 per month (5.0, (100.0, Second))"
let printCPI txt =
match run parseCPI txt with
| Success (result, _, _) ->
printfn "$%f per month\t%A\n" (cpiToPerMonth result) result
| Failure (_, error, _) ->
printfn "%A" error
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment