A cheatsheet to the time library

Posted on September 16, 2019

In a way, date and time handling is one place where Haskell’s strict type system shines. Handling timezones correctly, along with the distinction between timestamps and calendar dates is an endless source of bugs. Being precise about which exact type of time data your program takes in and spits out can completely obliviate that problem.

The de-facto standard date and time library in Haskell, time, however, can be a little obtuse to get started with. I always feel like I have to reread the documentation every time I need to write date-related code. It’s not even obvious how to do the most common operation, getting the current time. Producing things like the current month, day of the week, and so on, requires a surprising number of type conversions. Here’s a cheatsheet for the most common use cases for the time library.

All examples were testing on time version 1.8.0.2.

Importing and using

Add to your package.yaml/cabal file:

In modules where you need to work with date/time data:

The “primitive” type in the time library is UTCTime. If you need to do anything involving the current time, it will most likely involve some conversion from/to this type. As the name suggests, it’s a timestamp in the UTC timezone. The exact accuracy will depend on your operating system, but it will be accurate to at least the second and is capable of being accurate up to the picosecond.

Getting the current time

Working with dates

If all you need is calendar day-level fidelity, then you want the Day type. Note that the UTCTime type has a utctDay accessor for getting the calendar day, if you need the current date.

Working with time of day

If you’re instead looking for an intra-day timestamp, regardless of which particular calendar day it is, you’ll want the fittingly-named TimeOfDay type.

There don’t seem to be any functions for adding/diffing TimeOfDay values. You can either construct updated TimeOfDay values directly, or if you’d rather not deal with rollover manually, do your manipulations on UTCTime values and convert to a TimeOfDay once you need to output or display something.

Working with timezones and ZonedTime

The main types for working with local times are the ZonedTime and TimeZone types. As you might expect, the TimeZone type represents an offset from UTC; a ZonedTime is essentially a timestamp paired with a TimeZone.

Given a TimeZone and a UTCTime, you can convert into a ZonedTime. Since most of the time you’ll just be working in the current (i.e. system) timezone, time provides some convenience functions that don’t require you to provide a timezone.

How do you actually get a TimeZone object? There are functions for getting the current timezone, but the ability to get a specific timezone is oddly anemic.

TimeZone has a Read instance which can parse certain timezone abbreviations. It’s rather unreliable, though, and will silently produce UTC if it can’t figure out the timezone, so be careful with it.

EDIT: Neil Mayhew informed me that if you want to work in non-system timezones, you should look at the tz package. Thanks, Neil!

Getting subcomponents of local times

We’ve only seen how to get the year/month/day/hour/etc. starting from a UTCTime, but what if we want those values from a ZonedTime? We can’t convert to a UTCTime first; that would defeat the purpose. Thankfully, if we pop open the fields of ZonedTime, we get a LocalTime object:

Creating UTCTime values directly

Since this requires creating a bunch of intermediate values, here’s a useful hepler function:

Formatting dates and times

time provides a single function, formatTime, for formatting all time types into text. It takes a format string as its second argument for determining which components of the time type to show.

Here are the format directives you probably care about:

  • %y: year, 2-digit abbreviation
  • %Y: year, full
  • %m: month, 2-digit
  • %d: day of month, 2-digit
  • %H: hour, 2-digit, 24-hour clock
  • %I: hour, 2-digit, 12-hour clock
  • %M: minute, 2-digit
  • %S: second, 2-digit
  • %p: AM/PM
  • %z: timezone offset
  • %Z: timezone name

If you don’t want the zero-padding of the specific component, you can add a dash between the percent sign and the directive, e.g. a format string of "%H:%M" would give "04:10" but a format string of "%-H:%M" would give "4:10".

The full list of directives is listed in the documentation for formatTime.

Formatting localization

If you need to output formatted times in languages other than English, formatTime provides some rudimentary support for that through its TimeLocale parameter. We passed in defaultTimeLocale above, but you can create your own as well. It’s essentially a mapping for the non-numeric components, like month names, day-of-week names, and symbols for AM/PM.

For example, we could create a locale for formatting Japanese days-of-week like so:

If you’re just working with English dates, you probably don’t need to mess with TimeLocales. You can define a handy alias that always uses the default locale:

Parsing dates and times

For taking date strings as input, time provides the parseTimeM function. As the name suggests, it gives the output time value wrapped in a monad of your choice, although in practice you’ll probably only use Maybe. It uses the same format string format as formatTime.

Note that the constraint is Monad m, not MonadFail m; this can bite you if you try to use Either!

Again, it would likely make sense to make an alias for parseTimeM to suit your own situation; say, using the default locale and restricting the monad to Maybe.

Performing arithmetic on time data

If you need to add and subtract times, you have two options based on whether you need to do arithmetic at day-level fidelity or timestamp-level fidelity.

If you want to do arithmetic on days, months, and years, work within the Day type and use the addX functions defined in Data.Time.Calendar.

There’s also addGregorianMonthsRollOver and addGregorianYearsRollOver functions in addition to the addXXXClip functions. They increment (or decrement) the year/month, then if the day of month would be out of bounds for that month, they ‘roll over’ the extra days into the next month.

You almost certainly want to use the clipping functions.

If you want to do arithmetic on hours, minutes, and seconds, work within the UTCTime type and use the functions in Data.Time.Clock.

Remember that you can do day/month/year arithmetic on UTCTimes by mapping over their inner Day values.


Generally, you should be doing all your calculations in UTCTime, only converting to “derivative” types like Day, TimeOfDay, or ZonedTime if you (a) need the subcomponents of them like the current day of the week, or (b) for outputting to some human-visible result.

Since Haskell is pure, you’ll have to be careful within date-related code about how you actually get timestamps. Getting the current time clearly isn’t a pure operation, so some thought is required to avoid ‘infecting’ all of your code with IO when working with time. But that’s a topic for another time. With just this, you should be able to write perfectly useful datetime code in Haskell.

Have fun working with time!

Found this useful? Got a comment to make? Talk to me!


You might also like


Before you close that tab...