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:
for_
, which I use for pretty much everything these daystime
packagetz
packageI 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.