merge from 1.8 branch
[bpt/guile.git] / srfi / srfi-19.scm
1 ;;; srfi-19.scm --- Time/Date Library
2
3 ;; Copyright (C) 2001, 2002, 2003, 2005, 2006 Free Software Foundation, Inc.
4 ;;
5 ;; This library is free software; you can redistribute it and/or
6 ;; modify it under the terms of the GNU Lesser General Public
7 ;; License as published by the Free Software Foundation; either
8 ;; version 2.1 of the License, or (at your option) any later version.
9 ;;
10 ;; This library is distributed in the hope that it will be useful,
11 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 ;; Lesser General Public License for more details.
14 ;;
15 ;; You should have received a copy of the GNU Lesser General Public
16 ;; License along with this library; if not, write to the Free Software
17 ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
19 ;;; Author: Rob Browning <rlb@cs.utexas.edu>
20 ;;; Originally from SRFI reference implementation by Will Fitzgerald.
21
22 ;;; Commentary:
23
24 ;; This module is fully documented in the Guile Reference Manual.
25
26 ;;; Code:
27
28 ;; FIXME: I haven't checked a decent amount of this code for potential
29 ;; performance improvements, but I suspect that there may be some
30 ;; substantial ones to be realized, esp. in the later "parsing" half
31 ;; of the file, by rewriting the code with use of more Guile native
32 ;; functions that do more work in a "chunk".
33 ;;
34 ;; FIXME: mkoeppe: Time zones are treated a little simplistic in
35 ;; SRFI-19; they are only a numeric offset. Thus, printing time zones
36 ;; (PRIV:LOCALE-PRINT-TIME-ZONE) can't be implemented sensibly. The
37 ;; functions taking an optional TZ-OFFSET should be extended to take a
38 ;; symbolic time-zone (like "CET"); this string should be stored in
39 ;; the DATE structure.
40
41 (define-module (srfi srfi-19)
42 :use-module (srfi srfi-6)
43 :use-module (srfi srfi-8)
44 :use-module (srfi srfi-9))
45
46 (begin-deprecated
47 ;; Prevent `export' from re-exporting core bindings. This behaviour
48 ;; of `export' is deprecated and will disappear in one of the next
49 ;; releases.
50 (define current-time #f))
51
52 (export ;; Constants
53 time-duration
54 time-monotonic
55 time-process
56 time-tai
57 time-thread
58 time-utc
59 ;; Current time and clock resolution
60 current-date
61 current-julian-day
62 current-modified-julian-day
63 current-time
64 time-resolution
65 ;; Time object and accessors
66 make-time
67 time?
68 time-type
69 time-nanosecond
70 time-second
71 set-time-type!
72 set-time-nanosecond!
73 set-time-second!
74 copy-time
75 ;; Time comparison procedures
76 time<=?
77 time<?
78 time=?
79 time>=?
80 time>?
81 ;; Time arithmetic procedures
82 time-difference
83 time-difference!
84 add-duration
85 add-duration!
86 subtract-duration
87 subtract-duration!
88 ;; Date object and accessors
89 make-date
90 date?
91 date-nanosecond
92 date-second
93 date-minute
94 date-hour
95 date-day
96 date-month
97 date-year
98 date-zone-offset
99 date-year-day
100 date-week-day
101 date-week-number
102 ;; Time/Date/Julian Day/Modified Julian Day converters
103 date->julian-day
104 date->modified-julian-day
105 date->time-monotonic
106 date->time-tai
107 date->time-utc
108 julian-day->date
109 julian-day->time-monotonic
110 julian-day->time-tai
111 julian-day->time-utc
112 modified-julian-day->date
113 modified-julian-day->time-monotonic
114 modified-julian-day->time-tai
115 modified-julian-day->time-utc
116 time-monotonic->date
117 time-monotonic->time-tai
118 time-monotonic->time-tai!
119 time-monotonic->time-utc
120 time-monotonic->time-utc!
121 time-tai->date
122 time-tai->julian-day
123 time-tai->modified-julian-day
124 time-tai->time-monotonic
125 time-tai->time-monotonic!
126 time-tai->time-utc
127 time-tai->time-utc!
128 time-utc->date
129 time-utc->julian-day
130 time-utc->modified-julian-day
131 time-utc->time-monotonic
132 time-utc->time-monotonic!
133 time-utc->time-tai
134 time-utc->time-tai!
135 ;; Date to string/string to date converters.
136 date->string
137 string->date)
138
139 (cond-expand-provide (current-module) '(srfi-19))
140
141 (define time-tai 'time-tai)
142 (define time-utc 'time-utc)
143 (define time-monotonic 'time-monotonic)
144 (define time-thread 'time-thread)
145 (define time-process 'time-process)
146 (define time-duration 'time-duration)
147
148 ;; FIXME: do we want to add gc time?
149 ;; (define time-gc 'time-gc)
150
151 ;;-- LOCALE dependent constants
152
153 (define priv:locale-number-separator ".")
154
155 (define priv:locale-abbr-weekday-vector
156 (vector "Sun" "Mon" "Tue" "Wed" "Thu" "Fri" "Sat"))
157
158 (define priv:locale-long-weekday-vector
159 (vector
160 "Sunday" "Monday" "Tuesday" "Wednesday" "Thursday" "Friday" "Saturday"))
161
162 ;; note empty string in 0th place.
163 (define priv:locale-abbr-month-vector
164 (vector ""
165 "Jan"
166 "Feb"
167 "Mar"
168 "Apr"
169 "May"
170 "Jun"
171 "Jul"
172 "Aug"
173 "Sep"
174 "Oct"
175 "Nov"
176 "Dec"))
177
178 (define priv:locale-long-month-vector
179 (vector ""
180 "January"
181 "February"
182 "March"
183 "April"
184 "May"
185 "June"
186 "July"
187 "August"
188 "September"
189 "October"
190 "November"
191 "December"))
192
193 (define priv:locale-pm "PM")
194 (define priv:locale-am "AM")
195
196 ;; See date->string
197 (define priv:locale-date-time-format "~a ~b ~d ~H:~M:~S~z ~Y")
198 (define priv:locale-short-date-format "~m/~d/~y")
199 (define priv:locale-time-format "~H:~M:~S")
200 (define priv:iso-8601-date-time-format "~Y-~m-~dT~H:~M:~S~z")
201
202 ;;-- Miscellaneous Constants.
203 ;;-- only the priv:tai-epoch-in-jd might need changing if
204 ;; a different epoch is used.
205
206 (define priv:nano 1000000000) ; nanoseconds in a second
207 (define priv:sid 86400) ; seconds in a day
208 (define priv:sihd 43200) ; seconds in a half day
209 (define priv:tai-epoch-in-jd 4881175/2) ; julian day number for 'the epoch'
210
211 ;; FIXME: should this be something other than misc-error?
212 (define (priv:time-error caller type value)
213 (if value
214 (throw 'misc-error caller "TIME-ERROR type ~A: ~S" (list type value) #f)
215 (throw 'misc-error caller "TIME-ERROR type ~A" (list type) #f)))
216
217 ;; A table of leap seconds
218 ;; See ftp://maia.usno.navy.mil/ser7/tai-utc.dat
219 ;; and update as necessary.
220 ;; this procedures reads the file in the abover
221 ;; format and creates the leap second table
222 ;; it also calls the almost standard, but not R5 procedures read-line
223 ;; & open-input-string
224 ;; ie (set! priv:leap-second-table (priv:read-tai-utc-date "tai-utc.dat"))
225
226 (define (priv:read-tai-utc-data filename)
227 (define (convert-jd jd)
228 (* (- (inexact->exact jd) priv:tai-epoch-in-jd) priv:sid))
229 (define (convert-sec sec)
230 (inexact->exact sec))
231 (let ((port (open-input-file filename))
232 (table '()))
233 (let loop ((line (read-line port)))
234 (if (not (eq? line eof))
235 (begin
236 (let* ((data (read (open-input-string
237 (string-append "(" line ")"))))
238 (year (car data))
239 (jd (cadddr (cdr data)))
240 (secs (cadddr (cdddr data))))
241 (if (>= year 1972)
242 (set! table (cons
243 (cons (convert-jd jd) (convert-sec secs))
244 table)))
245 (loop (read-line port))))))
246 table))
247
248 ;; each entry is (tai seconds since epoch . # seconds to subtract for utc)
249 ;; note they go higher to lower, and end in 1972.
250 (define priv:leap-second-table
251 '((1136073600 . 33)
252 (915148800 . 32)
253 (867715200 . 31)
254 (820454400 . 30)
255 (773020800 . 29)
256 (741484800 . 28)
257 (709948800 . 27)
258 (662688000 . 26)
259 (631152000 . 25)
260 (567993600 . 24)
261 (489024000 . 23)
262 (425865600 . 22)
263 (394329600 . 21)
264 (362793600 . 20)
265 (315532800 . 19)
266 (283996800 . 18)
267 (252460800 . 17)
268 (220924800 . 16)
269 (189302400 . 15)
270 (157766400 . 14)
271 (126230400 . 13)
272 (94694400 . 12)
273 (78796800 . 11)
274 (63072000 . 10)))
275
276 (define (read-leap-second-table filename)
277 (set! priv:leap-second-table (priv:read-tai-utc-data filename))
278 (values))
279
280
281 (define (priv:leap-second-delta utc-seconds)
282 (letrec ((lsd (lambda (table)
283 (cond ((>= utc-seconds (caar table))
284 (cdar table))
285 (else (lsd (cdr table)))))))
286 (if (< utc-seconds (* (- 1972 1970) 365 priv:sid)) 0
287 (lsd priv:leap-second-table))))
288
289
290 ;;; the TIME structure; creates the accessors, too.
291
292 (define-record-type time
293 (make-time-unnormalized type nanosecond second)
294 time?
295 (type time-type set-time-type!)
296 (nanosecond time-nanosecond set-time-nanosecond!)
297 (second time-second set-time-second!))
298
299 (define (copy-time time)
300 (make-time (time-type time) (time-nanosecond time) (time-second time)))
301
302 (define (priv:split-real r)
303 (if (integer? r)
304 (values (inexact->exact r) 0)
305 (let ((l (truncate r)))
306 (values (inexact->exact l) (- r l)))))
307
308 (define (priv:time-normalize! t)
309 (if (>= (abs (time-nanosecond t)) 1000000000)
310 (receive (int frac)
311 (priv:split-real (time-nanosecond t))
312 (set-time-second! t (+ (time-second t)
313 (quotient int 1000000000)))
314 (set-time-nanosecond! t (+ (remainder int 1000000000)
315 frac))))
316 (if (and (positive? (time-second t))
317 (negative? (time-nanosecond t)))
318 (begin
319 (set-time-second! t (- (time-second t) 1))
320 (set-time-nanosecond! t (+ 1000000000 (time-nanosecond t))))
321 (if (and (negative? (time-second t))
322 (positive? (time-nanosecond t)))
323 (begin
324 (set-time-second! t (+ (time-second t) 1))
325 (set-time-nanosecond! t (+ 1000000000 (time-nanosecond t))))))
326 t)
327
328 (define (make-time type nanosecond second)
329 (priv:time-normalize! (make-time-unnormalized type nanosecond second)))
330
331 ;; Helpers
332 ;; FIXME: finish this and publish it?
333 (define (date->broken-down-time date)
334 (let ((result (mktime 0)))
335 ;; FIXME: What should we do about leap-seconds which may overflow
336 ;; set-tm:sec?
337 (set-tm:sec result (date-second date))
338 (set-tm:min result (date-minute date))
339 (set-tm:hour result (date-hour date))
340 ;; FIXME: SRFI day ranges from 0-31. (not compatible with set-tm:mday).
341 (set-tm:mday result (date-day date))
342 (set-tm:month result (- (date-month date) 1))
343 ;; FIXME: need to signal error on range violation.
344 (set-tm:year result (+ 1900 (date-year date)))
345 (set-tm:isdst result -1)
346 (set-tm:gmtoff result (- (date-zone-offset date)))
347 result))
348
349 ;;; current-time
350
351 ;;; specific time getters.
352
353 (define (priv:current-time-utc)
354 ;; Resolution is microseconds.
355 (let ((tod (gettimeofday)))
356 (make-time time-utc (* (cdr tod) 1000) (car tod))))
357
358 (define (priv:current-time-tai)
359 ;; Resolution is microseconds.
360 (let* ((tod (gettimeofday))
361 (sec (car tod))
362 (usec (cdr tod)))
363 (make-time time-tai
364 (* usec 1000)
365 (+ (car tod) (priv:leap-second-delta sec)))))
366
367 ;;(define (priv:current-time-ms-time time-type proc)
368 ;; (let ((current-ms (proc)))
369 ;; (make-time time-type
370 ;; (quotient current-ms 10000)
371 ;; (* (remainder current-ms 1000) 10000))))
372
373 ;; -- we define it to be the same as TAI.
374 ;; A different implemation of current-time-montonic
375 ;; will require rewriting all of the time-monotonic converters,
376 ;; of course.
377
378 (define (priv:current-time-monotonic)
379 ;; Resolution is microseconds.
380 (priv:current-time-tai))
381
382 (define (priv:current-time-thread)
383 (priv:time-error 'current-time 'unsupported-clock-type 'time-thread))
384
385 (define priv:ns-per-guile-tick (/ 1000000000 internal-time-units-per-second))
386
387 (define (priv:current-time-process)
388 (let ((run-time (get-internal-run-time)))
389 (make-time
390 time-process
391 (quotient run-time internal-time-units-per-second)
392 (* (remainder run-time internal-time-units-per-second)
393 priv:ns-per-guile-tick))))
394
395 (define (priv:current-time-process)
396 (let ((run-time (get-internal-run-time)))
397 (list
398 'time-process
399 (* (remainder run-time internal-time-units-per-second)
400 priv:ns-per-guile-tick)
401 (quotient run-time internal-time-units-per-second))))
402
403 ;;(define (priv:current-time-gc)
404 ;; (priv:current-time-ms-time time-gc current-gc-milliseconds))
405
406 (define (current-time . clock-type)
407 (let ((clock-type (if (null? clock-type) time-utc (car clock-type))))
408 (cond
409 ((eq? clock-type time-tai) (priv:current-time-tai))
410 ((eq? clock-type time-utc) (priv:current-time-utc))
411 ((eq? clock-type time-monotonic) (priv:current-time-monotonic))
412 ((eq? clock-type time-thread) (priv:current-time-thread))
413 ((eq? clock-type time-process) (priv:current-time-process))
414 ;; ((eq? clock-type time-gc) (priv:current-time-gc))
415 (else (priv:time-error 'current-time 'invalid-clock-type clock-type)))))
416
417 ;; -- Time Resolution
418 ;; This is the resolution of the clock in nanoseconds.
419 ;; This will be implementation specific.
420
421 (define (time-resolution . clock-type)
422 (let ((clock-type (if (null? clock-type) time-utc (car clock-type))))
423 (case clock-type
424 ((time-tai) 1000)
425 ((time-utc) 1000)
426 ((time-monotonic) 1000)
427 ((time-process) priv:ns-per-guile-tick)
428 ;; ((eq? clock-type time-thread) 1000)
429 ;; ((eq? clock-type time-gc) 10000)
430 (else (priv:time-error 'time-resolution 'invalid-clock-type clock-type)))))
431
432 ;; -- Time comparisons
433
434 (define (time=? t1 t2)
435 ;; Arrange tests for speed and presume that t1 and t2 are actually times.
436 ;; also presume it will be rare to check two times of different types.
437 (and (= (time-second t1) (time-second t2))
438 (= (time-nanosecond t1) (time-nanosecond t2))
439 (eq? (time-type t1) (time-type t2))))
440
441 (define (time>? t1 t2)
442 (or (> (time-second t1) (time-second t2))
443 (and (= (time-second t1) (time-second t2))
444 (> (time-nanosecond t1) (time-nanosecond t2)))))
445
446 (define (time<? t1 t2)
447 (or (< (time-second t1) (time-second t2))
448 (and (= (time-second t1) (time-second t2))
449 (< (time-nanosecond t1) (time-nanosecond t2)))))
450
451 (define (time>=? t1 t2)
452 (or (> (time-second t1) (time-second t2))
453 (and (= (time-second t1) (time-second t2))
454 (>= (time-nanosecond t1) (time-nanosecond t2)))))
455
456 (define (time<=? t1 t2)
457 (or (< (time-second t1) (time-second t2))
458 (and (= (time-second t1) (time-second t2))
459 (<= (time-nanosecond t1) (time-nanosecond t2)))))
460
461 ;; -- Time arithmetic
462
463 (define (time-difference! time1 time2)
464 (let ((sec-diff (- (time-second time1) (time-second time2)))
465 (nsec-diff (- (time-nanosecond time1) (time-nanosecond time2))))
466 (set-time-type! time1 time-duration)
467 (set-time-second! time1 sec-diff)
468 (set-time-nanosecond! time1 nsec-diff)
469 (priv:time-normalize! time1)))
470
471 (define (time-difference time1 time2)
472 (let ((result (copy-time time1)))
473 (time-difference! result time2)))
474
475 (define (add-duration! t duration)
476 (if (not (eq? (time-type duration) time-duration))
477 (priv:time-error 'add-duration 'not-duration duration)
478 (let ((sec-plus (+ (time-second t) (time-second duration)))
479 (nsec-plus (+ (time-nanosecond t) (time-nanosecond duration))))
480 (set-time-second! t sec-plus)
481 (set-time-nanosecond! t nsec-plus)
482 (priv:time-normalize! t))))
483
484 (define (add-duration t duration)
485 (let ((result (copy-time t)))
486 (add-duration! result duration)))
487
488 (define (subtract-duration! t duration)
489 (if (not (eq? (time-type duration) time-duration))
490 (priv:time-error 'add-duration 'not-duration duration)
491 (let ((sec-minus (- (time-second t) (time-second duration)))
492 (nsec-minus (- (time-nanosecond t) (time-nanosecond duration))))
493 (set-time-second! t sec-minus)
494 (set-time-nanosecond! t nsec-minus)
495 (priv:time-normalize! t))))
496
497 (define (subtract-duration time1 duration)
498 (let ((result (copy-time time1)))
499 (subtract-duration! result duration)))
500
501 ;; -- Converters between types.
502
503 (define (priv:time-tai->time-utc! time-in time-out caller)
504 (if (not (eq? (time-type time-in) time-tai))
505 (priv:time-error caller 'incompatible-time-types time-in))
506 (set-time-type! time-out time-utc)
507 (set-time-nanosecond! time-out (time-nanosecond time-in))
508 (set-time-second! time-out (- (time-second time-in)
509 (priv:leap-second-delta
510 (time-second time-in))))
511 time-out)
512
513 (define (time-tai->time-utc time-in)
514 (priv:time-tai->time-utc! time-in (make-time-unnormalized #f #f #f) 'time-tai->time-utc))
515
516
517 (define (time-tai->time-utc! time-in)
518 (priv:time-tai->time-utc! time-in time-in 'time-tai->time-utc!))
519
520 (define (priv:time-utc->time-tai! time-in time-out caller)
521 (if (not (eq? (time-type time-in) time-utc))
522 (priv:time-error caller 'incompatible-time-types time-in))
523 (set-time-type! time-out time-tai)
524 (set-time-nanosecond! time-out (time-nanosecond time-in))
525 (set-time-second! time-out (+ (time-second time-in)
526 (priv:leap-second-delta
527 (time-second time-in))))
528 time-out)
529
530 (define (time-utc->time-tai time-in)
531 (priv:time-utc->time-tai! time-in (make-time-unnormalized #f #f #f) 'time-utc->time-tai))
532
533 (define (time-utc->time-tai! time-in)
534 (priv:time-utc->time-tai! time-in time-in 'time-utc->time-tai!))
535
536 ;; -- these depend on time-monotonic having the same definition as time-tai!
537 (define (time-monotonic->time-utc time-in)
538 (if (not (eq? (time-type time-in) time-monotonic))
539 (priv:time-error caller 'incompatible-time-types time-in))
540 (let ((ntime (copy-time time-in)))
541 (set-time-type! ntime time-tai)
542 (priv:time-tai->time-utc! ntime ntime 'time-monotonic->time-utc)))
543
544 (define (time-monotonic->time-utc! time-in)
545 (if (not (eq? (time-type time-in) time-monotonic))
546 (priv:time-error caller 'incompatible-time-types time-in))
547 (set-time-type! time-in time-tai)
548 (priv:time-tai->time-utc! ntime ntime 'time-monotonic->time-utc))
549
550 (define (time-monotonic->time-tai time-in)
551 (if (not (eq? (time-type time-in) time-monotonic))
552 (priv:time-error caller 'incompatible-time-types time-in))
553 (let ((ntime (copy-time time-in)))
554 (set-time-type! ntime time-tai)
555 ntime))
556
557 (define (time-monotonic->time-tai! time-in)
558 (if (not (eq? (time-type time-in) time-monotonic))
559 (priv:time-error caller 'incompatible-time-types time-in))
560 (set-time-type! time-in time-tai)
561 time-in)
562
563 (define (time-utc->time-monotonic time-in)
564 (if (not (eq? (time-type time-in) time-utc))
565 (priv:time-error caller 'incompatible-time-types time-in))
566 (let ((ntime (priv:time-utc->time-tai! time-in (make-time-unnormalized #f #f #f)
567 'time-utc->time-monotonic)))
568 (set-time-type! ntime time-monotonic)
569 ntime))
570
571 (define (time-utc->time-monotonic! time-in)
572 (if (not (eq? (time-type time-in) time-utc))
573 (priv:time-error caller 'incompatible-time-types time-in))
574 (let ((ntime (priv:time-utc->time-tai! time-in time-in
575 'time-utc->time-monotonic!)))
576 (set-time-type! ntime time-monotonic)
577 ntime))
578
579 (define (time-tai->time-monotonic time-in)
580 (if (not (eq? (time-type time-in) time-tai))
581 (priv:time-error caller 'incompatible-time-types time-in))
582 (let ((ntime (copy-time time-in)))
583 (set-time-type! ntime time-monotonic)
584 ntime))
585
586 (define (time-tai->time-monotonic! time-in)
587 (if (not (eq? (time-type time-in) time-tai))
588 (priv:time-error caller 'incompatible-time-types time-in))
589 (set-time-type! time-in time-monotonic)
590 time-in)
591
592 ;; -- Date Structures
593
594 ;; FIXME: to be really safe, perhaps we should normalize the
595 ;; seconds/nanoseconds/minutes coming in to make-date...
596
597 (define-record-type date
598 (make-date nanosecond second minute
599 hour day month
600 year
601 zone-offset)
602 date?
603 (nanosecond date-nanosecond set-date-nanosecond!)
604 (second date-second set-date-second!)
605 (minute date-minute set-date-minute!)
606 (hour date-hour set-date-hour!)
607 (day date-day set-date-day!)
608 (month date-month set-date-month!)
609 (year date-year set-date-year!)
610 (zone-offset date-zone-offset set-date-zone-offset!))
611
612 ;; gives the julian day which starts at noon.
613 (define (priv:encode-julian-day-number day month year)
614 (let* ((a (quotient (- 14 month) 12))
615 (y (- (+ year 4800) a (if (negative? year) -1 0)))
616 (m (- (+ month (* 12 a)) 3)))
617 (+ day
618 (quotient (+ (* 153 m) 2) 5)
619 (* 365 y)
620 (quotient y 4)
621 (- (quotient y 100))
622 (quotient y 400)
623 -32045)))
624
625 ;; gives the seconds/date/month/year
626 (define (priv:decode-julian-day-number jdn)
627 (let* ((days (inexact->exact (truncate jdn)))
628 (a (+ days 32044))
629 (b (quotient (+ (* 4 a) 3) 146097))
630 (c (- a (quotient (* 146097 b) 4)))
631 (d (quotient (+ (* 4 c) 3) 1461))
632 (e (- c (quotient (* 1461 d) 4)))
633 (m (quotient (+ (* 5 e) 2) 153))
634 (y (+ (* 100 b) d -4800 (quotient m 10))))
635 (values ; seconds date month year
636 (* (- jdn days) priv:sid)
637 (+ e (- (quotient (+ (* 153 m) 2) 5)) 1)
638 (+ m 3 (* -12 (quotient m 10)))
639 (if (>= 0 y) (- y 1) y))))
640
641 ;; relies on the fact that we named our time zone accessor
642 ;; differently from MzScheme's....
643 ;; This should be written to be OS specific.
644
645 (define (priv:local-tz-offset utc-time)
646 ;; SRFI uses seconds West, but guile (and libc) use seconds East.
647 (- (tm:gmtoff (localtime (time-second utc-time)))))
648
649 ;; special thing -- ignores nanos
650 (define (priv:time->julian-day-number seconds tz-offset)
651 (+ (/ (+ seconds tz-offset priv:sihd)
652 priv:sid)
653 priv:tai-epoch-in-jd))
654
655 (define (priv:leap-second? second)
656 (and (assoc second priv:leap-second-table) #t))
657
658 (define (time-utc->date time . tz-offset)
659 (if (not (eq? (time-type time) time-utc))
660 (priv:time-error 'time->date 'incompatible-time-types time))
661 (let* ((offset (if (null? tz-offset)
662 (priv:local-tz-offset time)
663 (car tz-offset)))
664 (leap-second? (priv:leap-second? (+ offset (time-second time))))
665 (jdn (priv:time->julian-day-number (if leap-second?
666 (- (time-second time) 1)
667 (time-second time))
668 offset)))
669
670 (call-with-values (lambda () (priv:decode-julian-day-number jdn))
671 (lambda (secs date month year)
672 ;; secs is a real because jdn is a real in Guile;
673 ;; but it is conceptionally an integer.
674 (let* ((int-secs (inexact->exact (round secs)))
675 (hours (quotient int-secs (* 60 60)))
676 (rem (remainder int-secs (* 60 60)))
677 (minutes (quotient rem 60))
678 (seconds (remainder rem 60)))
679 (make-date (time-nanosecond time)
680 (if leap-second? (+ seconds 1) seconds)
681 minutes
682 hours
683 date
684 month
685 year
686 offset))))))
687
688 (define (time-tai->date time . tz-offset)
689 (if (not (eq? (time-type time) time-tai))
690 (priv:time-error 'time->date 'incompatible-time-types time))
691 (let* ((offset (if (null? tz-offset)
692 (priv:local-tz-offset (time-tai->time-utc time))
693 (car tz-offset)))
694 (seconds (- (time-second time)
695 (priv:leap-second-delta (time-second time))))
696 (leap-second? (priv:leap-second? (+ offset seconds)))
697 (jdn (priv:time->julian-day-number (if leap-second?
698 (- seconds 1)
699 seconds)
700 offset)))
701 (call-with-values (lambda () (priv:decode-julian-day-number jdn))
702 (lambda (secs date month year)
703 ;; secs is a real because jdn is a real in Guile;
704 ;; but it is conceptionally an integer.
705 ;; adjust for leap seconds if necessary ...
706 (let* ((int-secs (inexact->exact (round secs)))
707 (hours (quotient int-secs (* 60 60)))
708 (rem (remainder int-secs (* 60 60)))
709 (minutes (quotient rem 60))
710 (seconds (remainder rem 60)))
711 (make-date (time-nanosecond time)
712 (if leap-second? (+ seconds 1) seconds)
713 minutes
714 hours
715 date
716 month
717 year
718 offset))))))
719
720 ;; this is the same as time-tai->date.
721 (define (time-monotonic->date time . tz-offset)
722 (if (not (eq? (time-type time) time-monotonic))
723 (priv:time-error 'time->date 'incompatible-time-types time))
724 (let* ((offset (if (null? tz-offset)
725 (priv:local-tz-offset (time-monotonic->time-utc time))
726 (car tz-offset)))
727 (seconds (- (time-second time)
728 (priv:leap-second-delta (time-second time))))
729 (leap-second? (priv:leap-second? (+ offset seconds)))
730 (jdn (priv:time->julian-day-number (if leap-second?
731 (- seconds 1)
732 seconds)
733 offset)))
734 (call-with-values (lambda () (priv:decode-julian-day-number jdn))
735 (lambda (secs date month year)
736 ;; secs is a real because jdn is a real in Guile;
737 ;; but it is conceptionally an integer.
738 ;; adjust for leap seconds if necessary ...
739 (let* ((int-secs (inexact->exact (round secs)))
740 (hours (quotient int-secs (* 60 60)))
741 (rem (remainder int-secs (* 60 60)))
742 (minutes (quotient rem 60))
743 (seconds (remainder rem 60)))
744 (make-date (time-nanosecond time)
745 (if leap-second? (+ seconds 1) seconds)
746 minutes
747 hours
748 date
749 month
750 year
751 offset))))))
752
753 (define (date->time-utc date)
754 (let* ((jdays (- (priv:encode-julian-day-number (date-day date)
755 (date-month date)
756 (date-year date))
757 priv:tai-epoch-in-jd))
758 ;; jdays is an integer plus 1/2,
759 (jdays-1/2 (inexact->exact (- jdays 1/2))))
760 (make-time
761 time-utc
762 (date-nanosecond date)
763 (+ (* jdays-1/2 24 60 60)
764 (* (date-hour date) 60 60)
765 (* (date-minute date) 60)
766 (date-second date)
767 (- (date-zone-offset date))))))
768
769 (define (date->time-tai date)
770 (time-utc->time-tai! (date->time-utc date)))
771
772 (define (date->time-monotonic date)
773 (time-utc->time-monotonic! (date->time-utc date)))
774
775 (define (priv:leap-year? year)
776 (or (= (modulo year 400) 0)
777 (and (= (modulo year 4) 0) (not (= (modulo year 100) 0)))))
778
779 (define (leap-year? date)
780 (priv:leap-year? (date-year date)))
781
782 ;; Map 1-based month number M to number of days in the year before the
783 ;; start of month M (in a non-leap year).
784 (define priv:month-assoc '((1 . 0) (2 . 31) (3 . 59) (4 . 90)
785 (5 . 120) (6 . 151) (7 . 181) (8 . 212)
786 (9 . 243) (10 . 273) (11 . 304) (12 . 334)))
787
788 (define (priv:year-day day month year)
789 (let ((days-pr (assoc month priv:month-assoc)))
790 (if (not days-pr)
791 (priv:error 'date-year-day 'invalid-month-specification month))
792 (if (and (priv:leap-year? year) (> month 2))
793 (+ day (cdr days-pr) 1)
794 (+ day (cdr days-pr)))))
795
796 (define (date-year-day date)
797 (priv:year-day (date-day date) (date-month date) (date-year date)))
798
799 ;; from calendar faq
800 (define (priv:week-day day month year)
801 (let* ((a (quotient (- 14 month) 12))
802 (y (- year a))
803 (m (+ month (* 12 a) -2)))
804 (modulo (+ day
805 y
806 (quotient y 4)
807 (- (quotient y 100))
808 (quotient y 400)
809 (quotient (* 31 m) 12))
810 7)))
811
812 (define (date-week-day date)
813 (priv:week-day (date-day date) (date-month date) (date-year date)))
814
815 (define (priv:days-before-first-week date day-of-week-starting-week)
816 (let* ((first-day (make-date 0 0 0 0
817 1
818 1
819 (date-year date)
820 #f))
821 (fdweek-day (date-week-day first-day)))
822 (modulo (- day-of-week-starting-week fdweek-day)
823 7)))
824
825 ;; The "-1" here is a fix for the reference implementation, to make a new
826 ;; week start on the given day-of-week-starting-week. date-year-day returns
827 ;; a day starting from 1 for 1st Jan.
828 ;;
829 (define (date-week-number date day-of-week-starting-week)
830 (quotient (- (date-year-day date)
831 1
832 (priv:days-before-first-week date day-of-week-starting-week))
833 7))
834
835 (define (current-date . tz-offset)
836 (let ((time (current-time time-utc)))
837 (time-utc->date
838 time
839 (if (null? tz-offset)
840 (priv:local-tz-offset time)
841 (car tz-offset)))))
842
843 ;; given a 'two digit' number, find the year within 50 years +/-
844 (define (priv:natural-year n)
845 (let* ((current-year (date-year (current-date)))
846 (current-century (* (quotient current-year 100) 100)))
847 (cond
848 ((>= n 100) n)
849 ((< n 0) n)
850 ((<= (- (+ current-century n) current-year) 50) (+ current-century n))
851 (else (+ (- current-century 100) n)))))
852
853 (define (date->julian-day date)
854 (let ((nanosecond (date-nanosecond date))
855 (second (date-second date))
856 (minute (date-minute date))
857 (hour (date-hour date))
858 (day (date-day date))
859 (month (date-month date))
860 (year (date-year date)))
861 (+ (priv:encode-julian-day-number day month year)
862 (- 1/2)
863 (+ (/ (+ (* hour 60 60)
864 (* minute 60)
865 second
866 (/ nanosecond priv:nano))
867 priv:sid)))))
868
869 (define (date->modified-julian-day date)
870 (- (date->julian-day date)
871 4800001/2))
872
873 (define (time-utc->julian-day time)
874 (if (not (eq? (time-type time) time-utc))
875 (priv:time-error 'time->date 'incompatible-time-types time))
876 (+ (/ (+ (time-second time) (/ (time-nanosecond time) priv:nano))
877 priv:sid)
878 priv:tai-epoch-in-jd))
879
880 (define (time-utc->modified-julian-day time)
881 (- (time-utc->julian-day time)
882 4800001/2))
883
884 (define (time-tai->julian-day time)
885 (if (not (eq? (time-type time) time-tai))
886 (priv:time-error 'time->date 'incompatible-time-types time))
887 (+ (/ (+ (- (time-second time)
888 (priv:leap-second-delta (time-second time)))
889 (/ (time-nanosecond time) priv:nano))
890 priv:sid)
891 priv:tai-epoch-in-jd))
892
893 (define (time-tai->modified-julian-day time)
894 (- (time-tai->julian-day time)
895 4800001/2))
896
897 ;; this is the same as time-tai->julian-day
898 (define (time-monotonic->julian-day time)
899 (if (not (eq? (time-type time) time-monotonic))
900 (priv:time-error 'time->date 'incompatible-time-types time))
901 (+ (/ (+ (- (time-second time)
902 (priv:leap-second-delta (time-second time)))
903 (/ (time-nanosecond time) priv:nano))
904 priv:sid)
905 priv:tai-epoch-in-jd))
906
907 (define (time-monotonic->modified-julian-day time)
908 (- (time-monotonic->julian-day time)
909 4800001/2))
910
911 (define (julian-day->time-utc jdn)
912 (let ((secs (* priv:sid (- jdn priv:tai-epoch-in-jd))))
913 (receive (seconds parts)
914 (priv:split-real secs)
915 (make-time time-utc
916 (* parts priv:nano)
917 seconds))))
918
919 (define (julian-day->time-tai jdn)
920 (time-utc->time-tai! (julian-day->time-utc jdn)))
921
922 (define (julian-day->time-monotonic jdn)
923 (time-utc->time-monotonic! (julian-day->time-utc jdn)))
924
925 (define (julian-day->date jdn . tz-offset)
926 (let* ((time (julian-day->time-utc jdn))
927 (offset (if (null? tz-offset)
928 (priv:local-tz-offset time)
929 (car tz-offset))))
930 (time-utc->date time offset)))
931
932 (define (modified-julian-day->date jdn . tz-offset)
933 (apply julian-day->date (+ jdn 4800001/2)
934 tz-offset))
935
936 (define (modified-julian-day->time-utc jdn)
937 (julian-day->time-utc (+ jdn 4800001/2)))
938
939 (define (modified-julian-day->time-tai jdn)
940 (julian-day->time-tai (+ jdn 4800001/2)))
941
942 (define (modified-julian-day->time-monotonic jdn)
943 (julian-day->time-monotonic (+ jdn 4800001/2)))
944
945 (define (current-julian-day)
946 (time-utc->julian-day (current-time time-utc)))
947
948 (define (current-modified-julian-day)
949 (time-utc->modified-julian-day (current-time time-utc)))
950
951 ;; returns a string rep. of number N, of minimum LENGTH, padded with
952 ;; character PAD-WITH. If PAD-WITH is #f, no padding is done, and it's
953 ;; as if number->string was used. if string is longer than or equal
954 ;; in length to LENGTH, it's as if number->string was used.
955
956 (define (priv:padding n pad-with length)
957 (let* ((str (number->string n))
958 (str-len (string-length str)))
959 (if (or (>= str-len length)
960 (not pad-with))
961 str
962 (string-append (make-string (- length str-len) pad-with) str))))
963
964 (define (priv:last-n-digits i n)
965 (abs (remainder i (expt 10 n))))
966
967 (define (priv:locale-abbr-weekday n)
968 (vector-ref priv:locale-abbr-weekday-vector n))
969
970 (define (priv:locale-long-weekday n)
971 (vector-ref priv:locale-long-weekday-vector n))
972
973 (define (priv:locale-abbr-month n)
974 (vector-ref priv:locale-abbr-month-vector n))
975
976 (define (priv:locale-long-month n)
977 (vector-ref priv:locale-long-month-vector n))
978
979 (define (priv:vector-find needle haystack comparator)
980 (let ((len (vector-length haystack)))
981 (define (priv:vector-find-int index)
982 (cond
983 ((>= index len) #f)
984 ((comparator needle (vector-ref haystack index)) index)
985 (else (priv:vector-find-int (+ index 1)))))
986 (priv:vector-find-int 0)))
987
988 (define (priv:locale-abbr-weekday->index string)
989 (priv:vector-find string priv:locale-abbr-weekday-vector string=?))
990
991 (define (priv:locale-long-weekday->index string)
992 (priv:vector-find string priv:locale-long-weekday-vector string=?))
993
994 (define (priv:locale-abbr-month->index string)
995 (priv:vector-find string priv:locale-abbr-month-vector string=?))
996
997 (define (priv:locale-long-month->index string)
998 (priv:vector-find string priv:locale-long-month-vector string=?))
999
1000
1001 ;; FIXME: mkoeppe: Put a symbolic time zone in the date structs.
1002 ;; Print it here instead of the numerical offset if available.
1003 (define (priv:locale-print-time-zone date port)
1004 (priv:tz-printer (date-zone-offset date) port))
1005
1006 ;; FIXME: we should use strftime to determine this dynamically if possible.
1007 ;; Again, locale specific.
1008 (define (priv:locale-am/pm hr)
1009 (if (> hr 11) priv:locale-pm priv:locale-am))
1010
1011 (define (priv:tz-printer offset port)
1012 (cond
1013 ((= offset 0) (display "Z" port))
1014 ((negative? offset) (display "-" port))
1015 (else (display "+" port)))
1016 (if (not (= offset 0))
1017 (let ((hours (abs (quotient offset (* 60 60))))
1018 (minutes (abs (quotient (remainder offset (* 60 60)) 60))))
1019 (display (priv:padding hours #\0 2) port)
1020 (display (priv:padding minutes #\0 2) port))))
1021
1022 ;; A table of output formatting directives.
1023 ;; the first time is the format char.
1024 ;; the second is a procedure that takes the date, a padding character
1025 ;; (which might be #f), and the output port.
1026 ;;
1027 (define priv:directives
1028 (list
1029 (cons #\~ (lambda (date pad-with port)
1030 (display #\~ port)))
1031 (cons #\a (lambda (date pad-with port)
1032 (display (priv:locale-abbr-weekday (date-week-day date))
1033 port)))
1034 (cons #\A (lambda (date pad-with port)
1035 (display (priv:locale-long-weekday (date-week-day date))
1036 port)))
1037 (cons #\b (lambda (date pad-with port)
1038 (display (priv:locale-abbr-month (date-month date))
1039 port)))
1040 (cons #\B (lambda (date pad-with port)
1041 (display (priv:locale-long-month (date-month date))
1042 port)))
1043 (cons #\c (lambda (date pad-with port)
1044 (display (date->string date priv:locale-date-time-format) port)))
1045 (cons #\d (lambda (date pad-with port)
1046 (display (priv:padding (date-day date)
1047 #\0 2)
1048 port)))
1049 (cons #\D (lambda (date pad-with port)
1050 (display (date->string date "~m/~d/~y") port)))
1051 (cons #\e (lambda (date pad-with port)
1052 (display (priv:padding (date-day date)
1053 #\Space 2)
1054 port)))
1055 (cons #\f (lambda (date pad-with port)
1056 (if (> (date-nanosecond date)
1057 priv:nano)
1058 (display (priv:padding (+ (date-second date) 1)
1059 pad-with 2)
1060 port)
1061 (display (priv:padding (date-second date)
1062 pad-with 2)
1063 port))
1064 (receive (i f)
1065 (priv:split-real (/
1066 (date-nanosecond date)
1067 priv:nano 1.0))
1068 (let* ((ns (number->string f))
1069 (le (string-length ns)))
1070 (if (> le 2)
1071 (begin
1072 (display priv:locale-number-separator port)
1073 (display (substring ns 2 le) port)))))))
1074 (cons #\h (lambda (date pad-with port)
1075 (display (date->string date "~b") port)))
1076 (cons #\H (lambda (date pad-with port)
1077 (display (priv:padding (date-hour date)
1078 pad-with 2)
1079 port)))
1080 (cons #\I (lambda (date pad-with port)
1081 (let ((hr (date-hour date)))
1082 (if (> hr 12)
1083 (display (priv:padding (- hr 12)
1084 pad-with 2)
1085 port)
1086 (display (priv:padding hr
1087 pad-with 2)
1088 port)))))
1089 (cons #\j (lambda (date pad-with port)
1090 (display (priv:padding (date-year-day date)
1091 pad-with 3)
1092 port)))
1093 (cons #\k (lambda (date pad-with port)
1094 (display (priv:padding (date-hour date)
1095 #\Space 2)
1096 port)))
1097 (cons #\l (lambda (date pad-with port)
1098 (let ((hr (if (> (date-hour date) 12)
1099 (- (date-hour date) 12) (date-hour date))))
1100 (display (priv:padding hr #\Space 2)
1101 port))))
1102 (cons #\m (lambda (date pad-with port)
1103 (display (priv:padding (date-month date)
1104 pad-with 2)
1105 port)))
1106 (cons #\M (lambda (date pad-with port)
1107 (display (priv:padding (date-minute date)
1108 pad-with 2)
1109 port)))
1110 (cons #\n (lambda (date pad-with port)
1111 (newline port)))
1112 (cons #\N (lambda (date pad-with port)
1113 (display (priv:padding (date-nanosecond date)
1114 pad-with 7)
1115 port)))
1116 (cons #\p (lambda (date pad-with port)
1117 (display (priv:locale-am/pm (date-hour date)) port)))
1118 (cons #\r (lambda (date pad-with port)
1119 (display (date->string date "~I:~M:~S ~p") port)))
1120 (cons #\s (lambda (date pad-with port)
1121 (display (time-second (date->time-utc date)) port)))
1122 (cons #\S (lambda (date pad-with port)
1123 (if (> (date-nanosecond date)
1124 priv:nano)
1125 (display (priv:padding (+ (date-second date) 1)
1126 pad-with 2)
1127 port)
1128 (display (priv:padding (date-second date)
1129 pad-with 2)
1130 port))))
1131 (cons #\t (lambda (date pad-with port)
1132 (display #\Tab port)))
1133 (cons #\T (lambda (date pad-with port)
1134 (display (date->string date "~H:~M:~S") port)))
1135 (cons #\U (lambda (date pad-with port)
1136 (if (> (priv:days-before-first-week date 0) 0)
1137 (display (priv:padding (+ (date-week-number date 0) 1)
1138 #\0 2) port)
1139 (display (priv:padding (date-week-number date 0)
1140 #\0 2) port))))
1141 (cons #\V (lambda (date pad-with port)
1142 (display (priv:padding (date-week-number date 1)
1143 #\0 2) port)))
1144 (cons #\w (lambda (date pad-with port)
1145 (display (date-week-day date) port)))
1146 (cons #\x (lambda (date pad-with port)
1147 (display (date->string date priv:locale-short-date-format) port)))
1148 (cons #\X (lambda (date pad-with port)
1149 (display (date->string date priv:locale-time-format) port)))
1150 (cons #\W (lambda (date pad-with port)
1151 (if (> (priv:days-before-first-week date 1) 0)
1152 (display (priv:padding (+ (date-week-number date 1) 1)
1153 #\0 2) port)
1154 (display (priv:padding (date-week-number date 1)
1155 #\0 2) port))))
1156 (cons #\y (lambda (date pad-with port)
1157 (display (priv:padding (priv:last-n-digits
1158 (date-year date) 2)
1159 pad-with
1160 2)
1161 port)))
1162 (cons #\Y (lambda (date pad-with port)
1163 (display (date-year date) port)))
1164 (cons #\z (lambda (date pad-with port)
1165 (priv:tz-printer (date-zone-offset date) port)))
1166 (cons #\Z (lambda (date pad-with port)
1167 (priv:locale-print-time-zone date port)))
1168 (cons #\1 (lambda (date pad-with port)
1169 (display (date->string date "~Y-~m-~d") port)))
1170 (cons #\2 (lambda (date pad-with port)
1171 (display (date->string date "~k:~M:~S~z") port)))
1172 (cons #\3 (lambda (date pad-with port)
1173 (display (date->string date "~k:~M:~S") port)))
1174 (cons #\4 (lambda (date pad-with port)
1175 (display (date->string date "~Y-~m-~dT~k:~M:~S~z") port)))
1176 (cons #\5 (lambda (date pad-with port)
1177 (display (date->string date "~Y-~m-~dT~k:~M:~S") port)))))
1178
1179
1180 (define (priv:get-formatter char)
1181 (let ((associated (assoc char priv:directives)))
1182 (if associated (cdr associated) #f)))
1183
1184 (define (priv:date-printer date index format-string str-len port)
1185 (if (>= index str-len)
1186 (values)
1187 (let ((current-char (string-ref format-string index)))
1188 (if (not (char=? current-char #\~))
1189 (begin
1190 (display current-char port)
1191 (priv:date-printer date (+ index 1) format-string str-len port))
1192 (if (= (+ index 1) str-len) ; bad format string.
1193 (priv:time-error 'priv:date-printer 'bad-date-format-string
1194 format-string)
1195 (let ((pad-char? (string-ref format-string (+ index 1))))
1196 (cond
1197 ((char=? pad-char? #\-)
1198 (if (= (+ index 2) str-len) ; bad format string.
1199 (priv:time-error 'priv:date-printer
1200 'bad-date-format-string
1201 format-string)
1202 (let ((formatter (priv:get-formatter
1203 (string-ref format-string
1204 (+ index 2)))))
1205 (if (not formatter)
1206 (priv:time-error 'priv:date-printer
1207 'bad-date-format-string
1208 format-string)
1209 (begin
1210 (formatter date #f port)
1211 (priv:date-printer date
1212 (+ index 3)
1213 format-string
1214 str-len
1215 port))))))
1216
1217 ((char=? pad-char? #\_)
1218 (if (= (+ index 2) str-len) ; bad format string.
1219 (priv:time-error 'priv:date-printer
1220 'bad-date-format-string
1221 format-string)
1222 (let ((formatter (priv:get-formatter
1223 (string-ref format-string
1224 (+ index 2)))))
1225 (if (not formatter)
1226 (priv:time-error 'priv:date-printer
1227 'bad-date-format-string
1228 format-string)
1229 (begin
1230 (formatter date #\Space port)
1231 (priv:date-printer date
1232 (+ index 3)
1233 format-string
1234 str-len
1235 port))))))
1236 (else
1237 (let ((formatter (priv:get-formatter
1238 (string-ref format-string
1239 (+ index 1)))))
1240 (if (not formatter)
1241 (priv:time-error 'priv:date-printer
1242 'bad-date-format-string
1243 format-string)
1244 (begin
1245 (formatter date #\0 port)
1246 (priv:date-printer date
1247 (+ index 2)
1248 format-string
1249 str-len
1250 port))))))))))))
1251
1252
1253 (define (date->string date . format-string)
1254 (let ((str-port (open-output-string))
1255 (fmt-str (if (null? format-string) "~c" (car format-string))))
1256 (priv:date-printer date 0 fmt-str (string-length fmt-str) str-port)
1257 (get-output-string str-port)))
1258
1259 (define (priv:char->int ch)
1260 (case ch
1261 ((#\0) 0)
1262 ((#\1) 1)
1263 ((#\2) 2)
1264 ((#\3) 3)
1265 ((#\4) 4)
1266 ((#\5) 5)
1267 ((#\6) 6)
1268 ((#\7) 7)
1269 ((#\8) 8)
1270 ((#\9) 9)
1271 (else (priv:time-error 'bad-date-template-string
1272 (list "Non-integer character" ch i)))))
1273
1274 ;; read an integer upto n characters long on port; upto -> #f is any length
1275 (define (priv:integer-reader upto port)
1276 (let loop ((accum 0) (nchars 0))
1277 (let ((ch (peek-char port)))
1278 (if (or (eof-object? ch)
1279 (not (char-numeric? ch))
1280 (and upto (>= nchars upto)))
1281 accum
1282 (loop (+ (* accum 10) (priv:char->int (read-char port)))
1283 (+ nchars 1))))))
1284
1285 (define (priv:make-integer-reader upto)
1286 (lambda (port)
1287 (priv:integer-reader upto port)))
1288
1289 ;; read *exactly* n characters and convert to integer; could be padded
1290 (define (priv:integer-reader-exact n port)
1291 (let ((padding-ok #t))
1292 (define (accum-int port accum nchars)
1293 (let ((ch (peek-char port)))
1294 (cond
1295 ((>= nchars n) accum)
1296 ((eof-object? ch)
1297 (priv:time-error 'string->date 'bad-date-template-string
1298 "Premature ending to integer read."))
1299 ((char-numeric? ch)
1300 (set! padding-ok #f)
1301 (accum-int port
1302 (+ (* accum 10) (priv:char->int (read-char port)))
1303 (+ nchars 1)))
1304 (padding-ok
1305 (read-char port) ; consume padding
1306 (accum-int port accum (+ nchars 1)))
1307 (else ; padding where it shouldn't be
1308 (priv:time-error 'string->date 'bad-date-template-string
1309 "Non-numeric characters in integer read.")))))
1310 (accum-int port 0 0)))
1311
1312
1313 (define (priv:make-integer-exact-reader n)
1314 (lambda (port)
1315 (priv:integer-reader-exact n port)))
1316
1317 (define (priv:zone-reader port)
1318 (let ((offset 0)
1319 (positive? #f))
1320 (let ((ch (read-char port)))
1321 (if (eof-object? ch)
1322 (priv:time-error 'string->date 'bad-date-template-string
1323 (list "Invalid time zone +/-" ch)))
1324 (if (or (char=? ch #\Z) (char=? ch #\z))
1325 0
1326 (begin
1327 (cond
1328 ((char=? ch #\+) (set! positive? #t))
1329 ((char=? ch #\-) (set! positive? #f))
1330 (else
1331 (priv:time-error 'string->date 'bad-date-template-string
1332 (list "Invalid time zone +/-" ch))))
1333 (let ((ch (read-char port)))
1334 (if (eof-object? ch)
1335 (priv:time-error 'string->date 'bad-date-template-string
1336 (list "Invalid time zone number" ch)))
1337 (set! offset (* (priv:char->int ch)
1338 10 60 60)))
1339 (let ((ch (read-char port)))
1340 (if (eof-object? ch)
1341 (priv:time-error 'string->date 'bad-date-template-string
1342 (list "Invalid time zone number" ch)))
1343 (set! offset (+ offset (* (priv:char->int ch)
1344 60 60))))
1345 (let ((ch (read-char port)))
1346 (if (eof-object? ch)
1347 (priv:time-error 'string->date 'bad-date-template-string
1348 (list "Invalid time zone number" ch)))
1349 (set! offset (+ offset (* (priv:char->int ch)
1350 10 60))))
1351 (let ((ch (read-char port)))
1352 (if (eof-object? ch)
1353 (priv:time-error 'string->date 'bad-date-template-string
1354 (list "Invalid time zone number" ch)))
1355 (set! offset (+ offset (* (priv:char->int ch)
1356 60))))
1357 (if positive? offset (- offset)))))))
1358
1359 ;; looking at a char, read the char string, run thru indexer, return index
1360 (define (priv:locale-reader port indexer)
1361
1362 (define (read-char-string result)
1363 (let ((ch (peek-char port)))
1364 (if (char-alphabetic? ch)
1365 (read-char-string (cons (read-char port) result))
1366 (list->string (reverse! result)))))
1367
1368 (let* ((str (read-char-string '()))
1369 (index (indexer str)))
1370 (if index index (priv:time-error 'string->date
1371 'bad-date-template-string
1372 (list "Invalid string for " indexer)))))
1373
1374 (define (priv:make-locale-reader indexer)
1375 (lambda (port)
1376 (priv:locale-reader port indexer)))
1377
1378 (define (priv:make-char-id-reader char)
1379 (lambda (port)
1380 (if (char=? char (read-char port))
1381 char
1382 (priv:time-error 'string->date
1383 'bad-date-template-string
1384 "Invalid character match."))))
1385
1386 ;; A List of formatted read directives.
1387 ;; Each entry is a list.
1388 ;; 1. the character directive;
1389 ;; a procedure, which takes a character as input & returns
1390 ;; 2. #t as soon as a character on the input port is acceptable
1391 ;; for input,
1392 ;; 3. a port reader procedure that knows how to read the current port
1393 ;; for a value. Its one parameter is the port.
1394 ;; 4. a action procedure, that takes the value (from 3.) and some
1395 ;; object (here, always the date) and (probably) side-effects it.
1396 ;; In some cases (e.g., ~A) the action is to do nothing
1397
1398 (define priv:read-directives
1399 (let ((ireader4 (priv:make-integer-reader 4))
1400 (ireader2 (priv:make-integer-reader 2))
1401 (ireaderf (priv:make-integer-reader #f))
1402 (eireader2 (priv:make-integer-exact-reader 2))
1403 (eireader4 (priv:make-integer-exact-reader 4))
1404 (locale-reader-abbr-weekday (priv:make-locale-reader
1405 priv:locale-abbr-weekday->index))
1406 (locale-reader-long-weekday (priv:make-locale-reader
1407 priv:locale-long-weekday->index))
1408 (locale-reader-abbr-month (priv:make-locale-reader
1409 priv:locale-abbr-month->index))
1410 (locale-reader-long-month (priv:make-locale-reader
1411 priv:locale-long-month->index))
1412 (char-fail (lambda (ch) #t))
1413 (do-nothing (lambda (val object) (values))))
1414
1415 (list
1416 (list #\~ char-fail (priv:make-char-id-reader #\~) do-nothing)
1417 (list #\a char-alphabetic? locale-reader-abbr-weekday do-nothing)
1418 (list #\A char-alphabetic? locale-reader-long-weekday do-nothing)
1419 (list #\b char-alphabetic? locale-reader-abbr-month
1420 (lambda (val object)
1421 (set-date-month! object val)))
1422 (list #\B char-alphabetic? locale-reader-long-month
1423 (lambda (val object)
1424 (set-date-month! object val)))
1425 (list #\d char-numeric? ireader2 (lambda (val object)
1426 (set-date-day!
1427 object val)))
1428 (list #\e char-fail eireader2 (lambda (val object)
1429 (set-date-day! object val)))
1430 (list #\h char-alphabetic? locale-reader-abbr-month
1431 (lambda (val object)
1432 (set-date-month! object val)))
1433 (list #\H char-numeric? ireader2 (lambda (val object)
1434 (set-date-hour! object val)))
1435 (list #\k char-fail eireader2 (lambda (val object)
1436 (set-date-hour! object val)))
1437 (list #\m char-numeric? ireader2 (lambda (val object)
1438 (set-date-month! object val)))
1439 (list #\M char-numeric? ireader2 (lambda (val object)
1440 (set-date-minute!
1441 object val)))
1442 (list #\S char-numeric? ireader2 (lambda (val object)
1443 (set-date-second! object val)))
1444 (list #\y char-fail eireader2
1445 (lambda (val object)
1446 (set-date-year! object (priv:natural-year val))))
1447 (list #\Y char-numeric? ireader4 (lambda (val object)
1448 (set-date-year! object val)))
1449 (list #\z (lambda (c)
1450 (or (char=? c #\Z)
1451 (char=? c #\z)
1452 (char=? c #\+)
1453 (char=? c #\-)))
1454 priv:zone-reader (lambda (val object)
1455 (set-date-zone-offset! object val))))))
1456
1457 (define (priv:string->date date index format-string str-len port template-string)
1458 (define (skip-until port skipper)
1459 (let ((ch (peek-char port)))
1460 (if (eof-object? port)
1461 (priv:time-error 'string->date 'bad-date-format-string template-string)
1462 (if (not (skipper ch))
1463 (begin (read-char port) (skip-until port skipper))))))
1464 (if (>= index str-len)
1465 (begin
1466 (values))
1467 (let ((current-char (string-ref format-string index)))
1468 (if (not (char=? current-char #\~))
1469 (let ((port-char (read-char port)))
1470 (if (or (eof-object? port-char)
1471 (not (char=? current-char port-char)))
1472 (priv:time-error 'string->date
1473 'bad-date-format-string template-string))
1474 (priv:string->date date
1475 (+ index 1)
1476 format-string
1477 str-len
1478 port
1479 template-string))
1480 ;; otherwise, it's an escape, we hope
1481 (if (> (+ index 1) str-len)
1482 (priv:time-error 'string->date
1483 'bad-date-format-string template-string)
1484 (let* ((format-char (string-ref format-string (+ index 1)))
1485 (format-info (assoc format-char priv:read-directives)))
1486 (if (not format-info)
1487 (priv:time-error 'string->date
1488 'bad-date-format-string template-string)
1489 (begin
1490 (let ((skipper (cadr format-info))
1491 (reader (caddr format-info))
1492 (actor (cadddr format-info)))
1493 (skip-until port skipper)
1494 (let ((val (reader port)))
1495 (if (eof-object? val)
1496 (priv:time-error 'string->date
1497 'bad-date-format-string
1498 template-string)
1499 (actor val date)))
1500 (priv:string->date date
1501 (+ index 2)
1502 format-string
1503 str-len
1504 port
1505 template-string))))))))))
1506
1507 (define (string->date input-string template-string)
1508 (define (priv:date-ok? date)
1509 (and (date-nanosecond date)
1510 (date-second date)
1511 (date-minute date)
1512 (date-hour date)
1513 (date-day date)
1514 (date-month date)
1515 (date-year date)
1516 (date-zone-offset date)))
1517 (let ((newdate (make-date 0 0 0 0 #f #f #f #f)))
1518 (priv:string->date newdate
1519 0
1520 template-string
1521 (string-length template-string)
1522 (open-input-string input-string)
1523 template-string)
1524 (if (not (date-zone-offset newdate))
1525 (begin
1526 ;; this is necessary to get DST right -- as far as we can
1527 ;; get it right (think of the double/missing hour in the
1528 ;; night when we are switching between normal time and DST).
1529 (set-date-zone-offset! newdate
1530 (priv:local-tz-offset
1531 (make-time time-utc 0 0)))
1532 (set-date-zone-offset! newdate
1533 (priv:local-tz-offset
1534 (date->time-utc newdate)))))
1535 (if (priv:date-ok? newdate)
1536 newdate
1537 (priv:time-error
1538 'string->date
1539 'bad-date-format-string
1540 (list "Incomplete date read. " newdate template-string)))))
1541
1542 ;;; srfi-19.scm ends here