
Recently I've been working on an iCalendar parser implementation with support for recurrence rules. For the recurrence logic, I've been relying on Chris Heller's excellent time-recurrence package. Unfortunately, it seems we have both encountered great difficulty in cleanly handling time zones. For example, as seen in the case of the CalendarTime LocalTime instance[1], Chris does the following, instance CalendarTimeConvertible LocalTime where toCalendarTime (LocalTime day t) = CalendarTime (fromEnum $ todSec t) (todMin t) (todHour t) d (toEnum m) y weekDay yearDay tz where (y, m, d, weekDay, yearDay) = dayInfo day tz = unsafePerformIO getCurrentTimeZone Ouch. Unfortunately, I have been entirely unable to come up with any better way to deal with timezones in a referentially transparent way. Passing the current timezone as an argument seems heavy-handed and reveals the leakiness of the CalendarTimeConvertible abstraction. Short of making toCalendarTime an action, can anyone see a suitable way of refactoring CalendarTimeConvertible to avoid unsafe IO? Perhaps a TimeZoneInfo monad is in order, exposing lookup of arbitrary (through timezone-olson) as well the current timezone? It seems like this would be inconvenient and overkill, however. Ideas? - Ben [1] https://github.com/hellertime/time-recurrence/blob/master/src/Data/Time/Cale...

Ben Gamari wrote:
Recently I've been working on an iCalendar parser implementation with support for recurrence rules. For the recurrence logic, I've been relying on Chris Heller's excellent time-recurrence package. Unfortunately, it seems we have both encountered great difficulty in cleanly handling time zones... tz = unsafePerformIO getCurrentTimeZone Ouch... Passing the current timezone as an argument seems heavy-handed and reveals the leakiness of the CalendarTimeConvertible abstraction. Short of making toCalendarTime an action, can anyone see a suitable way of refactoring CalendarTimeConvertible to avoid unsafe IO?
Well, timezone info definitely is something from the outside world, and it even changes over time (very slowly). So it's IO. Yet just about all of what your library does is pure. So passing timezone info as a parameter to pure functions is the most straightforward approach. You would pass the timezone as a parameter to all pure functions that need it. If the need for the tz parameter is ubiquitous, it might then be worth it to use a pure reader monad of some kind. As a convenience, you might provide one or more functions that do the timezone lookup, do the pure calculation, and return the result in IO. All of that does require refactoring CalendarTimeConvertible though, yes. I think the use of unsafePerformIO is a pretty sure sign that the design of CalendarTimeConvertible could use some improvement.
Perhaps a TimeZoneInfo monad is in order, exposing lookup of arbitrary (through timezone-olson) as well the current timezone? It seems like this would be inconvenient and overkill, however.
I agree. This whole calculation is pure, really. It would be a shame to force the entire thing into an impure monad just because of timezones. Regards, Yitz

Glad to hear you have found use for my library, always nice to see something you produce find purpose other than your own.
I rationalized the use of unsafePerformIO in the library by deciding that it was only being used in the case of a LocalTime instance (a user could always stick to UTCTime and avoid the issues) and that unless the machine running the program using time-recurrence was traveling across timezones (and the system was updating that fact) the program would essentially be referentially transparent :)
That said, I'm curious to hear what opinions those with more Haskell knowledge than myself have so add to this question.
-Chris
On Nov 6, 2011, at 2:42 PM, Ben Gamari
Recently I've been working on an iCalendar parser implementation with support for recurrence rules. For the recurrence logic, I've been relying on Chris Heller's excellent time-recurrence package. Unfortunately, it seems we have both encountered great difficulty in cleanly handling time zones. For example, as seen in the case of the CalendarTime LocalTime instance[1], Chris does the following,
instance CalendarTimeConvertible LocalTime where toCalendarTime (LocalTime day t) = CalendarTime (fromEnum $ todSec t) (todMin t) (todHour t) d (toEnum m) y weekDay yearDay tz where (y, m, d, weekDay, yearDay) = dayInfo day tz = unsafePerformIO getCurrentTimeZone
Ouch. Unfortunately, I have been entirely unable to come up with any better way to deal with timezones in a referentially transparent way. Passing the current timezone as an argument seems heavy-handed and reveals the leakiness of the CalendarTimeConvertible abstraction. Short of making toCalendarTime an action, can anyone see a suitable way of refactoring CalendarTimeConvertible to avoid unsafe IO?
Perhaps a TimeZoneInfo monad is in order, exposing lookup of arbitrary (through timezone-olson) as well the current timezone? It seems like this would be inconvenient and overkill, however.
Ideas?
- Ben
[1] https://github.com/hellertime/time-recurrence/blob/master/src/Data/Time/Cale...

