The time library is a common source of confusion for newHaskell users, I've noticed. With no disrespect to the authors intended, I feellike the existing tutorials don't do a very good job of conveying the essenceof the library so I'm going to give it a shot myself.
Here's a cheat sheet with the important functions I mention in this tutorial.There are others, but some of them I actively disrecommend you from using, andsome of them are just convenient wrappers around the ones I mention.
This will probably make no sense until you've read the rest of the tutorial.When you have, you might want to bookmark this page for a quick reference.
Dates:
Day
is the type representing a datefromGregorianValid :: Integer -> Int -> Int -> Maybe Day
creates a date value, by taking a year–month–day triple and returningNothing
if the date is invalidaddDays :: Integer -> Day -> Day
can add and subtract a number of days to a date value; supply a negativeInteger
for subtractiondiffDays :: Day -> Day -> Integer
gives you the number of days that lie between two datesaddGregorianMonthsClip :: Integer -> Day -> Day
moves a date a number of months forward or backward, adjusting it when necessary to stay within short monthstoGregorian :: Day -> (Integer, Int, Int)
converts a date back to a year–month–day triple, but it shouldn't be something you do too often
Universal time:
UTCTime
is the type representing a point in universal time; it is built from a date value and the number of seconds into that daygetCurrentTime :: IO UTCTime
returns the current universal timeaddUTCTime :: NominalDiffTime -> UTCTime -> UTCTime
lets you compute new times based on an existing point in time, by adding the number of seconds specified in the first argument to the second argumentdiffUTCTime :: UTCTime -> UTCTime -> NominalDiffTime
computes the number of seconds that have passed between the two points in time specified- A
NominalDiffTime
is a regular number, but can not be combined withInteger
,Double
and other numbers without conversion realToFrac
converts any number to aNominalDiffTime
- You can pattern match on a
UTCTime
to extract the date component and do maths on it separately
Local time:
ZonedTime
is the type of a local time valuezonedTimeToUTC :: ZonedTime -> UTCTime
converts a local time value to universal timeutcToZonedTime :: TimeZone -> UTCTime -> ZonedTime
converts a universal time value to local time, given a timezone for the local time- A
TimeZone
is a value representing the users timezone; it can be pattern matched out from a local time value getZonedTime :: IO ZonedTime
gets the current local time for the user
Formatting:
formatTime :: FormatTime t => TimeLocale -> String -> t -> String
formats a date or time value according to the format string and locale specified- A
TimeLocale
is a value that contains, among other things, translations of the weekday names to the users language defaultTimeLocale
contains the English names of weekdays among other things; can be modified if you don't want to create a time locale from scratch
It's important to know that the time library is designed with safety as apriority. Knowing this gives you understanding for why some things seem to needextra steps.
As an example, it is common to accidentally treat a local time value (10o'clock on a Sunday morning in Sweden as I'm writing this) with UTC (strictly8:00 everywhere in the world right now.) UTC stands for'universal coordinated time' and is the same everywhere on Earth, while localtime can vary as you travel from place to place due to daylight savings time,time zones and other silly human irregularities.
The Haskell Cheatsheetfrom Amazon.com, formatted especially for the Kindle eReader. The guide itself is written as a 'literate' Haskell file, meaning it is directly executable. That file is available when using the latter two options above. Haskell Cheat Sheet 😁. No, this is not a math paper by any stretch of the imagination. A cheat sheet is a pithy reference, not an introductory document. This certainly is one, and you only knowing the basics doesn’t make you qualified to judge it.
If you accidentally mix local time and UTC, you can get bad, impredictablebehaviour. Imagine agreeing that you should call someone at 10.00 UTC, andthen you call them 10.00 your time! They'll either not take your callor be a bit upset with you, if it comes at a bad time for them.
The time library prevents this sort of confusion entirely. If you try totreat a local time value as UTC the compiler will refuse your program. If youwant to convert a local time value to UTC you have to ask for it and providea timezone so it knows how to do the conversion.
We can think of the time library as having three different kinds ofdata:
Date values, which indicate a specific date (like '25april, 2009') and are timezone independent. These are represented in Haskellwith the
Day
type.Universal time values, represented with two types: the
UTCTime
type represents a date and time combination ('25 april,2009, 13:52:31') and theNominalDiffTime
type represents adifference between twoUTCTime
s.Local time values, represented by the
ZonedTime
type which links a local time with the relevanttimezone.
You are not meant to use local time values too much. Local time is acomplicated and difficult thing to handle. The local time stuff is usefulwhen you want to interface with users, but internally your application shoulduse universal time for calculations and coordination.
Generally, you'll use universal time for most of the things you do, but sinceUTCTime
uses the Day
type, I thought we might look atdate values first.
These three things should cover the most common use cases, but will notcover all of them. Time management is really complicated, and toinclude every possible use case in this tutorial is not possible withoutturning it into a book. This is a beginners' tutorial to the timelibrary, and does not intend to be a comprehensive guide to time management incomputer programs.
Oh, and by the way: unless I say otherwise, all the functions andconstructors I talk about are available by importing justData.Time
.
Sometimes you don't care about the exact point in time something happened.This blog, for example, only has a date attached to articles – not a time. Idon't care about micro-managing which hour and minute an article is publishedat, so I just set a date on it and then publish it at UTC midnight thatdate.
Date values are also useful when something happens for an entire day, ora span of days. Like, Christmas Day in western cultures is on December 25th.There is no time specification that it starts at 03:00 and ends at 14:30 – itspans the entire day.
Construction
Constructing a date value from a year–month–day triple is easy. The functionfromGregorianValid
is your friend. It returns a MaybeDay
value which is Nothing
if you enter an invalid date.
In other words,
but,
because the February 31, 2014 is not a valid date. So be sure to check thereturn value of fromGregorianValid
, especially if the date isentered by a user.
What's all this Gregorian business and who was Gregory? Gregory was a popein the 14th century who accepted a proposal by Lilius (astronomer and polymath)to modernise the calendar. The one they used at the time slowly drifted out ofsync with the actual seasons, and the calendar Lilius proposed takes longer todrift out of sync with the actual seasons. The calendar we use in the westtoday is the one Lilius proposed. It's called Gregorian after the pope whodecided that it was time to switch to it.
(The calendar we used before the Gregorian reform was called the Juliancalendar. You can guess who decided on that one …)
Destructuring
If you for some reason want to convert a Day
value back toa year–month–day triple, you do that with the toGregorian
function. However, I'd argue that wanting to do that often is a signof bad code design somewhere.
If you want to plain convert a date to a string, you can do that with theregular show
function:
If you want to print a date in a custom format, you can do that too withouthaving to convert it to numbers again. We'll get to that later.
Calculation
There are a few ways you may want to calculate with dates. The most obviousone is saying something like 'what date is it 7 days from now?'
This tells us that 7 days from today (i.e. in a week) it'll be the 6th ofSeptember. If I want to know how many days it is until my birthday, we canfigure that out too.
These are probably the most common ways you'll want to calculate with days,but there are a couple more 'business-y' ways of doing it. For example, imagineyou want to bill a customer a month from now. Do you add 31 days to the current date?Or 30? What if it's January and the next month only has 28 days? The customerwill expect to be billed in February, but if you add 30 days to January 31,you'll end up in March!
For that particular situation, we have theaddGregorianMonthsClip
function. If you use that to add one monthto May 31, it will attempt to find June 31, but since that date is invalid itback down to June 30, the closest valid date in June:
You could easily use this to build a list of the dates you want to bill thecustomer for the next year:
Note how 2016 must be a leap year, because it expects to be able to billthe customer on February 29. Had 2016 not been a leap year, it would have saidFebruary 28 instead.
If we add time to these date values, we get universal time.
Universal time, or UTC, is an attempt to standardise and coordinate timeacross the whole world. Writing code to deal with time is never fun, butwhenever you have to do it, I strongly recommend using universal time for allyour calculations, and then converting to and from local time only as a laststep.
As you will see shortly, universal time is an extension of theDay
type you have already learned how to manipulate. This meansthat what you learned about the Day
type can be used whenmanipulating universal time as well.
Construction
In the time library, universal time is represented through theUTCTime
type. It is simply built up from a Day
andhow many seconds into that day the clock shows.
So we can signify midnight today as
If we wanted 12:34:56 today then we could do
Finally, the easiest way to get a UTCTime
value is to just getthe current universal time!
Note that for obvious reasons, this is an I/O action so it needs to be runlike any other IO
computation.
Calculation
The two most basic forms of calculation with universal time are similarto the ones we saw with dates: adding time, and figuring out the differencebetween two times.
With UTCTime
, we express the time to add as a number of seconds,and the difference between two UTCTime
s is also returned as anumber of seconds.
When I started writing this tutorial, I ran
and if we look at start
now, we see that I started writing ataround 8 o'clock universal time:
How many seconds ago was that? Changing mac address for wifi. Let's find out!
Apparently that was around 12,200 seconds ago, or 3.4 hours. Don't get toocaught up with the NominalDiffTime
name. It is just a numberrepresenting the seconds that have passed between two points in universaltime. For the most part, it can be treated like any other number.
Difftime Calculation
However, you might notice one pecularity. You can't add a regularInteger
or Double
to a NominalDiffTime
,because the compiler will complain that they are of different types. So youmight end up here:
The solution is to convert our regular Integer
toa NominalDiffTime
, which we do (slightly unintuitively) withrealToFrac
.
The same thing applies if we want to add an Integer
(orDouble
) to a UTCTime
:
an error which is easily solved by converting with realToFrac
.
Destructuring
Haskell Syntax
Since a UTCTime
value is just a regular data type with twofields (one for the date and one for the time of day) it is quite easy topick it apart and put it together again. Imagine, for example, that we wantedto create a UTCTime
value for the same time, but tomorrow.
We get the current time and use pattern matching to extract the dayand time separately. We then create a new UTCTime
value from the'day + 1' and the same time.
I strongly recommend to not manipulate the time part of aUTCTime
other than to set it to a value you know is safe.Don't add or subtract hours or minutes from it, because if you accidentallycreate an invalid value you will get unexpected results:
If you need to add or subtract hours and minutes, use theaddUTCTime
function with positive/negativeNominalDiffTime
s instead, and you'll get the expectedbehaviour.
Haskell Cheat Sheet
It bears repeating: do not just put in an unvalidatedNominalDiffTime
into a UTCTime
constructor. UseaddUTCTime
instead.
This is going to be a short segment! Local time is represented in thetime library as the ZonedTime
type. AZonedTime
value consists of two parts: a time and a time zone.These can be pattern matched out, but you shouldn't ever touch the time value.If you need to poke at the time, convert to a UTCTime
first, and then poke atthe UTCTime
the way you've already learned.
Extracting the timezone from a ZonedTime
value can be useful, ifyou need the TimeZone
value for conversions and the like.
You convert a ZonedTime
value into a UTCTime
value with thezonedTimeToUTC
function. If you have a TimeZone
valuecorresponding to the users timezone, you can convert a UTCTime
back to aZonedTime
value with the utcToZonedTime
function.
Here's an example of how you can (avoid to) manipulate local time values.
Since it's probably a good idea, I'll quickly touch on converting time values tostrings.
The basic tool for formatting time is the formatTime
function.As arguments, it takes a TimeLocale
, which we will mostly ignore;a String
with directives for how to format the time; and at
value which is something like a Day
,UTCTime
or ZonedTime
.
If you specify a completely numeric format, the locale doesn't matter atall, so we will just use the defaultTimeLocale
provided. Keep inmind though that if you want to represent the time with non-English words, like'Lundi 8 Sept 2014', then you'll have to modify the default locale with thecorrect translations.
The specification for the time format directives is on theHaddock documentation for the formatTime
function. Here's anexample on how to use it:
So that's it, I think. The time library can be a bit overwhelmingif you are used to time libraries in other languages. The reason is the safety.The time library is really solidly built, and when used correctly,you can reap the benefits of that.
The key to good usage is to stick to universal time for as much as you can,and only do local time when you are interfacing with users. As soon as yourprogram gets its hands on a local time value, convert it to universal time.
