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