2 % (c) The GRASP/AQUA Project, Glasgow University, 1995-97
4 \section[Time]{Haskell 1.4 Time of Day Library}
6 The {\em Time} library provides standard functionality for
7 clock times, including timezone information (i.e, the functionality of
8 "time.h", adapted to the Haskell environment), It follows RFC 1129 in
9 its use of Coordinated Universal Time (UTC).
12 {-# OPTIONS -#include "cbits/timezone.h" -#include "cbits/stgio.h" #-}
18 ClockTime(..), -- non-standard, lib. report gives this as abstract
24 timeDiffToString, -- non-standard
25 formatTimeDiff, -- non-standard
27 CalendarTime(CalendarTime),
45 import PrelPack ( unpackCString )
49 import Char ( intToDigit )
54 One way to partition and give name to chunks of a year and a week:
58 = January | February | March | April
59 | May | June | July | August
60 | September | October | November | December
61 deriving (Eq, Ord, Enum, Bounded, Ix, Read, Show)
64 = Sunday | Monday | Tuesday | Wednesday
65 | Thursday | Friday | Saturday
66 deriving (Eq, Ord, Enum, Bounded, Ix, Read, Show)
70 @ClockTime@ is an abstract type, used for the internal clock time.
71 Clock times may be compared, converted to strings, or converted to an
72 external calendar time @CalendarTime@.
76 -- I believe Int64 is more than big enough.
77 -- In fact, I think one of Int32 or Word32 would do. - ADR
78 data ClockTime = TOD Int64 Int64 deriving (Eq, Ord)
80 data ClockTime = TOD Integer Integer deriving (Eq, Ord)
84 When a @ClockTime@ is shown, it is converted to a string of the form
85 @"Mon Nov 28 21:45:41 GMT 1994"@.
87 For now, we are restricted to roughly:
88 Fri Dec 13 20:45:52 1901 through Tue Jan 19 03:14:07 2038, because
89 we use the C library routines based on 32 bit integers.
93 #warning Show ClockTime is bogus
94 instance Show ClockTime
96 instance Show ClockTime where
97 showsPrec p (TOD sec@(J# a# s# d#) nsec) =
98 showString $ unsafePerformIO $ do
99 buf <- allocChars 38 -- exactly enough for error message
100 str <- _ccall_ showTime (I# s#) d# buf
101 return (unpackCString str)
103 showList = showList__ (showsPrec 0)
108 @CalendarTime@ is a user-readable and manipulable
109 representation of the internal $ClockTime$ type. The
110 numeric fields have the following ranges.
116 year -maxInt .. maxInt [Pre-Gregorian dates are inaccurate]
117 mon 0 .. 11 [Jan = 0, Dec = 11]
121 sec 0 .. 61 [Allows for two leap seconds]
122 picosec 0 .. (10^12)-1 [This could be over-precise?]
123 wday 0 .. 6 [Sunday = 0, Saturday = 6]
124 yday 0 .. 365 [364 in non-Leap years]
125 tz -43200 .. 43200 [Variation from UTC in seconds]
128 The {\em tzname} field is the name of the time zone. The {\em isdst}
129 field indicates whether Daylight Savings Time would be in effect.
143 ctPicosec :: Integer,
151 deriving (Eq,Ord,Read,Show)
155 The @TimeDiff@ type records the difference between two clock times in
168 tdPicosec :: Int64 -- not standard
170 tdPicosec :: Integer -- not standard
173 deriving (Eq,Ord,Read,Show)
176 @getClockTime@ returns the current time in its internal representation.
180 getClockTime :: IO ClockTime
184 rc <- prim_getClockTime i1 i2
187 sec <- cvtUnsigned i1
188 nsec <- cvtUnsigned i2
189 return (TOD sec (nsec * 1000))
191 constructErrorAndFail "getClockTime"
193 malloc1 = primNewByteArray sizeof_int64
194 cvtUnsigned arr = primReadInt64Array arr 0
196 getClockTime :: IO ClockTime
200 rc <- _ccall_ getClockTime i1 i2
203 sec <- cvtUnsigned i1
204 nsec <- cvtUnsigned i2
205 return (TOD sec (nsec * 1000))
207 constructErrorAndFail "getClockTime"
209 malloc1 = IO $ \ s# ->
210 case newIntArray# 1# s# of
212 (# s2#, MutableByteArray bottom barr# #)
214 -- The C routine fills in an unsigned word. We don't have
215 -- `unsigned2Integer#,' so we freeze the data bits and use them
216 -- for an MP_INT structure. Note that zero is still handled specially,
217 -- although (J# 1# 1# (ptr to 0#)) is probably acceptable to gmp.
219 cvtUnsigned (MutableByteArray _ arr#) = IO $ \ s# ->
220 case readIntArray# arr# 0# s# of
224 else case unsafeFreezeByteArray# arr# s2# of
225 (# s3#, frozen# #) ->
226 (# s3#, J# 1# 1# frozen# #)
230 @addToClockTime@ {\em d} {\em t} adds a time difference {\em d} and a
231 clock time {\em t} to yield a new clock time. The difference {\em d}
232 may be either positive or negative. @[diffClockTimes@ {\em t1} {\em
233 t2} returns the difference between two clock times {\em t1} and {\em
239 addToClockTime :: TimeDiff -> ClockTime -> ClockTime
240 addToClockTime (TimeDiff year mon day hour min sec psec)
241 (TOD c_sec c_psec) = unsafePerformIO $ do
242 res <- allocWords sizeof_int64
243 rc <- prim_toClockSec year mon day hour min sec 0 res
246 diff_sec <- primReadInt64Array res 0
248 return (TOD (c_sec + diff_sec) (c_psec + diff_psec))
250 error "Time.addToClockTime: can't perform conversion of TimeDiff"
252 addToClockTime :: TimeDiff -> ClockTime -> ClockTime
253 addToClockTime (TimeDiff year mon day hour min sec psec)
254 (TOD c_sec c_psec) = unsafePerformIO $ do
255 res <- allocWords (``sizeof(time_t)'')
256 ptr <- _ccall_ toClockSec year mon day hour min sec 0 res
258 if ptr /= (``0''::Addr)
260 diff_sec = (int2Integer (indexIntOffAddr# ptr# 0#))
263 return (TOD (c_sec + diff_sec) (c_psec + diff_psec))
265 error "Time.addToClockTime: can't perform conversion of TimeDiff"
268 diffClockTimes :: ClockTime -> ClockTime -> TimeDiff
269 diffClockTimes tod_a tod_b =
271 CalendarTime year_a mon_a day_a hour_a min_a sec_a psec_a _ _ _ _ _ = toUTCTime tod_a
272 CalendarTime year_b mon_b day_b hour_b min_b sec_b psec_b _ _ _ _ _ = toUTCTime tod_b
274 TimeDiff (year_a - year_b)
283 @toCalendarTime@ {\em t} converts {\em t} to a local time, modified by
284 the current timezone and daylight savings time settings. @toUTCTime@
285 {\em t} converts {\em t} into UTC time. @toClockTime@ {\em l}
286 converts {\em l} into the corresponding internal @ClockTime@. The
287 {\em wday}, {\em yday}, {\em tzname}, and {\em isdst} fields are
292 toCalendarTime :: ClockTime -> IO CalendarTime
293 toCalendarTime (TOD sec psec) = do
294 res <- allocWords sizeof_int64
295 zoneNm <- allocChars 32
296 prim_SETZONE res zoneNm
297 rc <- prim_toLocalTime sec res
299 then constructErrorAndFail "Time.toCalendarTime: out of range"
301 sec <- get_tm_sec res
302 min <- get_tm_min res
303 hour <- get_tm_hour res
304 mday <- get_tm_mday res
305 mon <- get_tm_mon res
306 year <- get_tm_year res
307 wday <- get_tm_wday res
308 yday <- get_tm_yday res
309 isdst <- get_tm_isdst res
310 zone <- prim_ZONE res
311 tz <- prim_GMTOFF res
312 tzname <- primUnpackCString zone
313 return (CalendarTime (1900+year) mon mday hour min sec psec
314 (toEnum wday) yday tzname tz (isdst /= 0))
316 toUTCTime :: ClockTime -> CalendarTime
317 toUTCTime (TOD sec psec) = unsafePerformIO $ do
318 res <- allocWords sizeof_int64
319 zoneNm <- allocChars 32
320 prim_SETZONE res zoneNm
321 rc <- prim_toUTCTime sec res
323 then error "Time.toUTCTime: out of range"
325 sec <- get_tm_sec res
326 min <- get_tm_min res
327 hour <- get_tm_hour res
328 mday <- get_tm_mday res
329 mon <- get_tm_mon res
330 year <- get_tm_year res
331 wday <- get_tm_wday res
332 yday <- get_tm_yday res
333 return (CalendarTime (1900+year) mon mday hour min sec psec
334 (toEnum wday) yday "UTC" 0 False)
336 toClockTime :: CalendarTime -> ClockTime
337 toClockTime (CalendarTime year mon mday hour min sec psec wday yday tzname tz isdst) =
338 if psec < 0 || psec > 999999999999 then
339 error "Time.toClockTime: picoseconds out of range"
340 else if tz < -43200 || tz > 43200 then
341 error "Time.toClockTime: timezone offset out of range"
344 res <- allocWords sizeof_int64
345 rc <- prim_toClockSec year mon mday hour min sec isDst res
348 tm <- primReadInt64Array res 0
350 else error "Time.toClockTime: can't perform conversion"
353 isDst = if isdst then (1::Int) else 0
355 toCalendarTime :: ClockTime -> IO CalendarTime
356 toCalendarTime (TOD sec@(J# a# s# d#) psec) = do
357 res <- allocWords (``sizeof(struct tm)''::Int)
358 zoneNm <- allocChars 32
359 _casm_ ``SETZONE((struct tm *)%0,(char *)%1); '' res zoneNm
360 tm <- _ccall_ toLocalTime (I# s#) d# res
361 if tm == (``NULL''::Addr)
362 then constructErrorAndFail "Time.toCalendarTime: out of range"
364 sec <- _casm_ ``%r = ((struct tm *)%0)->tm_sec;'' tm
365 min <- _casm_ ``%r = ((struct tm *)%0)->tm_min;'' tm
366 hour <- _casm_ ``%r = ((struct tm *)%0)->tm_hour;'' tm
367 mday <- _casm_ ``%r = ((struct tm *)%0)->tm_mday;'' tm
368 mon <- _casm_ ``%r = ((struct tm *)%0)->tm_mon;'' tm
369 year <- _casm_ ``%r = ((struct tm *)%0)->tm_year;'' tm
370 wday <- _casm_ ``%r = ((struct tm *)%0)->tm_wday;'' tm
371 yday <- _casm_ ``%r = ((struct tm *)%0)->tm_yday;'' tm
372 isdst <- _casm_ ``%r = ((struct tm *)%0)->tm_isdst;'' tm
373 zone <- _ccall_ ZONE tm
374 tz <- _ccall_ GMTOFF tm
375 let tzname = unpackCString zone
376 return (CalendarTime (1900+year) mon mday hour min sec psec
377 (toEnum wday) yday tzname tz (isdst /= 0))
379 toUTCTime :: ClockTime -> CalendarTime
380 toUTCTime (TOD sec@(J# a# s# d#) psec) = unsafePerformIO $ do
381 res <- allocWords (``sizeof(struct tm)''::Int)
382 zoneNm <- allocChars 32
383 _casm_ ``SETZONE((struct tm *)%0,(char *)%1); '' res zoneNm
384 tm <- _ccall_ toUTCTime (I# s#) d# res
385 if tm == (``NULL''::Addr)
386 then error "Time.toUTCTime: out of range"
388 sec <- _casm_ ``%r = ((struct tm *)%0)->tm_sec;'' tm
389 min <- _casm_ ``%r = ((struct tm *)%0)->tm_min;'' tm
390 hour <- _casm_ ``%r = ((struct tm *)%0)->tm_hour;'' tm
391 mday <- _casm_ ``%r = ((struct tm *)%0)->tm_mday;'' tm
392 mon <- _casm_ ``%r = ((struct tm *)%0)->tm_mon;'' tm
393 year <- _casm_ ``%r = ((struct tm *)%0)->tm_year;'' tm
394 wday <- _casm_ ``%r = ((struct tm *)%0)->tm_wday;'' tm
395 yday <- _casm_ ``%r = ((struct tm *)%0)->tm_yday;'' tm
396 return (CalendarTime (1900+year) mon mday hour min sec psec
397 (toEnum wday) yday "UTC" 0 False)
399 toClockTime :: CalendarTime -> ClockTime
400 toClockTime (CalendarTime year mon mday hour min sec psec wday yday tzname tz isdst) =
401 if psec < 0 || psec > 999999999999 then
402 error "Time.toClockTime: picoseconds out of range"
403 else if tz < -43200 || tz > 43200 then
404 error "Time.toClockTime: timezone offset out of range"
407 res <- allocWords (``sizeof(time_t)'')
408 ptr <- _ccall_ toClockSec year mon mday hour min sec isDst res
411 then return (TOD (int2Integer (indexIntOffAddr# ptr# 0#)) psec)
412 else error "Time.toClockTime: can't perform conversion"
415 isDst = if isdst then (1::Int) else 0
419 bottom = error "Time.bottom"
422 -- (copied from PosixUtil, for now)
423 -- Allocate a mutable array of characters with no indices.
426 allocChars :: Int -> IO (PrimMutableByteArray RealWorld)
427 allocChars size = primNewByteArray size
429 -- Allocate a mutable array of words with no indices
431 allocWords :: Int -> IO (PrimMutableByteArray RealWorld)
432 allocWords size = primNewByteArray size
434 allocChars :: Int -> IO (MutableByteArray RealWorld ())
435 allocChars (I# size#) = IO $ \ s# ->
436 case newCharArray# size# s# of
438 (# s2#, MutableByteArray bot barr# #)
440 bot = error "Time.allocChars"
442 -- Allocate a mutable array of words with no indices
444 allocWords :: Int -> IO (MutableByteArray RealWorld ())
445 allocWords (I# size#) = IO $ \ s# ->
446 case newIntArray# size# s# of
448 (# s2#, MutableByteArray bot barr# #)
450 bot = error "Time.allocWords"
455 calendarTimeToString :: CalendarTime -> String
456 calendarTimeToString = formatCalendarTime defaultTimeLocale "%c"
458 formatCalendarTime :: TimeLocale -> String -> CalendarTime -> String
459 formatCalendarTime l fmt ct@(CalendarTime year mon day hour min sec sdec
460 wday yday tzname _ _) =
462 where doFmt ('%':c:cs) = decode c ++ doFmt cs
463 doFmt (c:cs) = c : doFmt cs
466 decode 'A' = fst (wDays l !! fromEnum wday)
467 decode 'a' = snd (wDays l !! fromEnum wday)
468 decode 'B' = fst (months l !! fromEnum mon)
469 decode 'b' = snd (months l !! fromEnum mon)
470 decode 'h' = snd (months l !! fromEnum mon)
471 decode 'C' = show2 (year `quot` 100)
472 decode 'c' = doFmt (dateTimeFmt l)
473 decode 'D' = doFmt "%m/%d/%y"
474 decode 'd' = show2 day
475 decode 'e' = show2' day
476 decode 'H' = show2 hour
477 decode 'I' = show2 (to12 hour)
478 decode 'j' = show3 yday
479 decode 'k' = show2' hour
480 decode 'l' = show2' (to12 hour)
481 decode 'M' = show2 min
482 decode 'm' = show2 (fromEnum mon+1)
484 decode 'p' = (if hour < 12 then fst else snd) (amPm l)
485 decode 'R' = doFmt "%H:%M"
486 decode 'r' = doFmt (time12Fmt l)
487 decode 'T' = doFmt "%H:%M:%S"
489 decode 'S' = show2 sec
490 decode 's' = show2 sec -- Implementation-dependent, sez the lib doc..
491 decode 'U' = show2 ((yday + 7 - fromEnum wday) `div` 7)
492 decode 'u' = show (let n = fromEnum wday in
493 if n == 0 then 7 else n)
496 (yday + 7 - if fromEnum wday > 0 then
497 fromEnum wday - 1 else 6) `divMod` 7
498 in show2 (if days >= 4 then
500 else if week == 0 then 53 else week)
503 show2 ((yday + 7 - if fromEnum wday > 0 then
504 fromEnum wday - 1 else 6) `div` 7)
505 decode 'w' = show (fromEnum wday)
506 decode 'X' = doFmt (timeFmt l)
507 decode 'x' = doFmt (dateFmt l)
508 decode 'Y' = show year
509 decode 'y' = show2 (year `rem` 100)
514 show2, show2', show3 :: Int -> String
515 show2 x = [intToDigit (x `quot` 10), intToDigit (x `rem` 10)]
517 show2' x = if x < 10 then [ ' ', intToDigit x] else show2 x
519 show3 x = intToDigit (x `quot` 100) : show2 (x `rem` 100)
521 to12 h = let h' = h `mod` 12 in if h == 0 then 12 else h
525 timeDiffToString :: TimeDiff -> String
526 timeDiffToString = formatTimeDiff defaultTimeLocale "%c"
528 formatTimeDiff :: TimeLocale -> String -> TimeDiff -> String
529 formatTimeDiff l fmt ct@(TimeDiff year month day hour min sec psec)
533 doFmt ('%':c:cs) = decode c ++ doFmt cs
534 doFmt (c:cs) = c : doFmt cs
538 'B' -> fst (months l !! fromEnum month)
539 'b' -> snd (months l !! fromEnum month)
540 'h' -> snd (months l !! fromEnum month)
541 'C' -> show2 (year `quot` 100)
542 'D' -> doFmt "%m/%d/%y"
546 'I' -> show2 (to12 hour)
548 'l' -> show2' (to12 hour)
550 'm' -> show2 (fromEnum month + 1)
552 'p' -> (if hour < 12 then fst else snd) (amPm l)
554 'r' -> doFmt (time12Fmt l)
555 'T' -> doFmt "%H:%M:%S"
558 's' -> show2 sec -- Implementation-dependent, sez the lib doc..
559 'X' -> doFmt (timeFmt l)
560 'x' -> doFmt (dateFmt l)
562 'y' -> show2 (year `rem` 100)
570 foreign import stdcall "libHS_cbits.so" "get_tm_sec" get_tm_sec :: Bytes -> IO Int
571 foreign import stdcall "libHS_cbits.so" "get_tm_min" get_tm_min :: Bytes -> IO Int
572 foreign import stdcall "libHS_cbits.so" "get_tm_hour" get_tm_hour :: Bytes -> IO Int
573 foreign import stdcall "libHS_cbits.so" "get_tm_mday" get_tm_mday :: Bytes -> IO Int
574 foreign import stdcall "libHS_cbits.so" "get_tm_mon" get_tm_mon :: Bytes -> IO Int
575 foreign import stdcall "libHS_cbits.so" "get_tm_year" get_tm_year :: Bytes -> IO Int
576 foreign import stdcall "libHS_cbits.so" "get_tm_wday" get_tm_wday :: Bytes -> IO Int
577 foreign import stdcall "libHS_cbits.so" "get_tm_yday" get_tm_yday :: Bytes -> IO Int
578 foreign import stdcall "libHS_cbits.so" "get_tm_isdst" get_tm_isdst :: Bytes -> IO Int
580 foreign import stdcall "libHS_cbits.so" "prim_ZONE" prim_ZONE :: Bytes -> IO Addr
581 foreign import stdcall "libHS_cbits.so" "prim_GMTOFF" prim_GMTOFF :: Bytes -> IO Int
583 foreign import stdcall "libHS_cbits.so" "prim_SETZONE" prim_SETZONE :: Bytes -> Bytes -> IO Int
585 foreign import stdcall "libHS_cbits.so" "sizeof_word" sizeof_word :: Int
586 foreign import stdcall "libHS_cbits.so" "sizeof_struct_tm" sizeof_struct_tm :: Int
587 foreign import stdcall "libHS_cbits.so" "sizeof_time_t" sizeof_time_t :: Int
589 -- believed to be at least 1 bit (the sign bit!) bigger than sizeof_time_t
593 foreign import stdcall "libHS_cbits.so" "prim_getClockTime" prim_getClockTime :: Bytes -> Bytes -> IO Int
594 foreign import stdcall "libHS_cbits.so" "prim_toClockSec" prim_toClockSec :: Int -> Int -> Int -> Int -> Int -> Int -> Int -> Bytes -> IO Int
595 foreign import stdcall "libHS_cbits.so" "prim_toLocalTime" prim_toLocalTime :: Int64 -> Bytes -> IO Int
596 foreign import stdcall "libHS_cbits.so" "prim_toUTCTime" prim_toUTCTime :: Int64 -> Bytes -> IO Int