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