[project @ 1997-10-13 16:12:54 by simonm]
[ghc-hetmet.git] / ghc / lib / required / Time.lhs
1 %
2 % (c) The GRASP/AQUA Project, Glasgow University, 1995-97
3 %
4 \section[Time]{Haskell 1.4 Time of Day Library}
5
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).
10
11 \begin{code}
12 {-# OPTIONS -#include "cbits/timezone.h" -#include "cbits/stgio.h"  #-}
13
14 module Time 
15        (
16         CalendarTime(..),
17         Month,
18         Day,
19         CalendarTime(CalendarTime),
20         TimeDiff(TimeDiff),
21         ClockTime(..), -- non-standard, lib. report gives this as abstract
22         getClockTime, addToClockTime, diffClockTimes,
23         toCalendarTime, toUTCTime, toClockTime,
24         calendarTimeToString, formatCalendarTime
25        ) where
26
27 import PrelBase
28 import ST
29 import IOBase
30 import ArrBase
31 import STBase
32 import UnsafeST ( unsafePerformPrimIO )
33 import ST
34 import Ix
35 import Foreign  ( Addr(..) )
36 import Char     ( intToDigit )
37 import PackBase ( unpackCString )
38 import Locale
39
40 \end{code}
41
42 One way to partition and give name to chunks of a year and a week:
43
44 \begin{code}
45 data Month
46  = January   | February | March    | April
47  | May       | June     | July     | August
48  | September | October  | November | December
49  deriving (Eq, Ord, Enum, Bounded, Ix, Read, Show)
50
51 data Day 
52  = Sunday | Monday | Tuesday | Wednesday
53  | Thursday | Friday | Saturday
54  deriving (Eq, Ord, Enum, Bounded, Ix, Read, Show)
55
56 \end{code}
57
58 @ClockTime@ is an abstract type, used for the internal clock time.
59 Clock times may be compared, converted to strings, or converted to an
60 external calendar time @CalendarTime@.
61
62 \begin{code}
63 data ClockTime = TOD Integer Integer deriving (Eq, Ord)
64 \end{code}
65
66 When a @ClockTime@ is shown, it is converted to a string of the form
67 @"Mon Nov 28 21:45:41 GMT 1994"@.
68
69 For now, we are restricted to roughly:
70 Fri Dec 13 20:45:52 1901 through Tue Jan 19 03:14:07 2038, because
71 we use the C library routines based on 32 bit integers.
72
73 \begin{code}
74 instance Show ClockTime where
75     showsPrec p (TOD sec@(J# a# s# d#) nsec) = showString $ unsafePerformPrimIO $
76             allocChars 32               >>= \ buf ->
77             _ccall_ showTime (I# s#) (ByteArray bottom d#) buf
78                                         >>= \ str ->
79             return (unpackCString str)
80
81     showList = showList__ (showsPrec 0)
82 \end{code}
83
84
85 @CalendarTime@ is a user-readable and manipulable
86 representation of the internal $ClockTime$ type.  The
87 numeric fields have the following ranges.
88
89 \begin{verbatim}
90 Value         Range             Comments
91 -----         -----             --------
92
93 year    -maxInt .. maxInt       [Pre-Gregorian dates are inaccurate]
94 mon           0 .. 11           [Jan = 0, Dec = 11]
95 day           1 .. 31
96 hour          0 .. 23
97 min           0 .. 59
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]
103 \end{verbatim}
104
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.
107
108 \begin{code}
109 data CalendarTime 
110  = CalendarTime  {
111      ctYear    :: Int,
112      ctMonth   :: Int,
113      ctDay     :: Int,
114      ctHour    :: Int,
115      ctMin     :: Int,
116      ctSec     :: Int,
117      ctPicosec :: Integer,
118      ctWDay    :: Day,
119      ctYDay    :: Int,
120      ctTZName  :: String,
121      ctTZ      :: Int,
122      ctIsDST   :: Bool
123  }
124  deriving (Eq,Ord,Read,Show)
125
126 \end{code}
127
128 The @TimeDiff@ type records the difference between two clock times in
129 a user-readable way.
130
131 \begin{code}
132 data TimeDiff
133  = TimeDiff {
134      tdYear    :: Int,
135      tdMonth   :: Int,
136      tdDay     :: Int,
137      tdHour    :: Int,
138      tdMin     :: Int,
139      tdSec     :: Int,
140      tdPicosec :: Integer -- not standard
141    }
142    deriving (Eq,Ord,Read,Show)
143 \end{code}
144
145 @getClockTime@ returns the current time in its internal representation.
146
147 \begin{code}
148 getClockTime :: IO ClockTime
149 getClockTime =
150     malloc1                                         `thenIO_Prim` \ i1 ->
151     malloc1                                         `thenIO_Prim` \ i2 ->
152     _ccall_ getClockTime i1 i2                      `thenIO_Prim` \ rc ->
153     if rc == 0 then
154         cvtUnsigned i1                              `thenIO_Prim` \ sec ->
155         cvtUnsigned i2                              `thenIO_Prim` \ nsec ->
156         return (TOD sec (nsec * 1000))
157     else
158         constructErrorAndFail "getClockTime"
159   where
160     malloc1 = ST $ \ s# ->
161         case newIntArray# 1# s# of 
162           StateAndMutableByteArray# s2# barr# -> 
163                 STret s2# (MutableByteArray bottom barr#)
164
165     --  The C routine fills in an unsigned word.  We don't have 
166     --  `unsigned2Integer#,' so we freeze the data bits and use them 
167     --  for an MP_INT structure.  Note that zero is still handled specially,
168     --  although (J# 1# 1# (ptr to 0#)) is probably acceptable to gmp.
169
170     cvtUnsigned (MutableByteArray _ arr#) = ST $ \ s# ->
171         case readIntArray# arr# 0# s# of 
172           StateAndInt# s2# r# ->
173             if r# ==# 0# then
174                 STret s2# 0
175             else
176                 case unsafeFreezeByteArray# arr# s2# of
177                   StateAndByteArray# s3# frozen# -> 
178                         STret s3# (J# 1# 1# frozen#)
179
180 \end{code}
181
182 @addToClockTime@ {\em d} {\em t} adds a time difference {\em d} and a
183 clock time {\em t} to yield a new clock time.  The difference {\em d}
184 may be either positive or negative.  @[diffClockTimes@ {\em t1} {\em
185 t2} returns the difference between two clock times {\em t1} and {\em
186 t2} as a @TimeDiff@.
187
188
189 \begin{code}
190 addToClockTime  :: TimeDiff  -> ClockTime -> ClockTime
191 addToClockTime (TimeDiff year mon day hour min sec psec) 
192                (TOD c_sec c_psec) = unsafePerformPrimIO $
193     allocWords (``sizeof(time_t)'') >>= \ res ->
194     _ccall_ toClockSec year mon day hour min sec 0 res 
195                                     >>= \ ptr@(A# ptr#) ->
196     if ptr /= ``NULL'' then
197        let
198         diff_sec  = (int2Integer# (indexIntOffAddr# ptr# 0#))
199         diff_psec = psec
200        in
201        return (TOD (c_sec + diff_sec) (c_psec + diff_psec))
202     else
203        error "Time.addToClockTime: can't perform conversion of TimeDiff"
204
205
206 diffClockTimes  :: ClockTime -> ClockTime -> TimeDiff
207 diffClockTimes tod_a tod_b =
208   let
209    CalendarTime year_a mon_a day_a hour_a min_a sec_a psec_a _ _ _ _ _ = toCalendarTime tod_a
210    CalendarTime year_b mon_b day_b hour_b min_b sec_b psec_b _ _ _ _ _ = toCalendarTime tod_b
211   in
212   TimeDiff (year_a - year_b) 
213            (mon_a  - mon_b) 
214            (day_a  - day_b)
215            (hour_a - hour_b)
216            (min_b  - min_a)
217            (sec_a  - sec_b)
218            (psec_a - psec_b)
219 \end{code}
220
221 @toCalendarTime@ {\em t} converts {\em t} to a local time, modified by
222 the current timezone and daylight savings time settings.  @toUTCTime@
223 {\em t} converts {\em t} into UTC time.  @toClockTime@ {\em l}
224 converts {\em l} into the corresponding internal @ClockTime@.  The
225 {\em wday}, {\em yday}, {\em tzname}, and {\em isdst} fields are
226 ignored.
227
228 \begin{code}
229 toCalendarTime :: ClockTime -> CalendarTime
230 toCalendarTime (TOD sec@(J# a# s# d#) psec) = unsafePerformPrimIO $
231     allocWords (``sizeof(struct tm)''::Int)         >>= \ res ->
232     allocChars 32                                   >>= \ zoneNm ->
233     _casm_ ``SETZONE((struct tm *)%0,(char *)%1); '' res zoneNm   >>= \ () ->
234     _ccall_ toLocalTime (I# s#) (ByteArray bottom d#) res
235                                                     >>= \ tm ->
236     if tm == (``NULL''::Addr) then
237         error "Time.toCalendarTime: out of range"
238     else
239         _casm_ ``%r = ((struct tm *)%0)->tm_sec;'' tm   >>= \ sec ->
240         _casm_ ``%r = ((struct tm *)%0)->tm_min;'' tm   >>= \ min ->
241         _casm_ ``%r = ((struct tm *)%0)->tm_hour;'' tm  >>= \ hour ->
242         _casm_ ``%r = ((struct tm *)%0)->tm_mday;'' tm  >>= \ mday ->
243         _casm_ ``%r = ((struct tm *)%0)->tm_mon;'' tm   >>= \ mon ->
244         _casm_ ``%r = ((struct tm *)%0)->tm_year;'' tm  >>= \ year ->
245         _casm_ ``%r = ((struct tm *)%0)->tm_wday;'' tm  >>= \ wday ->
246         _casm_ ``%r = ((struct tm *)%0)->tm_yday;'' tm  >>= \ yday ->
247         _casm_ ``%r = ((struct tm *)%0)->tm_isdst;'' tm >>= \ isdst ->
248         _ccall_ ZONE tm                                 >>= \ zone ->
249         _ccall_ GMTOFF tm                               >>= \ tz ->
250         let
251          tzname = unpackCString zone
252         in
253         returnPrimIO (CalendarTime (1900+year) mon mday hour min sec psec 
254                       (toEnum wday) yday tzname tz (isdst /= 0))
255
256 toUTCTime :: ClockTime -> CalendarTime
257 toUTCTime  (TOD sec@(J# a# s# d#) psec) = unsafePerformPrimIO (
258         allocWords (``sizeof(struct tm)''::Int)                     >>= \ res ->
259         allocChars 32                                               >>= \ zoneNm ->
260         _casm_ ``SETZONE((struct tm *)%0,(char *)%1); '' res zoneNm >>= \ () ->
261         _ccall_ toUTCTime (I# s#) (ByteArray bottom d#) res
262                                                     >>= \ tm ->
263     if tm == (``NULL''::Addr) then
264         error "Time.toUTCTime: out of range"
265     else
266         _casm_ ``%r = ((struct tm *)%0)->tm_sec;'' tm   >>= \ sec ->
267         _casm_ ``%r = ((struct tm *)%0)->tm_min;'' tm   >>= \ min ->
268         _casm_ ``%r = ((struct tm *)%0)->tm_hour;'' tm  >>= \ hour ->
269         _casm_ ``%r = ((struct tm *)%0)->tm_mday;'' tm  >>= \ mday ->
270         _casm_ ``%r = ((struct tm *)%0)->tm_mon;'' tm   >>= \ mon ->
271         _casm_ ``%r = ((struct tm *)%0)->tm_year;'' tm  >>= \ year ->
272         _casm_ ``%r = ((struct tm *)%0)->tm_wday;'' tm  >>= \ wday ->
273         _casm_ ``%r = ((struct tm *)%0)->tm_yday;'' tm  >>= \ yday ->
274         returnPrimIO (CalendarTime (1900+year) mon mday hour min sec psec 
275                       (toEnum wday) yday "UTC" 0 False)
276     )
277
278 toClockTime :: CalendarTime -> ClockTime
279 toClockTime (CalendarTime year mon mday hour min sec psec wday yday tzname tz isdst) =
280     if psec < 0 || psec > 999999999999 then
281         error "Time.toClockTime: picoseconds out of range"
282     else if tz < -43200 || tz > 43200 then
283         error "Time.toClockTime: timezone offset out of range"
284     else
285         unsafePerformPrimIO (
286             allocWords (``sizeof(time_t)'') >>= \ res ->
287             _ccall_ toClockSec year mon mday hour min sec isDst res
288                                                     >>= \ ptr@(A# ptr#) ->
289             if ptr /= ``NULL'' then
290                 returnPrimIO (TOD (int2Integer# (indexIntOffAddr# ptr# 0#)) psec)
291             else
292                 error "Time.toClockTime: can't perform conversion"
293         )
294     where
295      isDst = if isdst then (1::Int) else 0
296
297 bottom :: (Int,Int)
298 bottom = error "Time.bottom"
299
300
301 -- (copied from PosixUtil, for now)
302 -- Allocate a mutable array of characters with no indices.
303
304 allocChars :: Int -> ST s (MutableByteArray s ())
305 allocChars (I# size#) = ST $ \ s# ->
306     case newCharArray# size# s# of 
307       StateAndMutableByteArray# s2# barr# -> 
308         STret s2# (MutableByteArray bot barr#)
309   where
310     bot = error "Time.allocChars"
311
312 -- Allocate a mutable array of words with no indices
313
314 allocWords :: Int -> ST s (MutableByteArray s ())
315 allocWords (I# size#) = ST $ \ s# ->
316     case newIntArray# size# s# of 
317       StateAndMutableByteArray# s2# barr# -> 
318         STret s2# (MutableByteArray bot barr#)
319   where
320     bot = error "Time.allocWords"
321
322 \end{code}
323
324 \begin{code}
325 calendarTimeToString  :: CalendarTime -> String
326 calendarTimeToString  =  formatCalendarTime defaultTimeLocale "%c"
327
328 formatCalendarTime :: TimeLocale -> String -> CalendarTime -> String
329 formatCalendarTime l fmt ct@(CalendarTime year mon day hour min sec sdec 
330                                            wday yday tzname _ _) =
331         doFmt fmt
332   where doFmt ('%':c:cs) = decode c ++ doFmt cs
333         doFmt (c:cs) = c : doFmt cs
334         doFmt "" = ""
335         to12 h = let h' = h `mod` 12 in if h == 0 then 12 else h
336         decode 'A' = fst (wDays l  !! fromEnum wday)
337         decode 'a' = snd (wDays l  !! fromEnum wday)
338         decode 'B' = fst (months l !! fromEnum mon)
339         decode 'b' = snd (months l !! fromEnum mon)
340         decode 'h' = snd (months l !! fromEnum mon)
341         decode 'C' = show2 (year `quot` 100)
342         decode 'c' = doFmt (dateTimeFmt l)
343         decode 'D' = doFmt "%m/%d/%y"
344         decode 'd' = show2 day
345         decode 'e' = show2' day
346         decode 'H' = show2 hour
347         decode 'I' = show2 (to12 hour)
348         decode 'j' = show3 yday
349         decode 'k' = show2' hour
350         decode 'l' = show2' (to12 hour)
351         decode 'M' = show2 min
352         decode 'm' = show2 (fromEnum mon+1)
353         decode 'n' = "\n"
354         decode 'p' = (if hour < 12 then fst else snd) (amPm l)
355         decode 'R' = doFmt "%H:%M"
356         decode 'r' = doFmt (time12Fmt l)
357         decode 'T' = doFmt "%H:%M:%S"
358         decode 't' = "\t"
359         decode 'S' = show2 sec
360         decode 's' = show2 sec -- Implementation-dependent, sez the lib doc..
361         decode 'U' = show2 ((yday + 7 - fromEnum wday) `div` 7)
362         decode 'u' = show (let n = fromEnum wday in 
363                            if n == 0 then 7 else n)
364         decode 'V' = 
365             let (week, days) = 
366                    (yday + 7 - if fromEnum wday > 0 then 
367                                fromEnum wday - 1 else 6) `divMod` 7
368             in  show2 (if days >= 4 then
369                           week+1 
370                        else if week == 0 then 53 else week)
371
372         decode 'W' = 
373             show2 ((yday + 7 - if fromEnum wday > 0 then 
374                                fromEnum wday - 1 else 6) `div` 7)
375         decode 'w' = show (fromEnum wday)
376         decode 'X' = doFmt (timeFmt l)
377         decode 'x' = doFmt (dateFmt l)
378         decode 'Y' = show year
379         decode 'y' = show2 (year `rem` 100)
380         decode 'Z' = tzname
381         decode '%' = "%"
382         decode c   = [c]
383
384 show2, show2', show3 :: Int -> String
385 show2 x = [intToDigit (x `quot` 10), intToDigit (x `rem` 10)]
386
387 show2' x = if x < 10 then [ ' ', intToDigit x] else show2 x
388
389 show3 x = intToDigit (x `quot` 100) : show2 (x `rem` 100)
390 \end{code}