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