1 (* Modified from the ML Kit
4.1.4; basislib
/Date
.sml
2 * by mfluet@acm
.org on
2017-04-07
3 * by mfluet@acm
.org on
2006-4-25
4 * by mfluet@acm
.org on
2005-8-10 based on
5 * modifications from the ML Kit Version
3; basislib
/Date
.sml
6 * by sweeks@research
.nj
.nec
.com on
1999-1-3 and
7 * by sweeks@acm
.org on
2000-1-18.
10 (* Date
-- 1995-07-03, 1998-04-07 *)
12 structure Date
:> DATE
=
14 structure Prim
= PrimitiveFFI
.Date
15 structure Tm
= Prim
.Tm
17 (* Patch to make Time look like it deals
with Int.int
18 * instead
of LargeInt
.int.
23 val toSeconds
= LargeInt
.toInt
o toSeconds
24 val fromSeconds
= fromSeconds
o LargeInt
.fromInt
27 datatype weekday
= Mon | Tue | Wed | Thu | Fri | Sat | Sun
29 datatype month
= Jan | Feb | Mar | Apr | May | Jun
30 | Jul | Aug | Sep | Oct | Nov | Dec
33 T
of {day
: int, (* 1-31 *)
35 isDst
: bool option
, (* daylight savings time
in force
*)
36 minute
: int, (* 0-59 *)
38 offset
: int option
, (* signed seconds East
of UTC
:
39 * this zone
= UTC
+t
; ~
82800 < t
<= 82800 *)
40 second
: int, (* 0-61 (allowing for leap seconds
) *)
42 year
: int, (* e
.g
. 1995 *)
43 yearDay
: int} (* 0-365 *)
47 fun make
f (T r
) = f r
51 val isDst
= make #isDst
52 val minute
= make #minute
53 val month
= make #month
54 val second
= make #second
55 val weekDay
= make #weekDay
57 val yearDay
= make #yearDay
62 (* 86400 = 24*60*6 is the number
of seconds per day
*)
64 type tmoz
= {tm_hour
: C_Int
.t
,
65 tm_isdst
: C_Int
.t
, (* 0 = no
, 1 = yes
, ~
1 = don
't know
*)
74 fun make (f
: C_Time
.t ref
-> C_Int
.t C_Errno
.t
) (n
: C_Time
.t
) : tmoz
=
76 ; {tm_hour
= Tm
.getHour (),
77 tm_isdst
= Tm
.getIsDst (),
78 tm_mday
= Tm
.getMDay (),
79 tm_min
= Tm
.getMin (),
80 tm_mon
= Tm
.getMon (),
81 tm_sec
= Tm
.getSec (),
82 tm_wday
= Tm
.getWDay (),
83 tm_yday
= Tm
.getYDay (),
84 tm_year
= Tm
.getYear ()})
86 val getlocaltime_
= make Prim
.localTime
87 val getgmtime_
= make Prim
.gmTime
90 fun setTmBuf ({tm_hour
, tm_isdst
, tm_mday
, tm_min
, tm_mon
,
91 tm_sec
, tm_wday
, tm_yday
, tm_year
}: tmoz
) : unit
=
93 ; Tm
.setIsDst tm_isdst
100 ; Tm
.setYear tm_year
)
102 fun mktime_ (t
: tmoz
): C_Time
.t
= C_Errno
.check (setTmBuf t
; Prim
.mkTime ())
104 (* The offset to add to
local time to get UTC
: positive West
of UTC
*)
105 val localoffset
: int = C_Double
.round (Prim
.localOffset ())
107 val toweekday
: int -> weekday
=
108 fn 0 => Sun |
1 => Mon |
2 => Tue |
3 => Wed
109 |
4 => Thu |
5 => Fri |
6 => Sat
110 | _
=> raise Fail
"Internal error: Date.toweekday"
112 val fromwday
: weekday
-> int =
113 fn Sun
=> 0 | Mon
=> 1 | Tue
=> 2 | Wed
=> 3
114 | Thu
=> 4 | Fri
=> 5 | Sat
=> 6
116 val tomonth
: int -> month
=
117 fn 0 => Jan |
1 => Feb |
2 => Mar |
3 => Apr
118 |
4 => May |
5 => Jun |
6 => Jul |
7 => Aug
119 |
8 => Sep |
9 => Oct |
10 => Nov |
11 => Dec
120 | _
=> raise Fail
"Internal error: Date.tomonth"
122 val frommonth
: month
-> int =
123 fn Jan
=> 0 | Feb
=> 1 | Mar
=> 2 | Apr
=> 3
124 | May
=> 4 | Jun
=> 5 | Jul
=> 6 | Aug
=> 7
125 | Sep
=> 8 | Oct
=> 9 | Nov
=> 10 | Dec
=> 11
127 fun tmozToDate ({tm_hour
, tm_isdst
, tm_mday
, tm_min
, tm_mon
,
128 tm_sec
, tm_wday
, tm_yday
, tm_year
}: tmoz
) offset
=
129 T
{day
= C_Int
.toInt tm_mday
,
130 hour
= C_Int
.toInt tm_hour
,
131 isDst
= (case tm_isdst
of
135 minute
= C_Int
.toInt tm_min
,
136 month
= tomonth (C_Int
.toInt tm_mon
),
138 second
= C_Int
.toInt tm_sec
,
139 weekDay
= toweekday (C_Int
.toInt tm_wday
),
140 yearDay
= C_Int
.toInt tm_yday
,
141 year
= (C_Int
.toInt tm_year
) + 1900}
143 fun leapyear (y
: int) =
144 y
mod 4 = 0 andalso y
mod 100 <> 0 orelse y
mod 400 = 0
146 fun monthdays year month
: int =
149 | Feb
=> if leapyear year
then 29 else 28
161 (* Check whether date may be passed to ISO
/ANSI C functions
: *)
163 fun okDate (T
{year
, month
, day
, hour
, minute
, second
, ...}) =
164 1 <= day
andalso day
<= monthdays year month
165 andalso 0 <= hour
andalso hour
<= 23
166 andalso 0 <= minute
andalso minute
<= 59
167 andalso 0 <= second
andalso second
<= 61 (* leap seconds
*)
169 fun dateToTmoz (dt
as T
{year
, month
, day
, hour
, minute
, second
,
170 weekDay
, yearDay
, isDst
, ...}): tmoz
=
173 else {tm_hour
= C_Int
.fromInt hour
,
174 tm_isdst
= (case isDst
of
178 tm_mday
= C_Int
.fromInt day
,
179 tm_min
= C_Int
.fromInt minute
,
180 tm_mon
= C_Int
.fromInt (frommonth month
),
181 tm_sec
= C_Int
.fromInt second
,
182 tm_wday
= C_Int
.fromInt (fromwday weekDay
),
183 tm_yday
= C_Int
.fromInt yearDay
,
184 tm_year
= C_Int
.fromInt (year
- 1900)}
186 (* -------------------------------------------------- *)
187 (* Translated from Emacs
's calendar
.el
: *)
189 (* Reingold
: Number
of the day within the year
: *)
191 fun dayinyear (year
: int, month
: month
, day
: int): int =
192 let val monthno
= frommonth month
194 day
- 1 + 31 * monthno
195 - (if monthno
> 1 then
196 (27 + 4 * monthno
) div 10 - (if leapyear year
then 1 else 0)
200 (* Reingold
: Find the number
of days elapsed from
the (imagined
)
201 Gregorian date Sunday
, December
31, 1 BC to the given date
. *)
203 fun todaynumber year month day
=
204 let val prioryears
= year
- 1
206 dayinyear (year
, month
, day
)
214 (* Reingold et al
: from absolute day number to year
, month
, date
: *)
216 fun fromdaynumber n
=
218 val n400
= d0
div 146097
219 val d1
= d0
mod 146097
220 val n100
= d1
div 36524
221 val d2
= d1
mod 36524
225 val day
= 1 + d3
mod 365
226 val year
= 400 * n400
+ 100 * n100
+ n4
* 4 + n1
+ 1
228 let val mdays
= monthdays
year (tomonth month
)
230 if mdays
< day
then loop (month
+1) (day
-mdays
)
231 else (year
, tomonth month
, day
)
234 if n100
= 4 orelse n1
= 4 then
240 (* -------------------------------------------------- *)
242 fun weekday daynumber
= toweekday (daynumber
mod 7)
244 (* Normalize a date
, disregarding leap seconds
: *)
246 fun normalizedate yr0 mo0 dy0 hr0 mn0 sec0 offset
=
247 let val mn1
= mn0
+ sec0
div 60
248 val second
= sec0
mod 60
249 val hr1
= hr0
+ mn1
div 60
250 val minute
= mn1
mod 60
251 val dayno
= todaynumber yr0 mo0 dy0
+ hr1
div 24
252 val hour
= hr1
mod 24
253 val (year
, month
, day
) = fromdaynumber dayno
254 val date1
= T
{day
= day
,
256 isDst
= (case offset
of
258 | SOME _
=> SOME
false),
263 weekDay
= weekday dayno
,
265 yearDay
= dayinyear (year
, month
, day
)}
267 (* One cannot reliably compute DST
in non
-local timezones
,
268 not even given the offset from UTC
. Countries
in the
269 Northern hemisphere have DST during Mar
-Oct
, those around
270 Equator
do not have DST
, and those
in the Southern
271 hemisphere have DST during Oct
-Mar
. *)
272 if year
< 1970 orelse year
> 2037 then date1
276 tmozToDate (getlocaltime_ (mktime_ (dateToTmoz date1
)))
281 fun fromTimeLocal t
=
282 tmozToDate (getlocaltime_ (C_Time
.fromInt (Time
.toSeconds t
))) NONE
285 tmozToDate (getgmtime_ (C_Time
.fromInt (Time
.toSeconds t
))) (SOME
0)
287 (* The following implements conversion from a
local date to
288 * a Time
.time
. It IGNORES wday
and yday
.
291 fun toTime (date
as T
{offset
, ...}) =
296 | SOME secs
=> localoffset
+ secs
297 val clock
= C_Time
.toInt (mktime_ (dateToTmoz date
)) - secoffset
299 if clock
< 0 then raise Date
300 else Time
.fromSeconds clock
303 fun localOffset () = Time
.fromSeconds (localoffset
mod 86400)
308 val a
= Array
.tabulate (Char.maxOrd
+ 1, fn _
=> false)
309 val validChars
= "aAbBcdHIjmMpSUwWxXyYZ%"
311 (size validChars
, fn i
=>
312 Array
.update (a
, Char.ord (String.sub (validChars
, i
)), true));
313 fn c
=> Array
.sub (a
, Char.ord c
)
318 val _
= setTmBuf (dateToTmoz d
)
319 val bufLen
= 50 (* more than enough for a single format char
*)
320 val buf
= Array
.alloc bufLen
321 fun strftime fmtChar
=
325 (buf
, C_Size
.fromInt bufLen
,
326 NullString
.fromString (concat
["%", str fmtChar
, "\000"]))
327 val len
= C_Size
.toInt len
329 then raise Fail
"Date.fmt"
330 else ArraySlice
.vector (ArraySlice
.slice (buf
, 0, SOME len
))
332 val max
= size fmtStr
333 fun loop (i
, start
, accum
) =
336 let val len
= i
- start
340 else String.extract (fmtStr
, start
, SOME len
) :: accum
346 if #
"%" = String.sub (fmtStr
, i
)
354 val c
= String.sub (fmtStr
, i
)
357 then loop (i
+ 1, i
+ 1,
358 strftime c
:: newAccum ())
359 else loop (i
, i
, newAccum ())
362 else loop (i
+ 1, start
, accum
)
364 in concat (rev (loop (0, 0, [])))
368 val toString
= fmt
"%a %b %d %H:%M:%S %Y"
370 type ('a
, 'b
) reader
= ('a
, 'b
) Reader
.reader
372 fun scan (reader
: (char
, 'a
) reader
): (t
, 'a
) reader
=
374 type 'b t
= ('b
, 'a
) reader
375 val none
: 'b t
= fn _
=> NONE
376 fun done (b
: 'b
): 'b t
= fn a
=> SOME (b
, a
)
377 fun peek1 (f
: char
-> 'b t
): 'b t
=
381 |
SOME (c
, _
) => f c a
382 fun read1 (f
: char
-> 'b t
): 'b t
=
386 |
SOME (c
, a
) => f c a
387 fun skipSpace (r
: 'b t
): 'b t
=
392 then read1 (fn _
=> loop ())
397 fun readN (n
: int, f
: string -> 'b t
): 'b t
=
399 fun loop (n
: int, ac
: char list
): 'b t
=
401 then f (implode (rev ac
))
402 else read1 (fn c
=> loop (n
- 1, c
:: ac
))
406 fun readChar (c
: char
, r
: 'b t
): 'b t
=
407 read1 (fn c
' => if c
= c
' then r
else none
)
408 fun readWeekDay (f
: weekday
-> 'b t
): 'b t
=
419 fun readMonth (f
: month
-> 'b t
): 'b t
=
435 fun readDigs (n
: int, lower
: int, upper
: int, f
: int -> 'b t
): 'b t
=
437 if not (CharVector
.all
Char.isDigit s
)
444 ac
* 10 + (Char.ord c
- Char.ord #
"0"))
447 if lower
<= v
andalso v
<= upper
454 then read1 (fn _
=> readDigs (1, 1, 9, f
))
455 else readDigs (2, 1, 31, f
))
456 fun readHour f
= readDigs (2, 0, 23, f
)
457 fun readMinute f
= readDigs (2, 0, 59, f
)
458 fun readSeconds f
= readDigs (2, 0, 61, f
)
459 fun readYear f
= readDigs (4, 0, 9999, f
)
497 yearDay
= dayinyear (year
, month
, day
)}
501 fun fromString s
= StringCvt.scanString scan s
503 (* Ignore timezone
and DST when comparing dates
: *)
505 (T
{year
=y1
,month
=mo1
,day
=d1
,hour
=h1
,minute
=mi1
,second
=s1
, ...},
506 T
{year
=y2
,month
=mo2
,day
=d2
,hour
=h2
,minute
=mi2
,second
=s2
, ...}) =
508 fun cmp (v1
, v2
, cmpnext
) =
509 case Int.compare (v1
, v2
) of
514 fn _
=> cmp (frommonth mo1
, frommonth mo2
,
517 fn _
=> cmp (mi1
, mi2
,
522 fun date
{ year
, month
, day
, hour
, minute
, second
, offset
} =
523 if year
< 0 then raise Date
526 val (dayoffset
, offset
') =
531 val secs
= Time
.toSeconds time
533 if secs
<= 82800 then ~secs
else 86400 - secs
535 (Int.quot (secs
, 86400), SOME secoffset
)
537 val day
' = day
+ dayoffset
539 normalizedate year month day
' hour minute second offset
'
542 fun offset (T
{offset
, ...}) =
544 (fn secs
=> Time
.fromSeconds ((86400 + secs
) mod 86400))