24 Days of Hackage: time

If there’s one task I don’t have time for these days, it’s dealing with time. Time is a classic example of a problem that appears simple on the outside, but is full of all sorts of complicated details when you really start working with it. Naturally, these problems only manifest themselves years down the line, when you’ve already committed to a broken implementation. Considering all of that, I want as little as possible to do with time, so I turn to a library rather than doing it myself. Today, we’ll have a look at Ashley Yakeley’s time library.

time does a great job of abstracting away the aforementioned tricky details into a powerful, type safe library. There are four main namespaces within this library:

Not only does time come with all of these data types and functions, it provides instances for a lot of the type classes offered by the Haskell prelude - and this can often be one of the hardest things to understand when first approaching this library. Let’s take stock of the most important type classes, and see how time uses them:

That’s a lot to take in, so lets see how this all plays out in practice. First of all, lets take our current time as a starting point. There are two ways of thinking about “current time” - our time as we see it on our own clocks (the time in our time zone), or the current time in UTC. For me, my timezone is currently UTC anyway, so we’ll work with the current UTC time:

myTime <- getCurrentTime
putStrLn $ "It's currently: " ++ show myTime

Next, we’d like to answer the question: what will the time be 5 hours from now? We already have a value to represent “now” (of type UTCTime), so we need a way to represent “5 hours” and a way to combine the two. “5 hours” is a time interval, so we can use NominalDiffTime for this. However, looking at the library, it doesn’t seem possible to create these values! This is where the type classes come into play - remember how we said that Num lets use treat literals as time data types? The Num instance for NominalDiffTime is used for converting from an amount of seconds, so with a little arithmetic we can represent 5 hours:

let fiveHours = 5 * 60 * 60

To offset a UTCTime with a NominalDiffTime we can’t use the normal (+) operator, because this is only supported for values of the same type1. time exports the addUTCTime :: NominalDiffTime -> UTCTime -> UTCTime, which is exactly what we want:

let later = fiveHours `addUTCTime` myTime
putStrLn $ "Five hours later it will be: " ++ show later

Now we see:

It's currently: 2013-12-15 14:03:18.095702 UTC
Five hours later it will be: 2013-12-15 19:03:18.095702 UTC

Perfect! The default show instance is a little bit complex though, so lets see if we can format this a bit better. I’d like to see a much more British time, preferably “15/12/2013 7:03 PM”. This is a breeze with Data.Time.Format:

let format = formatTime defaultTimeLocale "%Y/%m/%e %l:%M %P"
putStrLn $ "Formatted, that is: " ++ format later

Now, the output is:

It's currently: 2013-12-15 14:07:23.964489 UTC
Five hours later it will be: 2013-12-15 19:07:23.964489 UTC
Formatted, that is: 2013/12/15  7:07 pm

Finally, how about finding how many days we have to wait until Santa comes? We could parse this as a UTCTime, but we really only care about calendar days. To turn 2013/12/25 into a date, we use fromGregorian:

let christmasDay = fromGregorian 2013 12 25

To turn the current time into the calendar day, we can use utctDay to extract the day. To find out the difference of two days, we can’t use (-) for similar reasons, but we do have diffDays out our disposal:

let n = christmasDay `diffDays` utctDay myTime
putStrLn $ "Only " ++ show n ++ " days to go until Christmas!"

And a final run of our application shows:

It's currently: 2013-12-15 14:12:20.841326 UTC
Five hours later it will be: 2013-12-15 19:12:20.841326 UTC
Formatted, that is: 2013/12/15  7:12 pm
Only 10 days to go until Christmas!

Which puts me into blind panic as I realise… I still haven’t got my family their gifts!

  1. Curious readers might be left frustrated that we don’t have a general abstraction here, but it turns out there is a mathematical abstraction behind this: torsors. Roman Cheplyaka has blogged about torsors and dates, which Haskell programmers may find more accessible.

You can contact me via email at ollie@ocharles.org.uk or tweet to me @acid2. I share almost all of my work at GitHub. This post is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.

I accept Bitcoin donations: 14SsYeM3dmcUxj3cLz7JBQnhNdhg7dUiJn