Hi Hugs-team,

I found several divergences of the module  Time  of Hugs (v. Nov 
2002 for Linux) from the Haskell Report which can't be found neither
in the list of known Hugs bugs nor in the list of differences from 
Haskell 98 (the latter is also too old and claims that  Time  is not 
yet available).

1. The report specifies that the field  ctSec  of datatype 
CalendarTime  must be exported outside the module. In Hugs, this field 
is missing from the exports list.

2. The report requires (see the end of the penultimate paragraph 
before the library starts) that " toClockTime l  converts  l  [...] 
ignoring the contents of the  ctWDay ,  ctYDay ,  ctTZName ,  and  
ctIsDST  fields". Hugs, however, does not ignore  ctIsDST .

3. Additionally, the behaviour of the function  diffClockTime  is 
strange in Hugs. In the returned record, at most two fields are 
non-zero:  tdSec  and  tdPicosec . Thereby,  tdSec  is of type  Int  
which leads to incorrect value in the case of big time differences. 
The report is amazingly tongue-tied at this point, so it is even 
difficult to argue that your implementation is diverging. It seems 
natural to think however that this behaviour is not what was expected. 
I solved this problem by changing the type of  tdSec  to be  Integer . 
But this created a new divergence from the report.

4. There seem to be problems with dates before year 1970.

I attach my version of  Time.hs  to this mail, perhaps it helps you 
somewhat (the places of change are documented with today's date).
(Handling dates before 1970 has not been added there.)

The report on  Time  is really funny. It specifies that 
"Pre-Gregorian dates are inaccurate". So if your implementation 
handles pre-Gregorian dates accurately then it does not implement the 
Haskell 98 language correctly :)

Best regards,
H�rmel Nestra
(researcher of University of Tartu, Estonia)
--
-- Implementation of Haskell 98's Time module. Suitable for use
-- with Hugs98.
--
module Time
        ( ClockTime(..)         -- non-standard, report says its abstract.
                                -- instances: Eq, Ord
                                -- instances: Show  (non-standard)
        , Month( January
               , February
               , March
               , April
               , May
               , June
               , July
               , August
               , September
               , October
               , November
               , December
               )                -- instances: Eq, Ord, Enum, Bounded
                                -- instances: Ix, Read, Show
        , Day( Sunday
             , Monday
             , Tuesday
             , Wednesday
             , Thursday
             , Friday
             , Saturday)        -- instances: Eq, Ord, Enum, Bounded
                                -- instances: Ix, Read, Show
        , CalendarTime( CalendarTime
                      , ctYear
                      , ctMonth
                      , ctDay
                      , ctHour
                      , ctMin
                      , ctSec --added 2003-08-26
                      , ctPicosec
                      , ctWDay
                      , ctYDay
                      , ctTZName
                      , ctTZ
                      , ctIsDST
                      )         -- instances: Eq, Ord, Read, Show
        , TimeDiff( TimeDiff
                  , tdYear
                  , tdMonth
                  , tdDay
                  , tdHour
                  , tdMin
                  , tdSec
                  , tdPicosec
                  )             -- instances: Eq, Ord, Read, Show

        , getClockTime          -- :: IO ClockTime
        , addToClockTime        -- :: TimeDiff  -> ClockTime -> ClockTime
        , diffClockTimes        -- :: ClockTime -> ClockTime -> TimeDiff

        , toCalendarTime        -- :: ClockTime -> IO CalendarTime
        , toUTCTime             -- :: ClockTime -> CalendarTime
        , toClockTime           -- :: CalendarTime -> ClockTime

        , calendarTimeToString  -- :: CalendarTime -> String
        , formatCalendarTime    -- :: TimeLocale -> String -> CalendarTime -> String
        
          -- NON-STANDARD (but also provided by GHC impl)
        , noTimeDiff            -- :: TimeDiff
        ) where

import Locale
import Char ( intToDigit )
import IOExts ( unsafePerformIO )
import Ix ( Ix )

data ClockTime 
  = TOD Integer  -- secs
        Integer  -- micro (10^-6) secs [0, 999999]
    deriving ( Eq, Ord )


