time-diff

2018-04-12

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?

Overview

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

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.

Step 1: A sequence of UTC days

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.

Step 2: Time zone in effect at a given UTC time

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.

The full program

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.

Tags: Haskell, Time zone, Bothell, Haworth

All content © 2018 Richard Cook. All rights reserved.