Created
July 5, 2014 19:05
-
-
Save BrandonBrowning/26d0816b57017ec2b9d9 to your computer and use it in GitHub Desktop.
Time Interval Parser
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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