-- Definitions of Month, Day, ClockTime, TimeDiff - straight out of the report:
data Month 
  = January
  | February
  | March
  | April
  | May
  | June
  | July
  | August
  | September
  | October
  | November
  | December
    deriving (Eq, Ord, Enum, Bounded, Ix, Read, Show)

data Day
  = Sunday
  | Monday
  | Tuesday
  | Wednesday
  | Thursday 
  | Friday
  | Saturday
    deriving (Eq, Ord, Enum, Bounded, Ix, Read, Show)

data CalendarTime 
  = CalendarTime 
        { ctYear                          :: Int
        , ctMonth                         :: Month
        , ctDay, ctHour, ctMin, ctSec     :: Int
        , ctPicosec                       :: Integer
        , ctWDay                          :: Day
        , ctYDay                          :: Int
        , ctTZName                        :: String
        , ctTZ                            :: Int
        , ctIsDST                         :: Bool
        } deriving (Eq, Ord, Read, Show)

data TimeDiff 
  = TimeDiff 
        { tdYear, tdMonth, tdDay :: Int
        , tdHour, tdMin   :: Int --modified 2003-08-26
        , tdSec, tdPicosec              :: Integer --modified 2003-08-26
        } deriving (Eq, Ord, Read, Show)

noTimeDiff=TimeDiff 0 0 0
                    0 0 0
                    0

precision = 1000000 --added 2003-08-26

getClockTime :: IO ClockTime
getClockTime = do
  (s,micros) <- getClockTimePrim 
  return (TOD (fromIntegral s) (fromIntegral micros))

primitive getClockTimePrim :: IO (Int,Int)

toClockTime :: CalendarTime -> ClockTime
toClockTime (CalendarTime yr mon mday
                          hour min sec _
                          _ _ _ tz _) = unsafePerformIO $ do --modified 2003-08-26
  s <- toClockTimePrim (yr-1900) (fromEnum mon) mday
                       hour min sec
                       tz 0 --modified 2003-08-26
  return (TOD (fromIntegral s) 0)

primitive toClockTimePrim :: Int -> Int -> Int
                          -> Int -> Int -> Int
                          -> Int -> Int -> IO Int

toUTCTime :: ClockTime -> CalendarTime
toUTCTime ct = unsafePerformIO (toCalTime True ct)

toCalendarTime :: ClockTime -> IO CalendarTime
toCalendarTime = toCalTime False

toCalTime :: Bool -> ClockTime -> IO CalendarTime
toCalTime toUTC (TOD s msecs)
  | (s > fromIntegral (maxBound :: Int)) || 
    (s < fromIntegral (minBound :: Int))
  = error ((if toUTC then "toUTCTime: " else "toCalendarTime: ") ++
           "clock secs out of range")
  | otherwise = do
    (sec,min,hour,mday,mon,year,wday,yday,isdst,zone,off) <- 
                toCalTimePrim (if toUTC then 1 else 0) (fromIntegral s)
    return (CalendarTime{ ctYear=1900+year
                        , ctMonth=toEnum mon
                        , ctDay=mday
                        , ctHour=hour
                        , ctMin=min
                        , ctSec=sec
                        , ctPicosec=msecs*1000*1000
                        , ctWDay=toEnum wday
                        , ctYDay=yday
                        , ctTZName=(if toUTC then "UTC" else zone)
                        , ctTZ=(if toUTC then 0 else off)
                        , ctIsDST=not toUTC && (isdst/=0)
                        })

primitive toCalTimePrim :: Int -> Int -> IO 
(Int,Int,Int,Int,Int,Int,Int,Int,Int,String,Int)

-- non-standard Show instance, but worth it..? (provided by GHC too).
instance Show ClockTime where
  show ct = calendarTimeToString (unsafePerformIO (toCalendarTime ct))

