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