Hi!

There is a bug in GHC's implementation of Time.toClockTime.  For example,
the following program:

----------------------
module Main where
import Time

t = toClockTime $ CalendarTime { 
    ctYear = 2000, ctMonth = February, ctDay = 9,
    ctHour = 13, ctMin = 22, ctSec = 20, ctPicosec = 0,
    ctWDay = Monday, ctYDay = 0, 
    ctTZName = "UTC", ctTZ = 0, ctIsDST = False }

delta = TimeDiff 0 0 1 0 0 0 0

t' = addToClockTime delta t

main = do putStrLn $ calendarTimeToString $ toUTCTime t
          putStrLn $ calendarTimeToString $ toUTCTime t'
          putStrLn $ show $ diffClockTimes t' t
----------------------

gives the following output if the TZ enviroment variable is set to "CET":

----------------------
Tue Feb  9 12:22:20 UTC 2000
Wed Feb 10 11:22:20 UTC 2000
TimeDiff{tdYear=0,tdMonth=0,tdDay=1,tdHour=(-1),tdMin=0,tdSec=0,tdPicosec=0}
----------------------

That is, t is 1 hour earlier than the given time, and t' is 1 day minus 1
hour later than t instead of 1 day.

The reason is that toClockTime (which is used by addToClockTime) uses the C
library function mktime(), which converts *local* time to Unix
time.  toClockTime also uses the ctIsDST field of CalendarTime which should
be ignored according to the Library Report.  Instead, the ctTZ field of
CalendarTime should determine the time zone.

I propose the following fix:

*** /home/eelco/ghc/fptools/ghc/lib/std/Time.lhs        Mon Dec 20 11:34:35 1999
--- ghc/lib/std/Time.lhs        Wed Feb  9 17:22:11 2000
***************
*** 411,435 ****
  
              return (CalendarTime (1900+year) month mday hour min sec psec 
                          (toEnum wday) yday "UTC" 0 False)
  
  toClockTime :: CalendarTime -> ClockTime
! toClockTime (CalendarTime year mon mday hour min sec psec _wday _yday _tzname tz 
isdst) =
      if psec < 0 || psec > 999999999999 then
          error "Time.toClockTime: picoseconds out of range"
      else if tz < -43200 || tz > 43200 then
          error "Time.toClockTime: timezone offset out of range"
      else
!         unsafePerformIO ( do
!           res <- malloc1
!           rc  <- toClockSec year (fromEnum mon) mday hour min sec isDst res
!             if rc /= 0
!              then do
!              i <- cvtUnsigned res
!              return (TOD i psec)
!            else error "Time.toClockTime: can't perform conversion"
!         )
!     where
!      isDst = if isdst then (1::Int) else 0
  #endif
  
  
--- 411,441 ----
  
              return (CalendarTime (1900+year) month mday hour min sec psec 
                          (toEnum wday) yday "UTC" 0 False)
+               
+ daysInMonth :: [Int]          
+ daysInMonth = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ]
+ 
+ -- Number of leap days from 1970 up to and including {\em year}.
+ leapDays :: Int -> Int
+ leapDays year =
+     let l400 = (year - 1600) `div` 400
+         l100 = (year - 1900) `div` 100
+       l4   = (year - 1968) `div` 4
+     in l4 - l100 + l400
  
  toClockTime :: CalendarTime -> ClockTime
! toClockTime (CalendarTime year mon mday hour min sec psec _wday _yday _tzname tz 
_isdst) =
      if psec < 0 || psec > 999999999999 then
          error "Time.toClockTime: picoseconds out of range"
      else if tz < -43200 || tz > 43200 then
          error "Time.toClockTime: timezone offset out of range"
      else
!       let mon' = fromEnum mon -- 0 = january, ...
!           yday = mday - 1 + (sum $ take mon' daysInMonth)
!           leap = leapDays (if mon' >= 2 then year else year - 1)
!           days = fromInt (yday + leap + 365 * (year - 1970))
!           secs = 24 * 3600 * days + fromInt (3600 * hour + 60 * min + sec)
!       in TOD (secs - fromInt tz) psec
  #endif

  
Regards,

Eelco.

Reply via email to