I live in Bothell, Washington. I want to call my mother in Haworth, West Yorkshire. But what time is it there?

That’s not a terribly difficult question to get an answer for with Google. However, there is a much more interesting and nuanced question:

What was the time difference between Bothell, Washington and Haworth, West Yorkshire over time?

To answer this, we’ll use Haskell, of course. Specifically, we’re going to use the following:

- My new favourite function,
`for_`

, which I use for pretty much everything these days - The
`time`

package - The
`tz`

package

I want to generate a list of every day in a given year along with the effective time zones in both Bothell and Haworth and the corresponding difference in hours and minutes between the times (at midnight) in these two locations.

We can make a single UTC day as follows:

That’s 1 January 2018, just in case you were wondering.

I scratched my head thinking about how to generate a sequence for a little while. At first I was planning to `map`

the `fromGregorian`

function over various sequences of numbers. However, this requires knowledge of the number of days in each month in each year and lots of similar nastiness. How else to do this? Well, fortunately for me, `time`

also exposes an `addDays`

function. With this, we should be able to generate a list of calendar days by mapping some function of this function and our `startDay`

over a list of numbers:

or

These two formulations are equivalent, the first being the point-free style version of the latter. `pointfree.io`

is your friend for exploring automatic conversions of Haskell expressions into point-free form.

**Update: Let’s use Enum instead.**

It turns out that `Day`

has an instance for the `Enum`

type class which makes generating a sequence of days trivial:

Thanks to `lgastako`

for this tip.

The `tz`

package exposes functions like `timeZoneForPOSIX`

and `diffForPOSIX`

which sound like they may do what we need. Unfortunately, both functions deal in terms of `Int64`

values and the documentation for the package does not explain what this `Int64`

is directly. So, I had to take a look at the code. After doing that, I figured out that I need the `Int64`

that is the result type of the `utcTimeToInt64`

function in the `Data.Time.Zones.Internal`

module.

This leads to the following function:

`Minutes`

is a `newtype`

wrapper around `Int`

.

This function takes a `TZ`

(which is a time zone database entry for a given geographical location), a `UTCTime`

(which we can derive from our Gregorian `Day`

from above) and returns the time difference from UTC in minutes as well as the name of the time zone in effect (encoded using the `TimeZone`

) type.

Since the hidden `utcTimeToInt64`

function is critical to interoperability between `tz`

and `time`

(I think), I’ll probably file an issue against the package and submit a pull request to make this function part of the public API. If I get time, of course. That was a terrible pun.

Now, all we need to do is apply our `tzOffsetInfo`

function to a stream of `UTCTime`

instances deriving from our stream of `Day`

instances and print the results out. I present the full program here:

There, that was a quick introduction to time and time zones in Haskell.

Content © 2024 Richard Cook. All rights reserved.