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" #-}
19 CalendarTime(CalendarTime),
21 ClockTime(..), -- non-standard, lib. report gives this as abstract
22 getClockTime, addToClockTime, diffClockTimes,
23 toCalendarTime, toUTCTime, toClockTime,
24 calendarTimeToString, formatCalendarTime
34 import Char (intToDigit)
35 import PackedString (unpackPS, packCBytesST)
40 One way to partition and give name to chunks of a year and a week:
44 = January | February | March | April
45 | May | June | July | August
46 | September | October | November | December
47 deriving (Eq, Ord, Enum, Bounded, Ix, Read, Show)
50 = Sunday | Monday | Tuesday | Wednesday
51 | Thursday | Friday | Saturday
52 deriving (Eq, Ord, Enum, Bounded, Ix, Read, Show)
56 @ClockTime@ is an abstract type, used for the internal clock time.
57 Clock times may be compared, converted to strings, or converted to an
58 external calendar time @CalendarTime@.
61 data ClockTime = TOD Integer Integer deriving (Eq, Ord)
64 When a @ClockTime@ is shown, it is converted to a string of the form
65 @"Mon Nov 28 21:45:41 GMT 1994"@.
67 For now, we are restricted to roughly:
68 Fri Dec 13 20:45:52 1901 through Tue Jan 19 03:14:07 2038, because
69 we use the C library routines based on 32 bit integers.
72 instance Show ClockTime where
73 showsPrec p (TOD sec@(J# a# s# d#) nsec) = showString $ unsafePerformPrimIO $
74 allocChars 32 >>= \ buf ->
75 _ccall_ showTime (I# s#) (ByteArray bottom d#) buf
77 _ccall_ strlen str >>= \ len ->
78 packCBytesST len str >>= \ ps ->
81 showList = showList__ (showsPrec 0)
85 @CalendarTime@ is a user-readable and manipulable
86 representation of the internal $ClockTime$ type. The
87 numeric fields have the following ranges.
93 year -maxInt .. maxInt [Pre-Gregorian dates are inaccurate]
94 mon 0 .. 11 [Jan = 0, Dec = 11]
98 sec 0 .. 61 [Allows for two leap seconds]
99 picosec 0 .. (10^12)-1 [This could be over-precise?]
100 wday 0 .. 6 [Sunday = 0, Saturday = 6]
101 yday 0 .. 365 [364 in non-Leap years]
102 tz -43200 .. 43200 [Variation from UTC in seconds]
105 The {\em tzname} field is the name of the time zone. The {\em isdst}
106 field indicates whether Daylight Savings Time would be in effect.
117 ctPicosec :: Integer,
124 deriving (Eq,Ord,Read,Show)
128 The @TimeDiff@ type records the difference between two clock times in
140 tdPicosec :: Integer -- not standard
142 deriving (Eq,Ord,Read,Show)
145 @getClockTime@ returns the current time in its internal representation.
148 getClockTime :: IO ClockTime
150 malloc1 `thenIO_Prim` \ i1 ->
151 malloc1 `thenIO_Prim` \ i2 ->
152 _ccall_ getClockTime i1 i2 `thenIO_Prim` \ rc ->
154 cvtUnsigned i1 `thenIO_Prim` \ sec ->
155 cvtUnsigned i2 `thenIO_Prim` \ nsec ->
156 return (TOD sec (nsec * 1000))
158 constructErrorAndFail "getClockTime"
160 malloc1 = ST $ \ (S# s#) ->
161 case newIntArray# 1# s# of
162 StateAndMutableByteArray# s2# barr# -> (MutableByteArray bottom barr#, S# s2#)
164 -- The C routine fills in an unsigned word. We don't have `unsigned2Integer#,'
165 -- so we freeze the data bits and use them for an MP_INT structure. Note that
166 -- zero is still handled specially, although (J# 1# 1# (ptr to 0#)) is probably
167 -- acceptable to gmp.
169 cvtUnsigned (MutableByteArray _ arr#) = ST $ \ (S# s#) ->
170 case readIntArray# arr# 0# s# of
171 StateAndInt# s2# r# ->
175 case unsafeFreezeByteArray# arr# s2# of
176 StateAndByteArray# s3# frozen# -> (J# 1# 1# frozen#, S# s3#)
180 @addToClockTime@ {\em d} {\em t} adds a time difference {\em d} and a
181 clock time {\em t} to yield a new clock time. The difference {\em d}
182 may be either positive or negative. @[diffClockTimes@ {\em t1} {\em
183 t2} returns the difference between two clock times {\em t1} and {\em
188 addToClockTime :: TimeDiff -> ClockTime -> ClockTime
189 addToClockTime (TimeDiff year mon day hour min sec psec)
190 (TOD c_sec c_psec) = unsafePerformPrimIO $
191 allocWords (``sizeof(time_t)'') >>= \ res ->
192 _ccall_ toClockSec year mon day hour min sec 1 res
193 >>= \ ptr@(A# ptr#) ->
194 if ptr /= ``NULL'' then
196 diff_sec = (int2Integer# (indexIntOffAddr# ptr# 0#))
199 return (TOD (c_sec + diff_sec) (c_psec + diff_psec))
201 error "Time.addToClockTime: can't perform conversion of TimeDiff"
204 diffClockTimes :: ClockTime -> ClockTime -> TimeDiff
205 diffClockTimes tod_a tod_b =
207 CalendarTime year_a mon_a day_a hour_a min_a sec_a psec_a _ _ _ _ _ = toCalendarTime tod_a
208 CalendarTime year_b mon_b day_b hour_b min_b sec_b psec_b _ _ _ _ _ = toCalendarTime tod_b
210 TimeDiff (year_a - year_b)
219 @toCalendarTime@ {\em t} converts {\em t} to a local time, modified by
220 the current timezone and daylight savings time settings. @toUTCTime@
221 {\em t} converts {\em t} into UTC time. @toClockTime@ {\em l}
222 converts {\em l} into the corresponding internal @ClockTime@. The
223 {\em wday}, {\em yday}, {\em tzname}, and {\em isdst} fields are
227 toCalendarTime :: ClockTime -> CalendarTime
228 toCalendarTime (TOD sec@(J# a# s# d#) psec) = unsafePerformPrimIO $
229 allocWords (``sizeof(struct tm)''::Int) >>= \ res ->
230 allocChars 32 >>= \ zoneNm ->
231 _casm_ ``SETZONE((struct tm *)%0,(char *)%1); '' res zoneNm >>= \ () ->
232 _ccall_ toLocalTime (I# s#) (ByteArray bottom d#) res
234 if tm == (``NULL''::Addr) then
235 error "Time.toCalendarTime: out of range"
237 _casm_ ``%r = ((struct tm *)%0)->tm_sec;'' tm >>= \ sec ->
238 _casm_ ``%r = ((struct tm *)%0)->tm_min;'' tm >>= \ min ->
239 _casm_ ``%r = ((struct tm *)%0)->tm_hour;'' tm >>= \ hour ->
240 _casm_ ``%r = ((struct tm *)%0)->tm_mday;'' tm >>= \ mday ->
241 _casm_ ``%r = ((struct tm *)%0)->tm_mon;'' tm >>= \ mon ->
242 _casm_ ``%r = ((struct tm *)%0)->tm_year;'' tm >>= \ year ->
243 _casm_ ``%r = ((struct tm *)%0)->tm_wday;'' tm >>= \ wday ->
244 _casm_ ``%r = ((struct tm *)%0)->tm_yday;'' tm >>= \ yday ->
245 _casm_ ``%r = ((struct tm *)%0)->tm_isdst;'' tm >>= \ isdst ->
246 _ccall_ ZONE tm >>= \ zone ->
247 _ccall_ GMTOFF tm >>= \ tz ->
248 _ccall_ strlen zone >>= \ len ->
249 packCBytesST len zone >>= \ tzname ->
250 returnPrimIO (CalendarTime (1900+year) mon mday hour min sec psec
251 (toEnum wday) yday (unpackPS tzname) tz (isdst /= 0))
253 toUTCTime :: ClockTime -> CalendarTime
254 toUTCTime (TOD sec@(J# a# s# d#) psec) = unsafePerformPrimIO (
255 allocWords (``sizeof(struct tm)''::Int) >>= \ res ->
256 allocChars 32 >>= \ zoneNm ->
257 _casm_ ``SETZONE((struct tm *)%0,(char *)%1); '' res zoneNm >>= \ () ->
258 _ccall_ toUTCTime (I# s#) (ByteArray bottom d#) res
260 if tm == (``NULL''::Addr) then
261 error "Time.toUTCTime: out of range"
263 _casm_ ``%r = ((struct tm *)%0)->tm_sec;'' tm >>= \ sec ->
264 _casm_ ``%r = ((struct tm *)%0)->tm_min;'' tm >>= \ min ->
265 _casm_ ``%r = ((struct tm *)%0)->tm_hour;'' tm >>= \ hour ->
266 _casm_ ``%r = ((struct tm *)%0)->tm_mday;'' tm >>= \ mday ->
267 _casm_ ``%r = ((struct tm *)%0)->tm_mon;'' tm >>= \ mon ->
268 _casm_ ``%r = ((struct tm *)%0)->tm_year;'' tm >>= \ year ->
269 _casm_ ``%r = ((struct tm *)%0)->tm_wday;'' tm >>= \ wday ->
270 _casm_ ``%r = ((struct tm *)%0)->tm_yday;'' tm >>= \ yday ->
271 returnPrimIO (CalendarTime (1900+year) mon mday hour min sec psec
272 (toEnum wday) yday "UTC" 0 False)
275 toClockTime :: CalendarTime -> ClockTime
276 toClockTime (CalendarTime year mon mday hour min sec psec wday yday tzname tz isdst) =
277 if psec < 0 || psec > 999999999999 then
278 error "Time.toClockTime: picoseconds out of range"
279 else if tz < -43200 || tz > 43200 then
280 error "Time.toClockTime: timezone offset out of range"
282 unsafePerformPrimIO (
283 allocWords (``sizeof(time_t)'') >>= \ res ->
284 _ccall_ toClockSec year mon mday hour min sec tz res
285 >>= \ ptr@(A# ptr#) ->
286 if ptr /= ``NULL'' then
287 returnPrimIO (TOD (int2Integer# (indexIntOffAddr# ptr# 0#)) psec)
289 error "Time.toClockTime: can't perform conversion"
293 bottom = error "Time.bottom"
296 -- (copied from PosixUtil, for now)
297 -- Allocate a mutable array of characters with no indices.
299 allocChars :: Int -> ST s (MutableByteArray s ())
300 allocChars (I# size#) = ST $ \ (S# s#) ->
301 case newCharArray# size# s# of
302 StateAndMutableByteArray# s2# barr# -> (MutableByteArray bot barr#, S# s2#)
304 bot = error "Time.allocChars"
306 -- Allocate a mutable array of words with no indices
308 allocWords :: Int -> ST s (MutableByteArray s ())
309 allocWords (I# size#) = ST $ \ (S# s#) ->
310 case newIntArray# size# s# of
311 StateAndMutableByteArray# s2# barr# -> (MutableByteArray bot barr#, S# s2#)
313 bot = error "Time.allocWords"
318 calendarTimeToString :: CalendarTime -> String
319 calendarTimeToString = formatCalendarTime defaultTimeLocale "%c"
321 formatCalendarTime :: TimeLocale -> String -> CalendarTime -> String
322 formatCalendarTime l fmt ct@(CalendarTime year mon day hour min sec sdec
323 wday yday tzname _ _) =
325 where doFmt ('%':c:cs) = decode c ++ doFmt cs
326 doFmt (c:cs) = c : doFmt cs
328 to12 h = let h' = h `mod` 12 in if h == 0 then 12 else h
329 decode 'A' = fst (wDays l !! fromEnum wday)
330 decode 'a' = snd (wDays l !! fromEnum wday)
331 decode 'B' = fst (months l !! fromEnum mon)
332 decode 'b' = snd (months l !! fromEnum mon)
333 decode 'h' = snd (months l !! fromEnum mon)
334 decode 'C' = show2 (year `quot` 100)
335 decode 'c' = doFmt (dateTimeFmt l)
336 decode 'D' = doFmt "%m/%d/%y"
337 decode 'd' = show2 day
338 decode 'e' = show2' day
339 decode 'H' = show2 hour
340 decode 'I' = show2 (to12 hour)
341 decode 'j' = show3 yday
342 decode 'k' = show2' hour
343 decode 'l' = show2' (to12 hour)
344 decode 'M' = show2 min
345 decode 'm' = show2 (fromEnum mon+1)
347 decode 'p' = (if hour < 12 then fst else snd) (amPm l)
348 decode 'R' = doFmt "%H:%M"
349 decode 'r' = doFmt (time12Fmt l)
350 decode 'T' = doFmt "%H:%M:%S"
352 decode 'S' = show2 sec
353 decode 's' = show2 sec -- Implementation-dependent, sez the lib doc..
354 decode 'U' = show2 ((yday + 7 - fromEnum wday) `div` 7)
355 decode 'u' = show (let n = fromEnum wday in
356 if n == 0 then 7 else n)
359 (yday + 7 - if fromEnum wday > 0 then
360 fromEnum wday - 1 else 6) `divMod` 7
361 in show2 (if days >= 4 then
363 else if week == 0 then 53 else week)
366 show2 ((yday + 7 - if fromEnum wday > 0 then
367 fromEnum wday - 1 else 6) `div` 7)
368 decode 'w' = show (fromEnum wday)
369 decode 'X' = doFmt (timeFmt l)
370 decode 'x' = doFmt (dateFmt l)
371 decode 'Y' = show year
372 decode 'y' = show2 (year `rem` 100)
377 show2, show2', show3 :: Int -> String
378 show2 x = [intToDigit (x `quot` 10), intToDigit (x `rem` 10)]
380 show2' x = if x < 10 then [ ' ', intToDigit x] else show2 x
382 show3 x = intToDigit (x `quot` 100) : show2 (x `rem` 100)