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