On Sun, Nov 6, 2011 at 17:25, Heller Time
I rationalized the use of unsafePerformIO in the library by deciding that it was only being used in the case of a LocalTime instance (a user could always stick to UTCTime and avoid the issues) and that unless the machine running the program using time-recurrence was traveling across timezones (and the system was updating that fact) the program would essentially be referentially transparent :)
There's also late-night hackers doing stuff when DST changes (as it did in the US last night and Europe a week ago). -- brandon s allbery allbery.b@gmail.com wandering unix systems administrator (available) (412) 475-9364 vm/sms

An all to real point that I had overlooked inadvertently. I think adding a timezone parameter would be a fine change to the API. I'll look at it this evening, I've been meaning t
Chris Heller
SAS - Advanced Analytics
Teragram Research & Development
phone: 1-617-576-6800 x54237
mobile: 1-617-460-3643
email: heller@teragram.com
On Nov 6, 2011, at 5:28 PM, Brandon Allbery
On Sun, Nov 6, 2011 at 17:25, Heller Time
wrote: I rationalized the use of unsafePerformIO in the library by deciding that it was only being used in the case of a LocalTime instance (a user could always stick to UTCTime and avoid the issues) and that unless the machine running the program using time-recurrence was traveling across timezones (and the system was updating that fact) the program would essentially be referentially transparent :) There's also late-night hackers doing stuff when DST changes (as it did in the US last night and Europe a week ago).
-- brandon s allbery allbery.b@gmail.com wandering unix systems administrator (available) (412) 475-9364 vm/sms

On Sun, 2011-11-06 at 17:25 -0500, Heller Time wrote:
unless the machine running the program using time-recurrence was traveling across timezones (and the system was updating that fact)
Note that this is *not* an unusual situation at all these days. DST was already mentioned, but also note that more and more software is running on mobile devices that do frequently update their time zone information. Unpredictably breaking code when this occurs is going to get a lot worse when people starting building Haskell for their Android and iOS phones, as we're very very close to seeing happen. -- Chris

Last night I modified the CalendarTimeConvertible type class. I added
a function toCalendarTimeWithTimeZone, which adds a time-zone
parameter, and eliminates the need for the unsafePerformIO in the
LocalTime derivation.
However this morning I realized that this was not the right approach.
The problem is that LocalTime is under-defined, and the correct
solution is to in fact not derive CalendarTimeConvertible for the
LocalTime type.
Instead one should think of a CalendarTime as meaningless without a
time-zone, and therefore you cannot derive from
CalendarTimeConvertible without one. This means users should stick to
either the UTCTime derivation of CalendarTimeConvertible, or the
ZonedTime derivation (which is in fact a LocalTime plus a TimeZone as
desired).
-Chris
On Mon, Nov 7, 2011 at 2:56 AM, Chris Smith
On Sun, 2011-11-06 at 17:25 -0500, Heller Time wrote:
unless the machine running the program using time-recurrence was traveling across timezones (and the system was updating that fact)
Note that this is *not* an unusual situation at all these days. DST was already mentioned, but also note that more and more software is running on mobile devices that do frequently update their time zone information. Unpredictably breaking code when this occurs is going to get a lot worse when people starting building Haskell for their Android and iOS phones, as we're very very close to seeing happen.
-- Chris
participants (6)
-
Ben Gamari
-
Brandon Allbery
-
Chris Heller
-
Chris Smith
-
Heller Time
-
Yitzchak Gale