2 % (c) The GRASP/AQUA Project, Glasgow University, 1995-99
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
25 , timeDiffToString -- non-standard
26 , formatTimeDiff -- non-standard
32 , calendarTimeToString
47 import PrelPack ( unpackCString, new_ps_array )
51 import Char ( intToDigit )
56 One way to partition and give name to chunks of a year and a week:
60 = January | February | March | April
61 | May | June | July | August
62 | September | October | November | December
63 deriving (Eq, Ord, Enum, Bounded, Ix, Read, Show)
66 = Sunday | Monday | Tuesday | Wednesday
67 | Thursday | Friday | Saturday
68 deriving (Eq, Ord, Enum, Bounded, Ix, Read, Show)
72 @ClockTime@ is an abstract type, used for the internal clock time.
73 Clock times may be compared, converted to strings, or converted to an
74 external calendar time @CalendarTime@.
78 -- I believe Int64 is more than big enough.
79 -- In fact, I think one of Int32 or Word32 would do. - ADR
80 data ClockTime = TOD Int64 Int64 deriving (Eq, Ord)
82 data ClockTime = TOD Integer Integer deriving (Eq, Ord)
86 When a @ClockTime@ is shown, it is converted to a string of the form
87 @"Mon Nov 28 21:45:41 GMT 1994"@.
89 For now, we are restricted to roughly:
90 Fri Dec 13 20:45:52 1901 through Tue Jan 19 03:14:07 2038, because
91 we use the C library routines based on 32 bit integers.
95 #warning Show ClockTime is bogus
96 instance Show ClockTime
98 instance Show ClockTime where
99 showsPrec p (TOD (S# i) _nsec) =
100 case int2Integer# i of (# s, d #) -> showsPrec p (TOD (J# s d) _nsec)
101 showsPrec _ (TOD (J# s# d#) _nsec) =
102 showString $ unsafePerformIO $ do
103 buf <- allocChars 38 -- exactly enough for error message
104 str <- showTime (I# s#) d# buf
105 return (unpackCString str)
107 showList = showList__ (showsPrec 0)
112 @CalendarTime@ is a user-readable and manipulable
113 representation of the internal $ClockTime$ type. The
114 numeric fields have the following ranges.
120 year -maxInt .. maxInt [Pre-Gregorian dates are inaccurate]
121 mon 0 .. 11 [Jan = 0, Dec = 11]
125 sec 0 .. 61 [Allows for two leap seconds]
126 picosec 0 .. (10^12)-1 [This could be over-precise?]
127 wday 0 .. 6 [Sunday = 0, Saturday = 6]
128 yday 0 .. 365 [364 in non-Leap years]
129 tz -43200 .. 43200 [Variation from UTC in seconds]
132 The {\em tzname} field is the name of the time zone. The {\em isdst}
133 field indicates whether Daylight Savings Time would be in effect.
147 ctPicosec :: Integer,
155 deriving (Eq,Ord,Read,Show)
159 The @TimeDiff@ type records the difference between two clock times in
172 tdPicosec :: Int64 -- not standard
174 tdPicosec :: Integer -- not standard
177 deriving (Eq,Ord,Read,Show)
180 @getClockTime@ returns the current time in its internal representation.
183 getClockTime :: IO ClockTime
187 rc <- primGetClockTime i1 i2
190 sec <- cvtUnsigned i1
191 nsec <- cvtUnsigned i2
192 return (TOD sec (nsec * 1000))
194 constructErrorAndFail "getClockTime"
197 malloc1 = primNewByteArray sizeof_int64
198 cvtUnsigned arr = primReadInt64Array arr 0
200 malloc1 = IO $ \ s# ->
201 case newIntArray# 1# s# of
203 (# s2#, MutableByteArray bottom barr# #)
205 -- The C routine fills in an unsigned word. We don't have
206 -- `unsigned2Integer#,' so we freeze the data bits and use them
207 -- for an MP_INT structure. Note that zero is still handled specially,
208 -- although (J# 1# (ptr to 0#)) is probably acceptable to gmp.
210 cvtUnsigned (MutableByteArray _ arr#) = IO $ \ s# ->
211 case readIntArray# arr# 0# s# of
215 else case unsafeFreezeByteArray# arr# s2# of
216 (# s3#, frozen# #) ->
217 (# s3#, J# 1# frozen# #)
221 @addToClockTime@ {\em d} {\em t} adds a time difference {\em d} and a
222 clock time {\em t} to yield a new clock time. The difference {\em d}
223 may be either positive or negative. @[diffClockTimes@ {\em t1} {\em
224 t2} returns the difference between two clock times {\em t1} and {\em
230 addToClockTime :: TimeDiff -> ClockTime -> ClockTime
231 addToClockTime (TimeDiff year mon day hour min sec psec)
232 (TOD c_sec c_psec) = unsafePerformIO $ do
233 res <- allocWords sizeof_int64
234 rc <- toClockSec year mon day hour min sec 0 res
237 diff_sec <- primReadInt64Array res 0
239 return (TOD (c_sec + diff_sec) (c_psec + diff_psec))
241 error "Time.addToClockTime: can't perform conversion of TimeDiff"
243 addToClockTime :: TimeDiff -> ClockTime -> ClockTime
244 addToClockTime (TimeDiff year mon day hour min sec psec)
245 (TOD c_sec c_psec) = unsafePerformIO $ do
246 res <- stToIO (newIntArray (0,sizeof_time_t))
247 rc <- toClockSec year mon day hour min sec (0::Int) res
250 diff_sec_i <- stToIO (readIntArray res 0)
252 diff_sec = int2Integer (case diff_sec_i of I# i# -> i#)
254 return (TOD (c_sec + diff_sec) (c_psec + diff_psec))
256 error "Time.addToClockTime: can't perform conversion of TimeDiff"
259 diffClockTimes :: ClockTime -> ClockTime -> TimeDiff
260 diffClockTimes tod_a tod_b =
262 CalendarTime year_a mon_a day_a hour_a min_a sec_a psec_a _ _ _ _ _ = toUTCTime tod_a
263 CalendarTime year_b mon_b day_b hour_b min_b sec_b psec_b _ _ _ _ _ = toUTCTime tod_b
265 TimeDiff (year_a - year_b)
274 @toCalendarTime@ {\em t} converts {\em t} to a local time, modified by
275 the current timezone and daylight savings time settings. @toUTCTime@
276 {\em t} converts {\em t} into UTC time. @toClockTime@ {\em l}
277 converts {\em l} into the corresponding internal @ClockTime@. The
278 {\em wday}, {\em yday}, {\em tzname}, and {\em isdst} fields are
283 toCalendarTime :: ClockTime -> IO CalendarTime
284 toCalendarTime (TOD sec psec) = do
285 res <- allocWords sizeof_int64
286 zoneNm <- allocChars 32
287 prim_SETZONE res zoneNm
288 rc <- prim_toLocalTime sec res
290 then constructErrorAndFail "Time.toCalendarTime: out of range"
292 sec <- get_tm_sec res
293 min <- get_tm_min res
294 hour <- get_tm_hour res
295 mday <- get_tm_mday res
296 mon <- get_tm_mon res
297 year <- get_tm_year res
298 wday <- get_tm_wday res
299 yday <- get_tm_yday res
300 isdst <- get_tm_isdst res
301 zone <- prim_ZONE res
302 tz <- prim_GMTOFF res
303 tzname <- primUnpackCString zone
304 return (CalendarTime (1900+year) mon mday hour min sec psec
305 (toEnum wday) yday tzname tz (isdst /= 0))
307 toUTCTime :: ClockTime -> CalendarTime
308 toUTCTime (TOD sec psec) = unsafePerformIO $ do
309 res <- allocWords sizeof_int64
310 zoneNm <- allocChars 32
311 prim_SETZONE res zoneNm
312 rc <- prim_toUTCTime sec res
314 then error "Time.toUTCTime: out of range"
316 sec <- get_tm_sec res
317 min <- get_tm_min res
318 hour <- get_tm_hour res
319 mday <- get_tm_mday res
320 mon <- get_tm_mon res
321 year <- get_tm_year res
322 wday <- get_tm_wday res
323 yday <- get_tm_yday res
324 return (CalendarTime (1900+year) mon mday hour min sec psec
325 (toEnum wday) yday "UTC" 0 False)
327 toClockTime :: CalendarTime -> ClockTime
328 toClockTime (CalendarTime year mon mday hour min sec psec wday yday tzname tz isdst) =
329 if psec < 0 || psec > 999999999999 then
330 error "Time.toClockTime: picoseconds out of range"
331 else if tz < -43200 || tz > 43200 then
332 error "Time.toClockTime: timezone offset out of range"
335 res <- allocWords sizeof_int64
336 rc <- toClockSec year mon mday hour min sec isDst res
339 tm <- primReadInt64Array res 0
341 else error "Time.toClockTime: can't perform conversion"
344 isDst = if isdst then (1::Int) else 0
346 toCalendarTime :: ClockTime -> IO CalendarTime
347 toCalendarTime (TOD (S# i) psec)
348 = case int2Integer# i of (# s, d #) -> toCalendarTime (TOD (J# s d) psec)
349 toCalendarTime (TOD (J# s# d#) psec) = do
350 res <- allocWords sizeof_struct_tm
351 zoneNm <- allocChars 32
352 prim_SETZONE res zoneNm
353 rc <- prim_toLocalTime (I# s#) d# res
355 then constructErrorAndFail "Time.toCalendarTime: out of range"
357 sec <- get_tm_sec res
358 min <- get_tm_min res
359 hour <- get_tm_hour res
360 mday <- get_tm_mday res
361 mon <- get_tm_mon res
362 year <- get_tm_year res
363 wday <- get_tm_wday res
364 yday <- get_tm_yday res
365 isdst <- get_tm_isdst res
368 let tzname = unpackCString zone
369 return (CalendarTime (1900+year) mon mday hour min sec psec
370 (toEnum wday) yday tzname tz (isdst /= (0::Int)))
372 toUTCTime :: ClockTime -> CalendarTime
373 toUTCTime (TOD (S# i) psec)
374 = case int2Integer# i of (# s, d #) -> toUTCTime (TOD (J# s d) psec)
375 toUTCTime (TOD (J# s# d#) psec) = unsafePerformIO $ do
376 res <- allocWords sizeof_struct_tm
377 zoneNm <- allocChars 32
378 prim_SETZONE res zoneNm
379 rc <- prim_toUTCTime (I# s#) d# res
381 then error "Time.toUTCTime: out of range"
383 sec <- get_tm_sec res
384 min <- get_tm_min res
385 hour <- get_tm_hour res
386 mday <- get_tm_mday res
387 mon <- get_tm_mon res
388 year <- get_tm_year res
389 wday <- get_tm_wday res
390 yday <- get_tm_yday res
391 return (CalendarTime (1900+year) mon mday hour min sec psec
392 (toEnum wday) yday "UTC" 0 False)
394 toClockTime :: CalendarTime -> ClockTime
395 toClockTime (CalendarTime year mon mday hour min sec psec _wday _yday _tzname tz isdst) =
396 if psec < 0 || psec > 999999999999 then
397 error "Time.toClockTime: picoseconds out of range"
398 else if tz < -43200 || tz > 43200 then
399 error "Time.toClockTime: timezone offset out of range"
402 res <- stToIO (newIntArray (0, sizeof_time_t))
403 rc <- toClockSec year mon mday hour min sec isDst res
406 i <- stToIO (readIntArray res 0)
407 return (TOD (int2Integer (case i of I# i# -> i#)) psec)
408 else error "Time.toClockTime: can't perform conversion"
411 isDst = if isdst then (1::Int) else 0
415 bottom = error "Time.bottom"
418 -- (copied from PosixUtil, for now)
419 -- Allocate a mutable array of characters with no indices.
422 allocChars :: Int -> IO (PrimMutableByteArray RealWorld)
423 allocChars size = primNewByteArray size
425 -- Allocate a mutable array of words with no indices
427 allocWords :: Int -> IO (PrimMutableByteArray RealWorld)
428 allocWords size = primNewByteArray size
430 allocChars :: Int -> IO (MutableByteArray RealWorld Int)
431 allocChars (I# size#) = stToIO (new_ps_array size#)
433 -- Allocate a mutable array of words with no indices
435 allocWords :: Int -> IO (MutableByteArray RealWorld Int)
436 allocWords (I# size#) = IO $ \ s# ->
437 case newIntArray# size# s# of
439 (# s2#, MutableByteArray bot barr# #)
441 bot = error "Time.allocWords"
446 calendarTimeToString :: CalendarTime -> String
447 calendarTimeToString = formatCalendarTime defaultTimeLocale "%c"
449 formatCalendarTime :: TimeLocale -> String -> CalendarTime -> String
450 formatCalendarTime l fmt (CalendarTime year mon day hour min sec _
451 wday yday tzname _ _) =
453 where doFmt ('%':c:cs) = decode c ++ doFmt cs
454 doFmt (c:cs) = c : doFmt cs
457 decode 'A' = fst (wDays l !! fromEnum wday) -- day of the week, full name
458 decode 'a' = snd (wDays l !! fromEnum wday) -- day of the week, abbrev.
459 decode 'B' = fst (months l !! fromEnum mon) -- month, full name
460 decode 'b' = snd (months l !! fromEnum mon) -- month, abbrev
461 decode 'h' = snd (months l !! fromEnum mon) -- ditto
462 decode 'C' = show2 (year `quot` 100) -- century
463 decode 'c' = doFmt (dateTimeFmt l) -- locale's data and time format.
464 decode 'D' = doFmt "%m/%d/%y"
465 decode 'd' = show2 day -- day of the month
466 decode 'e' = show2' day -- ditto, padded
467 decode 'H' = show2 hour -- hours, 24-hour clock, padded
468 decode 'I' = show2 (to12 hour) -- hours, 12-hour clock
469 decode 'j' = show3 yday -- day of the year
470 decode 'k' = show2' hour -- hours, 24-hour clock, no padding
471 decode 'l' = show2' (to12 hour) -- hours, 12-hour clock, no padding
472 decode 'M' = show2 min -- minutes
473 decode 'm' = show2 (fromEnum mon+1) -- numeric month
475 decode 'p' = (if hour < 12 then fst else snd) (amPm l) -- am or pm
476 decode 'R' = doFmt "%H:%M"
477 decode 'r' = doFmt (time12Fmt l)
478 decode 'T' = doFmt "%H:%M:%S"
480 decode 'S' = show2 sec -- seconds
481 decode 's' = show2 sec -- number of secs since Epoch. (ToDo.)
482 decode 'U' = show2 ((yday + 7 - fromEnum wday) `div` 7) -- week number, starting on Sunday.
483 decode 'u' = show (let n = fromEnum wday in -- numeric day of the week (1=Monday, 7=Sunday)
484 if n == 0 then 7 else n)
485 decode 'V' = -- week number (as per ISO-8601.)
486 let (week, days) = -- [yep, I've always wanted to be able to display that too.]
487 (yday + 7 - if fromEnum wday > 0 then
488 fromEnum wday - 1 else 6) `divMod` 7
489 in show2 (if days >= 4 then
491 else if week == 0 then 53 else week)
493 decode 'W' = -- week number, weeks starting on monday
494 show2 ((yday + 7 - if fromEnum wday > 0 then
495 fromEnum wday - 1 else 6) `div` 7)
496 decode 'w' = show (fromEnum wday) -- numeric day of the week, weeks starting on Sunday.
497 decode 'X' = doFmt (timeFmt l) -- locale's preferred way of printing time.
498 decode 'x' = doFmt (dateFmt l) -- locale's preferred way of printing dates.
499 decode 'Y' = show year -- year, including century.
500 decode 'y' = show2 (year `rem` 100) -- year, within century.
501 decode 'Z' = tzname -- timezone name
506 show2, show2', show3 :: Int -> String
507 show2 x = [intToDigit (x `quot` 10), intToDigit (x `rem` 10)]
509 show2' x = if x < 10 then [ ' ', intToDigit x] else show2 x
511 show3 x = intToDigit (x `quot` 100) : show2 (x `rem` 100)
514 to12 h = let h' = h `mod` 12 in if h' == 0 then 12 else h'
517 Useful extensions for formatting TimeDiffs.
520 timeDiffToString :: TimeDiff -> String
521 timeDiffToString = formatTimeDiff defaultTimeLocale "%c"
523 formatTimeDiff :: TimeLocale -> String -> TimeDiff -> String
524 formatTimeDiff l fmt (TimeDiff year month day hour min sec _)
528 doFmt ('%':c:cs) = decode c ++ doFmt cs
529 doFmt (c:cs) = c : doFmt cs
533 'B' -> fst (months l !! fromEnum month)
534 'b' -> snd (months l !! fromEnum month)
535 'h' -> snd (months l !! fromEnum month)
536 'C' -> show2 (year `quot` 100)
537 'D' -> doFmt "%m/%d/%y"
541 'I' -> show2 (to12 hour)
543 'l' -> show2' (to12 hour)
545 'm' -> show2 (fromEnum month + 1)
547 'p' -> (if hour < 12 then fst else snd) (amPm l)
549 'r' -> doFmt (time12Fmt l)
550 'T' -> doFmt "%H:%M:%S"
553 's' -> show2 sec -- Implementation-dependent, sez the lib doc..
554 'X' -> doFmt (timeFmt l)
555 'x' -> doFmt (dateFmt l)
557 'y' -> show2 (year `rem` 100)
564 foreign import "libHS_cbits" "get_tm_sec" get_tm_sec :: MBytes -> IO Int
565 foreign import "libHS_cbits" "get_tm_min" get_tm_min :: MBytes -> IO Int
566 foreign import "libHS_cbits" "get_tm_hour" get_tm_hour :: MBytes -> IO Int
567 foreign import "libHS_cbits" "get_tm_mday" get_tm_mday :: MBytes -> IO Int
568 foreign import "libHS_cbits" "get_tm_mon" get_tm_mon :: MBytes -> IO Int
569 foreign import "libHS_cbits" "get_tm_year" get_tm_year :: MBytes -> IO Int
570 foreign import "libHS_cbits" "get_tm_wday" get_tm_wday :: MBytes -> IO Int
571 foreign import "libHS_cbits" "get_tm_yday" get_tm_yday :: MBytes -> IO Int
572 foreign import "libHS_cbits" "get_tm_isdst" get_tm_isdst :: MBytes -> IO Int
574 foreign import "libHS_cbits" "prim_ZONE" prim_ZONE :: Bytes -> IO Addr
575 foreign import "libHS_cbits" "prim_GMTOFF" prim_GMTOFF :: Bytes -> IO Int
577 foreign import "libHS_cbits" "sizeof_struct_tm" sizeof_struct_tm :: Int
580 -- believed to be at least 1 bit (the sign bit!) bigger than sizeof_time_t
585 type MBytes = MutableByteArray RealWorld Int
587 foreign import "libHS_cbits" "sizeof_time_t" sizeof_time_t :: Int
589 foreign import "libHS_cbits" "prim_SETZONE" prim_SETZONE :: MBytes -> MBytes -> IO Int
591 foreign import "libHS_cbits" "prim_toLocalTime" prim_toLocalTime :: Int64 -> MBytes -> IO Int
592 foreign import "libHS_cbits" "prim_toUTCTime" prim_toUTCTime :: Int64 -> MBytes -> IO Int
594 foreign import "libHS_cbits" "toLocalTime" prim_toLocalTime :: Int -> Bytes -> MBytes -> IO Int
595 foreign import "libHS_cbits" "toUTCTime" prim_toUTCTime :: Int -> Bytes -> MBytes -> IO Int
598 foreign import "libHS_cbits" "get_ZONE" get_ZONE :: MBytes -> IO Addr
599 foreign import "libHS_cbits" "GMTOFF" get_GMTOFF :: MBytes -> IO Int
602 foreign import "libHS_cbits" "toClockSec"
603 toClockSec :: Int -> Int -> Int -> Int -> Int
604 -> Int -> Int -> MBytes -> IO Int
606 foreign import "libHS_cbits" "prim_getClockTime"
607 primGetClockTime :: MutableByteArray RealWorld Int
608 -> MutableByteArray RealWorld Int
610 foreign import "libHS_cbits" "showTime"
614 -> IO Addr{-packed C string -}