[Haskell-cafe] Can't do basic time operations with System.Time

John Goerzen jgoerzen at complete.org
Fri Jan 21 09:31:09 EST 2005

On Fri, Jan 21, 2005 at 01:56:23PM -0000, Simon Marlow wrote:
> I can't tell what's wrong here, if anything (general brokenness in the
> System.Time API notwithstanding).  Ah *lightbuilb* - I think I see: you
> want to take a calendar time without any TZ information, assume it is a
> time at the current geographical location, and produce a CalendarTime
> for that.  Hmm, it looks like the current API doesn't support that -
> toClockTime assumes you know the timezone.  That's a shame.

That is exactly correct.

The other point that I discovered after reading the Haskell library report is
that ClockTime is supposed to be an opaque data type; that is, the trick
of getting the seconds out of the (TOD Integer Integer) in GHC is
non-portable.  (Reference: http://www.haskell.org/onlinelibrary/time.html) 

So I wound up writing one myself:

> There's one way that's almost correct: take your initial calendar time,
> set ctTZ to your non-DST UTC offset, then call toClockTime and
> toCalendarTime on the result, and you'll get an appropriate ctTZ for
> that time.  Plug the new ctTZ into the original time, and toClockTime
> again (you can shortcut this by adjusting the ClockTime directly).  I'll
> leave it up to you how to handle the boundaries properly :-)

I arrived at approximately the same conclusion last night and defined
the boundaries as unimportant :-)

I checked the following code into MissingH.  I would be pleased if you
could take it for fptools.  I should note that I am using an Integer
rather than an Int to represent seconds, since I think that is proper
given the size of values we might be encountering.  If you do take it
for fptools, you could probably rewrite normalizeTimeDiff to use the
very similar code in timeDiffToSecs (shameless almost-stealing here <g>)

I've written a few unit tests for this and it seems to be doing the
right thing (where the Right Thing is measured by what the C library is



module MissingH.Time(
import System.Time

{- | January 1, 1970, midnight, UTC, represented as a CalendarTime. -}
epoch :: CalendarTime
epoch = CalendarTime { ctYear = 1970, ctMonth = January,
                       ctDay = 1, ctHour = 0, ctMin = 0, ctSec = 0,
                       ctPicosec = 0, ctWDay = Thursday, ctYDay = 0,
                       ctTZName = "UTC", ctTZ = 0, ctIsDST = False}

{- | Converts the specified CalendarTime (see System.Time) to seconds-since-epoch time.

This conversion does respect the timezone specified on the input object.
If you want a conversion from UTC, specify ctTZ = 0 and ctIsDST = False.

When called like that, the behavior is equivolent to the GNU C function
timegm().  Unlike the C library, Haskell's CalendarTime supports
timezone information, so if such information is specified, it will impact
the result.

timegm :: CalendarTime -> Integer
timegm ct =
    timeDiffToSecs (diffClockTimes (toClockTime ct) (toClockTime epoch))

{- | Converts the specified CalendarTime (see System.Time) to 
seconds-since-epoch format.

The input CalendarTime is assumed to be the time as given in your local
timezone.  All timezone and DST fields in the object are ignored.

This behavior is equivolent to the timelocal() and mktime() functions that
C programmers are accustomed to.

Please note that the behavior for this function during the hour immediately
before or after a DST switchover may produce a result with a different hour
than you expect.

timelocal :: CalendarTime -> IO Integer
timelocal ct =
    do guessct <- toCalendarTime guesscl
       let newct = ct {ctTZ = ctTZ guessct}
       return $ timegm newct
    where guesscl = toClockTime ct
{- | Converts the given timeDiff to the number of seconds it represents. 

Uses the same algorithm as normalizeTimeDiff in GHC. -}
timeDiffToSecs :: TimeDiff -> Integer
timeDiffToSecs td = 
    (fromIntegral $ tdSec td) +
    60 * ((fromIntegral $ tdMin td) +
          60 * ((fromIntegral $ tdHour td) +
                24 * ((fromIntegral $ tdDay td) +
                      30 * ((fromIntegral $ tdMonth td) +
                            365 * (fromIntegral $ tdYear td)))))

More information about the Haskell-Cafe mailing list