* convert.c: include <string.h> for convert_i.c.
[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.
afb47f6d 4;;;
5bbfe8cb
RB
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.
afb47f6d 9;;;
5bbfe8cb
RB
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.
afb47f6d 14;;;
5bbfe8cb
RB
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
5bbfe8cb
RB
136 time-monotonic->time-tai
137 time-monotonic->time-tai!
138 time-monotonic->time-utc
139 time-monotonic->time-utc!
140 time-tai->date
141 time-tai->julian-day
142 time-tai->modified-julian-day
143 time-tai->time-monotonic
144 time-tai->time-monotonic!
145 time-tai->time-utc
146 time-tai->time-utc!
147 time-utc->date
148 time-utc->julian-day
149 time-utc->modified-julian-day
150 time-utc->time-monotonic
151 time-utc->time-monotonic!
152 time-utc->time-tai
153 time-utc->time-tai!
154 ;; Date to string/string to date converters.
155 date->string
4c4185ee 156 string->date)
5bbfe8cb 157
1b2f40b9
MG
158(cond-expand-provide (current-module) '(srfi-19))
159
5bbfe8cb
RB
160(define time-tai 'time-tai)
161(define time-utc 'time-utc)
162(define time-monotonic 'time-monotonic)
163(define time-thread 'time-thread)
164(define time-process 'time-process)
165(define time-duration 'time-duration)
166
167;; FIXME: do we want to add gc time?
168;; (define time-gc 'time-gc)
169
170;;-- LOCALE dependent constants
171
172(define priv:locale-number-separator ".")
173
174(define priv:locale-abbr-weekday-vector
afb47f6d 175 (vector "Sun" "Mon" "Tue" "Wed" "Thu" "Fri" "Sat"))
5bbfe8cb
RB
176
177(define priv:locale-long-weekday-vector
178 (vector
179 "Sunday" "Monday" "Tuesday" "Wednesday" "Thursday" "Friday" "Saturday"))
180
afb47f6d 181;; note empty string in 0th place.
5bbfe8cb
RB
182(define priv:locale-abbr-month-vector
183 (vector ""
184 "Jan"
185 "Feb"
186 "Mar"
187 "Apr"
188 "May"
189 "Jun"
190 "Jul"
191 "Aug"
192 "Sep"
193 "Oct"
194 "Nov"
afb47f6d 195 "Dec"))
5bbfe8cb
RB
196
197(define priv:locale-long-month-vector
198 (vector ""
199 "January"
200 "February"
201 "March"
202 "April"
203 "May"
204 "June"
205 "July"
206 "August"
207 "September"
208 "October"
209 "November"
afb47f6d 210 "December"))
5bbfe8cb
RB
211
212(define priv:locale-pm "PM")
213(define priv:locale-am "AM")
214
215;; See date->string
216(define priv:locale-date-time-format "~a ~b ~d ~H:~M:~S~z ~Y")
217(define priv:locale-short-date-format "~m/~d/~y")
218(define priv:locale-time-format "~H:~M:~S")
219(define priv:iso-8601-date-time-format "~Y-~m-~dT~H:~M:~S~z")
220
221;;-- Miscellaneous Constants.
222;;-- only the priv:tai-epoch-in-jd might need changing if
223;; a different epoch is used.
224
225(define priv:nano 1000000000) ; nanoseconds in a second
226(define priv:sid 86400) ; seconds in a day
227(define priv:sihd 43200) ; seconds in a half day
228(define priv:tai-epoch-in-jd 4881175/2) ; julian day number for 'the epoch'
229
230;; FIXME: should this be something other than misc-error?
231(define (priv:time-error caller type value)
232 (if value
233 (throw 'misc-error caller "TIME-ERROR type ~A: ~S" (list type value) #f)
234 (throw 'misc-error caller "TIME-ERROR type ~A" (list type) #f)))
235
236;; A table of leap seconds
237;; See ftp://maia.usno.navy.mil/ser7/tai-utc.dat
238;; and update as necessary.
239;; this procedures reads the file in the abover
240;; format and creates the leap second table
afb47f6d 241;; it also calls the almost standard, but not R5 procedures read-line
5bbfe8cb
RB
242;; & open-input-string
243;; ie (set! priv:leap-second-table (priv:read-tai-utc-date "tai-utc.dat"))
244
245(define (priv:read-tai-utc-data filename)
246 (define (convert-jd jd)
247 (* (- (inexact->exact jd) priv:tai-epoch-in-jd) priv:sid))
248 (define (convert-sec sec)
249 (inexact->exact sec))
250 (let ((port (open-input-file filename))
251 (table '()))
252 (let loop ((line (read-line port)))
253 (if (not (eq? line eof))
254 (begin
255 (let* ((data (read (open-input-string
afb47f6d 256 (string-append "(" line ")"))))
5bbfe8cb
RB
257 (year (car data))
258 (jd (cadddr (cdr data)))
259 (secs (cadddr (cdddr data))))
260 (if (>= year 1972)
261 (set! table (cons
262 (cons (convert-jd jd) (convert-sec secs))
263 table)))
264 (loop (read-line port))))))
265 table))
266
267;; each entry is (tai seconds since epoch . # seconds to subtract for utc)
268;; note they go higher to lower, and end in 1972.
269(define priv:leap-second-table
270 '((915148800 . 32)
271 (867715200 . 31)
272 (820454400 . 30)
273 (773020800 . 29)
274 (741484800 . 28)
275 (709948800 . 27)
276 (662688000 . 26)
277 (631152000 . 25)
278 (567993600 . 24)
279 (489024000 . 23)
280 (425865600 . 22)
281 (394329600 . 21)
282 (362793600 . 20)
283 (315532800 . 19)
284 (283996800 . 18)
285 (252460800 . 17)
286 (220924800 . 16)
287 (189302400 . 15)
288 (157766400 . 14)
289 (126230400 . 13)
290 (94694400 . 12)
291 (78796800 . 11)
292 (63072000 . 10)))
293
294(define (read-leap-second-table filename)
295 (set! priv:leap-second-table (priv:read-tai-utc-data filename))
296 (values))
297
298
299(define (priv:leap-second-delta utc-seconds)
300 (letrec ((lsd (lambda (table)
301 (cond ((>= utc-seconds (caar table))
302 (cdar table))
303 (else (lsd (cdr table)))))))
304 (if (< utc-seconds (* (- 1972 1970) 365 priv:sid)) 0
305 (lsd priv:leap-second-table))))
306
307
308;;; the TIME structure; creates the accessors, too.
309
310(define-record-type time
311 (make-time-unnormalized type nanosecond second)
312 time?
313 (type time-type set-time-type!)
314 (nanosecond time-nanosecond set-time-nanosecond!)
315 (second time-second set-time-second!))
316
317(define (copy-time time)
318 (make-time (time-type time) (time-nanosecond time) (time-second time)))
319
4549ba4a 320(define (priv:split-real r)
b21cccf3
MD
321 (if (integer? r)
322 (values (inexact->exact r) 0)
4549ba4a
MV
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 503 (let ((result (copy-time t)))
afb47f6d 504 (add-duration! result duration)))
5bbfe8cb
RB
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)
afb47f6d 527 (priv:leap-second-delta
5bbfe8cb
RB
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)
afb47f6d 544 (priv:leap-second-delta
5bbfe8cb
RB
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
afb47f6d 643;; gives the seconds/date/month/year
5bbfe8cb 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 679 (let* ((offset (if (null? tz-offset)
afb47f6d 680 (priv:local-tz-offset time)
4549ba4a 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))))
afb47f6d 778 (make-time
5bbfe8cb
RB
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
359b471e
NJ
800;; Map 1-based month number M to number of days in the year before the
801;; start of month M (in a non-leap year).
802(define priv:month-assoc '((1 . 0) (2 . 31) (3 . 59) (4 . 90)
803 (5 . 120) (6 . 151) (7 . 181) (8 . 212)
804 (9 . 243) (10 . 273) (11 . 304) (12 . 334)))
5bbfe8cb
RB
805
806(define (priv:year-day day month year)
cd328b4f 807 (let ((days-pr (assoc month priv:month-assoc)))
5bbfe8cb
RB
808 (if (not days-pr)
809 (priv:error 'date-year-day 'invalid-month-specification month))
810 (if (and (priv:leap-year? year) (> month 2))
811 (+ day (cdr days-pr) 1)
812 (+ day (cdr days-pr)))))
813
814(define (date-year-day date)
815 (priv:year-day (date-day date) (date-month date) (date-year date)))
816
afb47f6d 817;; from calendar faq
5bbfe8cb
RB
818(define (priv:week-day day month year)
819 (let* ((a (quotient (- 14 month) 12))
820 (y (- year a))
821 (m (+ month (* 12 a) -2)))
822 (modulo (+ day
823 y
824 (quotient y 4)
825 (- (quotient y 100))
826 (quotient y 400)
827 (quotient (* 31 m) 12))
828 7)))
829
830(define (date-week-day date)
831 (priv:week-day (date-day date) (date-month date) (date-year date)))
832
833(define (priv:days-before-first-week date day-of-week-starting-week)
834 (let* ((first-day (make-date 0 0 0 0
835 1
836 1
837 (date-year date)
838 #f))
839 (fdweek-day (date-week-day first-day)))
840 (modulo (- day-of-week-starting-week fdweek-day)
841 7)))
842
843(define (date-week-number date day-of-week-starting-week)
844 (quotient (- (date-year-day date)
845 (priv:days-before-first-week date day-of-week-starting-week))
846 7))
847
afb47f6d 848(define (current-date . tz-offset)
4549ba4a
MV
849 (let ((time (current-time time-utc)))
850 (time-utc->date
851 time
852 (if (null? tz-offset)
853 (priv:local-tz-offset time)
854 (car tz-offset)))))
5bbfe8cb
RB
855
856;; given a 'two digit' number, find the year within 50 years +/-
857(define (priv:natural-year n)
858 (let* ((current-year (date-year (current-date)))
859 (current-century (* (quotient current-year 100) 100)))
860 (cond
861 ((>= n 100) n)
862 ((< n 0) n)
863 ((<= (- (+ current-century n) current-year) 50) (+ current-century n))
864 (else (+ (- current-century 100) n)))))
865
866(define (date->julian-day date)
867 (let ((nanosecond (date-nanosecond date))
868 (second (date-second date))
869 (minute (date-minute date))
870 (hour (date-hour date))
871 (day (date-day date))
872 (month (date-month date))
873 (year (date-year date)))
874 (+ (priv:encode-julian-day-number day month year)
875 (- 1/2)
876 (+ (/ (+ (* hour 60 60)
877 (* minute 60)
878 second
879 (/ nanosecond priv:nano))
880 priv:sid)))))
881
882(define (date->modified-julian-day date)
883 (- (date->julian-day date)
884 4800001/2))
885
886(define (time-utc->julian-day time)
887 (if (not (eq? (time-type time) time-utc))
888 (priv:time-error 'time->date 'incompatible-time-types time))
889 (+ (/ (+ (time-second time) (/ (time-nanosecond time) priv:nano))
890 priv:sid)
891 priv:tai-epoch-in-jd))
892
893(define (time-utc->modified-julian-day time)
894 (- (time-utc->julian-day time)
895 4800001/2))
896
897(define (time-tai->julian-day time)
898 (if (not (eq? (time-type time) time-tai))
899 (priv:time-error 'time->date 'incompatible-time-types time))
afb47f6d 900 (+ (/ (+ (- (time-second time)
5bbfe8cb
RB
901 (priv:leap-second-delta (time-second time)))
902 (/ (time-nanosecond time) priv:nano))
903 priv:sid)
904 priv:tai-epoch-in-jd))
905
906(define (time-tai->modified-julian-day time)
907 (- (time-tai->julian-day time)
908 4800001/2))
909
910;; this is the same as time-tai->julian-day
911(define (time-monotonic->julian-day time)
912 (if (not (eq? (time-type time) time-monotonic))
913 (priv:time-error 'time->date 'incompatible-time-types time))
afb47f6d 914 (+ (/ (+ (- (time-second time)
5bbfe8cb
RB
915 (priv:leap-second-delta (time-second time)))
916 (/ (time-nanosecond time) priv:nano))
917 priv:sid)
918 priv:tai-epoch-in-jd))
919
920(define (time-monotonic->modified-julian-day time)
921 (- (time-monotonic->julian-day time)
922 4800001/2))
923
924(define (julian-day->time-utc jdn)
925 (let ((secs (* priv:sid (- jdn priv:tai-epoch-in-jd))))
926 (receive (seconds parts)
4549ba4a 927 (priv:split-real secs)
afb47f6d 928 (make-time time-utc
4549ba4a
MV
929 (* parts priv:nano)
930 seconds))))
5bbfe8cb
RB
931
932(define (julian-day->time-tai jdn)
933 (time-utc->time-tai! (julian-day->time-utc jdn)))
934
935(define (julian-day->time-monotonic jdn)
936 (time-utc->time-monotonic! (julian-day->time-utc jdn)))
937
938(define (julian-day->date jdn . tz-offset)
4549ba4a
MV
939 (let* ((time (julian-day->time-utc jdn))
940 (offset (if (null? tz-offset)
941 (priv:local-tz-offset time)
942 (car tz-offset))))
943 (time-utc->date time offset)))
5bbfe8cb
RB
944
945(define (modified-julian-day->date jdn . tz-offset)
4549ba4a
MV
946 (apply julian-day->date (+ jdn 4800001/2)
947 tz-offset))
5bbfe8cb
RB
948
949(define (modified-julian-day->time-utc jdn)
950 (julian-day->time-utc (+ jdn 4800001/2)))
951
952(define (modified-julian-day->time-tai jdn)
953 (julian-day->time-tai (+ jdn 4800001/2)))
954
955(define (modified-julian-day->time-monotonic jdn)
956 (julian-day->time-monotonic (+ jdn 4800001/2)))
957
958(define (current-julian-day)
959 (time-utc->julian-day (current-time time-utc)))
960
961(define (current-modified-julian-day)
962 (time-utc->modified-julian-day (current-time time-utc)))
963
964;; returns a string rep. of number N, of minimum LENGTH, padded with
965;; character PAD-WITH. If PAD-WITH is #f, no padding is done, and it's
966;; as if number->string was used. if string is longer than or equal
967;; in length to LENGTH, it's as if number->string was used.
968
969(define (priv:padding n pad-with length)
970 (let* ((str (number->string n))
971 (str-len (string-length str)))
972 (if (or (>= str-len length)
973 (not pad-with))
974 str
975 (string-append (make-string (- length str-len) pad-with) str))))
976
977(define (priv:last-n-digits i n)
978 (abs (remainder i (expt 10 n))))
979
afb47f6d 980(define (priv:locale-abbr-weekday n)
5bbfe8cb
RB
981 (vector-ref priv:locale-abbr-weekday-vector n))
982
983(define (priv:locale-long-weekday n)
984 (vector-ref priv:locale-long-weekday-vector n))
985
986(define (priv:locale-abbr-month n)
987 (vector-ref priv:locale-abbr-month-vector n))
988
989(define (priv:locale-long-month n)
990 (vector-ref priv:locale-long-month-vector n))
991
992(define (priv:vector-find needle haystack comparator)
993 (let ((len (vector-length haystack)))
994 (define (priv:vector-find-int index)
995 (cond
996 ((>= index len) #f)
997 ((comparator needle (vector-ref haystack index)) index)
998 (else (priv:vector-find-int (+ index 1)))))
999 (priv:vector-find-int 0)))
1000
1001(define (priv:locale-abbr-weekday->index string)
1002 (priv:vector-find string priv:locale-abbr-weekday-vector string=?))
1003
1004(define (priv:locale-long-weekday->index string)
1005 (priv:vector-find string priv:locale-long-weekday-vector string=?))
1006
1007(define (priv:locale-abbr-month->index string)
1008 (priv:vector-find string priv:locale-abbr-month-vector string=?))
1009
1010(define (priv:locale-long-month->index string)
1011 (priv:vector-find string priv:locale-long-month-vector string=?))
1012
1013
4549ba4a
MV
1014;; FIXME: mkoeppe: Put a symbolic time zone in the date structs.
1015;; Print it here instead of the numerical offset if available.
5bbfe8cb 1016(define (priv:locale-print-time-zone date port)
4549ba4a 1017 (priv:tz-printer (date-zone-offset date) port))
5bbfe8cb
RB
1018
1019;; FIXME: we should use strftime to determine this dynamically if possible.
1020;; Again, locale specific.
1021(define (priv:locale-am/pm hr)
1022 (if (> hr 11) priv:locale-pm priv:locale-am))
1023
1024(define (priv:tz-printer offset port)
1025 (cond
1026 ((= offset 0) (display "Z" port))
1027 ((negative? offset) (display "-" port))
1028 (else (display "+" port)))
1029 (if (not (= offset 0))
1030 (let ((hours (abs (quotient offset (* 60 60))))
1031 (minutes (abs (quotient (remainder offset (* 60 60)) 60))))
1032 (display (priv:padding hours #\0 2) port)
1033 (display (priv:padding minutes #\0 2) port))))
1034
5bbfe8cb
RB
1035;; A table of output formatting directives.
1036;; the first time is the format char.
1037;; the second is a procedure that takes the date, a padding character
1038;; (which might be #f), and the output port.
1039;;
afb47f6d 1040(define priv:directives
5bbfe8cb
RB
1041 (list
1042 (cons #\~ (lambda (date pad-with port)
1043 (display #\~ port)))
1044 (cons #\a (lambda (date pad-with port)
1045 (display (priv:locale-abbr-weekday (date-week-day date))
1046 port)))
1047 (cons #\A (lambda (date pad-with port)
1048 (display (priv:locale-long-weekday (date-week-day date))
1049 port)))
1050 (cons #\b (lambda (date pad-with port)
1051 (display (priv:locale-abbr-month (date-month date))
1052 port)))
1053 (cons #\B (lambda (date pad-with port)
1054 (display (priv:locale-long-month (date-month date))
1055 port)))
1056 (cons #\c (lambda (date pad-with port)
1057 (display (date->string date priv:locale-date-time-format) port)))
1058 (cons #\d (lambda (date pad-with port)
1059 (display (priv:padding (date-day date)
1060 #\0 2)
1061 port)))
1062 (cons #\D (lambda (date pad-with port)
1063 (display (date->string date "~m/~d/~y") port)))
1064 (cons #\e (lambda (date pad-with port)
1065 (display (priv:padding (date-day date)
1066 #\Space 2)
1067 port)))
1068 (cons #\f (lambda (date pad-with port)
1069 (if (> (date-nanosecond date)
1070 priv:nano)
1071 (display (priv:padding (+ (date-second date) 1)
1072 pad-with 2)
1073 port)
1074 (display (priv:padding (date-second date)
1075 pad-with 2)
1076 port))
afb47f6d
TTN
1077 (receive (i f)
1078 (priv:split-real (/
5bbfe8cb
RB
1079 (date-nanosecond date)
1080 priv:nano 1.0))
1081 (let* ((ns (number->string f))
1082 (le (string-length ns)))
1083 (if (> le 2)
1084 (begin
1085 (display priv:locale-number-separator port)
1086 (display (substring ns 2 le) port)))))))
1087 (cons #\h (lambda (date pad-with port)
1088 (display (date->string date "~b") port)))
1089 (cons #\H (lambda (date pad-with port)
1090 (display (priv:padding (date-hour date)
1091 pad-with 2)
1092 port)))
1093 (cons #\I (lambda (date pad-with port)
1094 (let ((hr (date-hour date)))
1095 (if (> hr 12)
1096 (display (priv:padding (- hr 12)
1097 pad-with 2)
1098 port)
1099 (display (priv:padding hr
1100 pad-with 2)
1101 port)))))
1102 (cons #\j (lambda (date pad-with port)
1103 (display (priv:padding (date-year-day date)
1104 pad-with 3)
1105 port)))
1106 (cons #\k (lambda (date pad-with port)
1107 (display (priv:padding (date-hour date)
1108 #\Space 2)
1109 port)))
1110 (cons #\l (lambda (date pad-with port)
1111 (let ((hr (if (> (date-hour date) 12)
1112 (- (date-hour date) 12) (date-hour date))))
1113 (display (priv:padding hr #\Space 2)
1114 port))))
1115 (cons #\m (lambda (date pad-with port)
1116 (display (priv:padding (date-month date)
1117 pad-with 2)
1118 port)))
1119 (cons #\M (lambda (date pad-with port)
1120 (display (priv:padding (date-minute date)
1121 pad-with 2)
1122 port)))
1123 (cons #\n (lambda (date pad-with port)
1124 (newline port)))
1125 (cons #\N (lambda (date pad-with port)
1126 (display (priv:padding (date-nanosecond date)
1127 pad-with 7)
1128 port)))
1129 (cons #\p (lambda (date pad-with port)
1130 (display (priv:locale-am/pm (date-hour date)) port)))
1131 (cons #\r (lambda (date pad-with port)
1132 (display (date->string date "~I:~M:~S ~p") port)))
1133 (cons #\s (lambda (date pad-with port)
1134 (display (time-second (date->time-utc date)) port)))
1135 (cons #\S (lambda (date pad-with port)
1136 (if (> (date-nanosecond date)
1137 priv:nano)
1138 (display (priv:padding (+ (date-second date) 1)
1139 pad-with 2)
1140 port)
1141 (display (priv:padding (date-second date)
1142 pad-with 2)
1143 port))))
1144 (cons #\t (lambda (date pad-with port)
1145 (display #\Tab port)))
1146 (cons #\T (lambda (date pad-with port)
1147 (display (date->string date "~H:~M:~S") port)))
1148 (cons #\U (lambda (date pad-with port)
1149 (if (> (priv:days-before-first-week date 0) 0)
1150 (display (priv:padding (+ (date-week-number date 0) 1)
1151 #\0 2) port)
1152 (display (priv:padding (date-week-number date 0)
1153 #\0 2) port))))
1154 (cons #\V (lambda (date pad-with port)
1155 (display (priv:padding (date-week-number date 1)
1156 #\0 2) port)))
1157 (cons #\w (lambda (date pad-with port)
1158 (display (date-week-day date) port)))
1159 (cons #\x (lambda (date pad-with port)
1160 (display (date->string date priv:locale-short-date-format) port)))
1161 (cons #\X (lambda (date pad-with port)
1162 (display (date->string date priv:locale-time-format) port)))
1163 (cons #\W (lambda (date pad-with port)
1164 (if (> (priv:days-before-first-week date 1) 0)
1165 (display (priv:padding (+ (date-week-number date 1) 1)
1166 #\0 2) port)
1167 (display (priv:padding (date-week-number date 1)
1168 #\0 2) port))))
1169 (cons #\y (lambda (date pad-with port)
afb47f6d 1170 (display (priv:padding (priv:last-n-digits
5bbfe8cb
RB
1171 (date-year date) 2)
1172 pad-with
1173 2)
1174 port)))
1175 (cons #\Y (lambda (date pad-with port)
1176 (display (date-year date) port)))
1177 (cons #\z (lambda (date pad-with port)
1178 (priv:tz-printer (date-zone-offset date) port)))
1179 (cons #\Z (lambda (date pad-with port)
1180 (priv:locale-print-time-zone date port)))
1181 (cons #\1 (lambda (date pad-with port)
1182 (display (date->string date "~Y-~m-~d") port)))
1183 (cons #\2 (lambda (date pad-with port)
1184 (display (date->string date "~k:~M:~S~z") port)))
1185 (cons #\3 (lambda (date pad-with port)
1186 (display (date->string date "~k:~M:~S") port)))
1187 (cons #\4 (lambda (date pad-with port)
1188 (display (date->string date "~Y-~m-~dT~k:~M:~S~z") port)))
1189 (cons #\5 (lambda (date pad-with port)
1190 (display (date->string date "~Y-~m-~dT~k:~M:~S") port)))))
1191
1192
1193(define (priv:get-formatter char)
1194 (let ((associated (assoc char priv:directives)))
1195 (if associated (cdr associated) #f)))
1196
1197(define (priv:date-printer date index format-string str-len port)
1198 (if (>= index str-len)
1199 (values)
1200 (let ((current-char (string-ref format-string index)))
1201 (if (not (char=? current-char #\~))
1202 (begin
1203 (display current-char port)
1204 (priv:date-printer date (+ index 1) format-string str-len port))
1205 (if (= (+ index 1) str-len) ; bad format string.
afb47f6d 1206 (priv:time-error 'priv:date-printer 'bad-date-format-string
5bbfe8cb
RB
1207 format-string)
1208 (let ((pad-char? (string-ref format-string (+ index 1))))
1209 (cond
1210 ((char=? pad-char? #\-)
1211 (if (= (+ index 2) str-len) ; bad format string.
1212 (priv:time-error 'priv:date-printer
afb47f6d 1213 'bad-date-format-string
5bbfe8cb 1214 format-string)
afb47f6d 1215 (let ((formatter (priv:get-formatter
5bbfe8cb
RB
1216 (string-ref format-string
1217 (+ index 2)))))
1218 (if (not formatter)
1219 (priv:time-error 'priv:date-printer
afb47f6d 1220 'bad-date-format-string
5bbfe8cb
RB
1221 format-string)
1222 (begin
1223 (formatter date #f port)
1224 (priv:date-printer date
1225 (+ index 3)
1226 format-string
1227 str-len
1228 port))))))
afb47f6d 1229
5bbfe8cb
RB
1230 ((char=? pad-char? #\_)
1231 (if (= (+ index 2) str-len) ; bad format string.
1232 (priv:time-error 'priv:date-printer
afb47f6d 1233 'bad-date-format-string
5bbfe8cb 1234 format-string)
afb47f6d 1235 (let ((formatter (priv:get-formatter
5bbfe8cb
RB
1236 (string-ref format-string
1237 (+ index 2)))))
1238 (if (not formatter)
1239 (priv:time-error 'priv:date-printer
afb47f6d 1240 'bad-date-format-string
5bbfe8cb
RB
1241 format-string)
1242 (begin
1243 (formatter date #\Space port)
1244 (priv:date-printer date
1245 (+ index 3)
1246 format-string
1247 str-len
1248 port))))))
1249 (else
afb47f6d 1250 (let ((formatter (priv:get-formatter
5bbfe8cb
RB
1251 (string-ref format-string
1252 (+ index 1)))))
1253 (if (not formatter)
1254 (priv:time-error 'priv:date-printer
afb47f6d 1255 'bad-date-format-string
5bbfe8cb
RB
1256 format-string)
1257 (begin
1258 (formatter date #\0 port)
1259 (priv:date-printer date
1260 (+ index 2)
1261 format-string
1262 str-len
1263 port))))))))))))
1264
1265
1266(define (date->string date . format-string)
1267 (let ((str-port (open-output-string))
5e1fb41f 1268 (fmt-str (if (null? format-string) "~c" (car format-string))))
5bbfe8cb
RB
1269 (priv:date-printer date 0 fmt-str (string-length fmt-str) str-port)
1270 (get-output-string str-port)))
1271
1272(define (priv:char->int ch)
1273 (case ch
1274 ((#\0) 0)
1275 ((#\1) 1)
1276 ((#\2) 2)
1277 ((#\3) 3)
1278 ((#\4) 4)
1279 ((#\5) 5)
1280 ((#\6) 6)
1281 ((#\7) 7)
1282 ((#\8) 8)
1283 ((#\9) 9)
1284 (else (priv:time-error 'bad-date-template-string
1285 (list "Non-integer character" ch i)))))
1286
1287;; read an integer upto n characters long on port; upto -> #f is any length
1288(define (priv:integer-reader upto port)
1289 (let loop ((accum 0) (nchars 0))
1290 (let ((ch (peek-char port)))
1291 (if (or (eof-object? ch)
1292 (not (char-numeric? ch))
1293 (and upto (>= nchars upto)))
1294 accum
4549ba4a 1295 (loop (+ (* accum 10) (priv:char->int (read-char port)))
5bbfe8cb
RB
1296 (+ nchars 1))))))
1297
1298(define (priv:make-integer-reader upto)
1299 (lambda (port)
1300 (priv:integer-reader upto port)))
1301
1302;; read *exactly* n characters and convert to integer; could be padded
1303(define (priv:integer-reader-exact n port)
1304 (let ((padding-ok #t))
1305 (define (accum-int port accum nchars)
1306 (let ((ch (peek-char port)))
5a1920de
RB
1307 (cond
1308 ((>= nchars n) accum)
afb47f6d
TTN
1309 ((eof-object? ch)
1310 (priv:time-error 'string->date 'bad-date-template-string
5bbfe8cb 1311 "Premature ending to integer read."))
5a1920de
RB
1312 ((char-numeric? ch)
1313 (set! padding-ok #f)
1314 (accum-int port
1315 (+ (* accum 10) (priv:char->int (read-char port)))
1316 (+ nchars 1)))
1317 (padding-ok
1318 (read-char port) ; consume padding
1319 (accum-int port accum (+ nchars 1)))
1320 (else ; padding where it shouldn't be
afb47f6d 1321 (priv:time-error 'string->date 'bad-date-template-string
5bbfe8cb
RB
1322 "Non-numeric characters in integer read.")))))
1323 (accum-int port 0 0)))
1324
1325
1326(define (priv:make-integer-exact-reader n)
1327 (lambda (port)
1328 (priv:integer-reader-exact n port)))
1329
afb47f6d
TTN
1330(define (priv:zone-reader port)
1331 (let ((offset 0)
5bbfe8cb
RB
1332 (positive? #f))
1333 (let ((ch (read-char port)))
1334 (if (eof-object? ch)
1335 (priv:time-error 'string->date 'bad-date-template-string
1336 (list "Invalid time zone +/-" ch)))
1337 (if (or (char=? ch #\Z) (char=? ch #\z))
1338 0
1339 (begin
1340 (cond
1341 ((char=? ch #\+) (set! positive? #t))
1342 ((char=? ch #\-) (set! positive? #f))
1343 (else
1344 (priv:time-error 'string->date 'bad-date-template-string
1345 (list "Invalid time zone +/-" ch))))
1346 (let ((ch (read-char port)))
1347 (if (eof-object? ch)
1348 (priv:time-error 'string->date 'bad-date-template-string
1349 (list "Invalid time zone number" ch)))
1350 (set! offset (* (priv:char->int ch)
1351 10 60 60)))
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 (+ offset (* (priv:char->int ch)
1357 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 10 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 60))))
1370 (if positive? offset (- offset)))))))
1371
1372;; looking at a char, read the char string, run thru indexer, return index
1373(define (priv:locale-reader port indexer)
b4d2a48e
RB
1374
1375 (define (read-char-string result)
1376 (let ((ch (peek-char port)))
1377 (if (char-alphabetic? ch)
1378 (read-char-string (cons (read-char port) result))
1379 (list->string (reverse! result)))))
afb47f6d
TTN
1380
1381 (let* ((str (read-char-string '()))
b4d2a48e
RB
1382 (index (indexer str)))
1383 (if index index (priv:time-error 'string->date
1384 'bad-date-template-string
1385 (list "Invalid string for " indexer)))))
5bbfe8cb
RB
1386
1387(define (priv:make-locale-reader indexer)
1388 (lambda (port)
1389 (priv:locale-reader port indexer)))
1390
1391(define (priv:make-char-id-reader char)
1392 (lambda (port)
1393 (if (char=? char (read-char port))
1394 char
1395 (priv:time-error 'string->date
1396 'bad-date-template-string
1397 "Invalid character match."))))
1398
1399;; A List of formatted read directives.
1400;; Each entry is a list.
afb47f6d 1401;; 1. the character directive;
5bbfe8cb
RB
1402;; a procedure, which takes a character as input & returns
1403;; 2. #t as soon as a character on the input port is acceptable
1404;; for input,
1405;; 3. a port reader procedure that knows how to read the current port
1406;; for a value. Its one parameter is the port.
1407;; 4. a action procedure, that takes the value (from 3.) and some
1408;; object (here, always the date) and (probably) side-effects it.
1409;; In some cases (e.g., ~A) the action is to do nothing
1410
afb47f6d 1411(define priv:read-directives
5bbfe8cb
RB
1412 (let ((ireader4 (priv:make-integer-reader 4))
1413 (ireader2 (priv:make-integer-reader 2))
1414 (ireaderf (priv:make-integer-reader #f))
1415 (eireader2 (priv:make-integer-exact-reader 2))
1416 (eireader4 (priv:make-integer-exact-reader 4))
1417 (locale-reader-abbr-weekday (priv:make-locale-reader
1418 priv:locale-abbr-weekday->index))
1419 (locale-reader-long-weekday (priv:make-locale-reader
1420 priv:locale-long-weekday->index))
1421 (locale-reader-abbr-month (priv:make-locale-reader
1422 priv:locale-abbr-month->index))
1423 (locale-reader-long-month (priv:make-locale-reader
1424 priv:locale-long-month->index))
1425 (char-fail (lambda (ch) #t))
1426 (do-nothing (lambda (val object) (values))))
afb47f6d 1427
5bbfe8cb
RB
1428 (list
1429 (list #\~ char-fail (priv:make-char-id-reader #\~) do-nothing)
1430 (list #\a char-alphabetic? locale-reader-abbr-weekday do-nothing)
1431 (list #\A char-alphabetic? locale-reader-long-weekday do-nothing)
1432 (list #\b char-alphabetic? locale-reader-abbr-month
1433 (lambda (val object)
4549ba4a 1434 (set-date-month! object val)))
5bbfe8cb
RB
1435 (list #\B char-alphabetic? locale-reader-long-month
1436 (lambda (val object)
4549ba4a 1437 (set-date-month! object val)))
5bbfe8cb 1438 (list #\d char-numeric? ireader2 (lambda (val object)
4549ba4a 1439 (set-date-day!
5bbfe8cb
RB
1440 object val)))
1441 (list #\e char-fail eireader2 (lambda (val object)
4549ba4a 1442 (set-date-day! object val)))
5bbfe8cb
RB
1443 (list #\h char-alphabetic? locale-reader-abbr-month
1444 (lambda (val object)
4549ba4a 1445 (set-date-month! object val)))
5bbfe8cb 1446 (list #\H char-numeric? ireader2 (lambda (val object)
4549ba4a 1447 (set-date-hour! object val)))
5bbfe8cb 1448 (list #\k char-fail eireader2 (lambda (val object)
4549ba4a 1449 (set-date-hour! object val)))
5bbfe8cb 1450 (list #\m char-numeric? ireader2 (lambda (val object)
4549ba4a 1451 (set-date-month! object val)))
5bbfe8cb 1452 (list #\M char-numeric? ireader2 (lambda (val object)
4549ba4a 1453 (set-date-minute!
5bbfe8cb
RB
1454 object val)))
1455 (list #\S char-numeric? ireader2 (lambda (val object)
4549ba4a 1456 (set-date-second! object val)))
afb47f6d 1457 (list #\y char-fail eireader2
5bbfe8cb 1458 (lambda (val object)
4549ba4a 1459 (set-date-year! object (priv:natural-year val))))
5bbfe8cb 1460 (list #\Y char-numeric? ireader4 (lambda (val object)
4549ba4a 1461 (set-date-year! object val)))
5bbfe8cb
RB
1462 (list #\z (lambda (c)
1463 (or (char=? c #\Z)
1464 (char=? c #\z)
1465 (char=? c #\+)
1466 (char=? c #\-)))
1467 priv:zone-reader (lambda (val object)
4549ba4a 1468 (set-date-zone-offset! object val))))))
5bbfe8cb
RB
1469
1470(define (priv:string->date date index format-string str-len port template-string)
1471 (define (skip-until port skipper)
1472 (let ((ch (peek-char port)))
1473 (if (eof-object? port)
1474 (priv:time-error 'string->date 'bad-date-format-string template-string)
1475 (if (not (skipper ch))
1476 (begin (read-char port) (skip-until port skipper))))))
1477 (if (>= index str-len)
afb47f6d 1478 (begin
5bbfe8cb
RB
1479 (values))
1480 (let ((current-char (string-ref format-string index)))
1481 (if (not (char=? current-char #\~))
1482 (let ((port-char (read-char port)))
1483 (if (or (eof-object? port-char)
1484 (not (char=? current-char port-char)))
1485 (priv:time-error 'string->date
1486 'bad-date-format-string template-string))
1487 (priv:string->date date
1488 (+ index 1)
1489 format-string
1490 str-len
1491 port
1492 template-string))
1493 ;; otherwise, it's an escape, we hope
1494 (if (> (+ index 1) str-len)
1495 (priv:time-error 'string->date
1496 'bad-date-format-string template-string)
1497 (let* ((format-char (string-ref format-string (+ index 1)))
1498 (format-info (assoc format-char priv:read-directives)))
1499 (if (not format-info)
1500 (priv:time-error 'string->date
1501 'bad-date-format-string template-string)
1502 (begin
1503 (let ((skipper (cadr format-info))
1504 (reader (caddr format-info))
1505 (actor (cadddr format-info)))
1506 (skip-until port skipper)
1507 (let ((val (reader port)))
1508 (if (eof-object? val)
1509 (priv:time-error 'string->date
1510 'bad-date-format-string
1511 template-string)
1512 (actor val date)))
1513 (priv:string->date date
1514 (+ index 2)
afb47f6d 1515 format-string
5bbfe8cb
RB
1516 str-len
1517 port
1518 template-string))))))))))
1519
1520(define (string->date input-string template-string)
1521 (define (priv:date-ok? date)
1522 (and (date-nanosecond date)
1523 (date-second date)
1524 (date-minute date)
1525 (date-hour date)
1526 (date-day date)
1527 (date-month date)
1528 (date-year date)
1529 (date-zone-offset date)))
4549ba4a 1530 (let ((newdate (make-date 0 0 0 0 #f #f #f #f)))
5bbfe8cb
RB
1531 (priv:string->date newdate
1532 0
1533 template-string
1534 (string-length template-string)
1535 (open-input-string input-string)
1536 template-string)
4549ba4a
MV
1537 (if (not (date-zone-offset newdate))
1538 (begin
1539 ;; this is necessary to get DST right -- as far as we can
1540 ;; get it right (think of the double/missing hour in the
1541 ;; night when we are switching between normal time and DST).
1542 (set-date-zone-offset! newdate
afb47f6d 1543 (priv:local-tz-offset
4549ba4a
MV
1544 (make-time time-utc 0 0)))
1545 (set-date-zone-offset! newdate
afb47f6d 1546 (priv:local-tz-offset
4549ba4a 1547 (date->time-utc newdate)))))
5bbfe8cb
RB
1548 (if (priv:date-ok? newdate)
1549 newdate
1550 (priv:time-error
1551 'string->date
1552 'bad-date-format-string
1553 (list "Incomplete date read. " newdate template-string)))))
afb47f6d
TTN
1554
1555;;; srfi-19.scm ends here