
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 doing). Thanks, John module MissingH.Time( timelocal, timegm, timeDiffToSecs, epoch ) where 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)))))