--adds days, hours, minutes and seconds first and months and years after that 
2003-08-26
addToClockTime :: TimeDiff -> ClockTime -> ClockTime
addToClockTime (TimeDiff year mon day hour min sec psec)
               (TOD csecs msecs) = 
        let
          (r_yr, r_mon) = mon `divMod` 12 --modified 2003-08-26
          
          psecToMSec ps = ps `div` precision --modified 2003-08-26

          secOff = --rhs modified 2003-08-26
            sec + 60 * (toInteger min +
            60 * (toInteger hour + 24 * toInteger day)) + exSec
          (exSec,msecs') = (msecs + psecToMSec psec) `quotRem` precision --modified 
2003-08-26
          
          new_mon = fromEnum (ctMonth cal) + r_mon
          (yr_diff , month') --declaration rewritten 2003-08-26
            = new_mon `divMod` 12

          year' = ctYear cal + year + r_yr + yr_diff
          cal = toUTCTime(TOD (csecs + secOff) msecs')
        in
        toClockTime cal{ctMonth=toEnum month', ctYear=year'} --modified 2003-08-26

diffClockTimes :: ClockTime -> ClockTime -> TimeDiff
diffClockTimes (TOD s1 ms1) (TOD s2 ms2) --rhs rewritten 2003-08-26
  = let
      (carry , frac)
        = (ms1 - ms2) `divMod` precision
    in
        noTimeDiff{ tdSec = s1 - s2 + carry
                  , tdPicosec= 1000 * 1000 * frac
                  }

-- formatting CalendarTimes.
calendarTimeToString    :: CalendarTime -> String
calendarTimeToString    =  formatCalendarTime defaultTimeLocale "%c"

formatCalendarTime :: TimeLocale -> String -> CalendarTime -> String
formatCalendarTime l fmt ct@(CalendarTime year mon day hour min sec sdec 
                                           wday yday tzname _ _) =
        doFmt fmt
  where doFmt ('%':c:cs) = decode c ++ doFmt cs
        doFmt (c:cs) = c : doFmt cs
        doFmt "" = ""

        decode 'A' = fst (wDays l  !! fromEnum wday)
        decode 'a' = snd (wDays l  !! fromEnum wday)
        decode 'B' = fst (months l !! fromEnum mon)
        decode 'b' = snd (months l !! fromEnum mon)
        decode 'h' = snd (months l !! fromEnum mon)
        decode 'C' = show2 (year `quot` 100)
        decode 'c' = doFmt (dateTimeFmt l)
        decode 'D' = doFmt "%m/%d/%y"
        decode 'd' = show2 day
        decode 'e' = show2' day
        decode 'H' = show2 hour
        decode 'I' = show2 (to12 hour)
        decode 'j' = show3 yday
        decode 'k' = show2' hour
        decode 'l' = show2' (to12 hour)
        decode 'M' = show2 min
        decode 'm' = show2 (fromEnum mon+1)
        decode 'n' = "\n"
        decode 'p' = (if hour < 12 then fst else snd) (amPm l)
        decode 'R' = doFmt "%H:%M"
        decode 'r' = doFmt (time12Fmt l)
        decode 'T' = doFmt "%H:%M:%S"
        decode 't' = "\t"
        decode 'S' = show2 sec
        decode 's' = show (case toClockTime ct of { (TOD s _) -> s })
        decode 'U' = show2 ((yday + 7 - fromEnum wday) `div` 7)
        decode 'u' = show (let n = fromEnum wday in 
                           if n == 0 then 7 else n)
        decode 'V' = 
            let (week, days) = 
                   (yday + 7 - if fromEnum wday > 0 then 
                               fromEnum wday - 1 else 6) `divMod` 7
            in  show2 (if days >= 4 then
                          week+1 
                       else if week == 0 then 53 else week)

        decode 'W' = 
            show2 ((yday + 7 - if fromEnum wday > 0 then 
                               fromEnum wday - 1 else 6) `div` 7)
        decode 'w' = show (fromEnum wday)
        decode 'X' = doFmt (timeFmt l)
        decode 'x' = doFmt (dateFmt l)
        decode 'Y' = show year
        decode 'y' = show2 (year `rem` 100)
        decode 'Z' = tzname
        decode '%' = "%"
        decode c   = [c]

show2, show2', show3 :: Int -> String
show2 x = [intToDigit (x `quot` 10), intToDigit (x `rem` 10)]

show2' x = if x < 10 then [ ' ', intToDigit x] else show2 x

show3 x = intToDigit (x `quot` 100) : show2 (x `rem` 100)

to12 :: Int -> Int
to12 h = let h' = h `mod` 12 in if h' == 0 then 12 else h'

Reply via email to