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