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