*** empty log message ***
[bpt/guile.git] / srfi / srfi-19.scm
CommitLineData
6be07c52
TTN
1;;; srfi-19.scm --- Time/Date Library
2
3;; Copyright (C) 2001, 2002 Free Software Foundation, Inc.
4;;
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
17;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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
824(define (date-week-number date day-of-week-starting-week)
825 (quotient (- (date-year-day date)
826 (priv:days-before-first-week date day-of-week-starting-week))
827 7))
828
afb47f6d 829(define (current-date . tz-offset)
4549ba4a
MV
830 (let ((time (current-time time-utc)))
831 (time-utc->date
832 time
833 (if (null? tz-offset)
834 (priv:local-tz-offset time)
835 (car tz-offset)))))
5bbfe8cb
RB
836
837;; given a 'two digit' number, find the year within 50 years +/-
838(define (priv:natural-year n)
839 (let* ((current-year (date-year (current-date)))
840 (current-century (* (quotient current-year 100) 100)))
841 (cond
842 ((>= n 100) n)
843 ((< n 0) n)
844 ((<= (- (+ current-century n) current-year) 50) (+ current-century n))
845 (else (+ (- current-century 100) n)))))
846
847(define (date->julian-day date)
848 (let ((nanosecond (date-nanosecond date))
849 (second (date-second date))
850 (minute (date-minute date))
851 (hour (date-hour date))
852 (day (date-day date))
853 (month (date-month date))
854 (year (date-year date)))
855 (+ (priv:encode-julian-day-number day month year)
856 (- 1/2)
857 (+ (/ (+ (* hour 60 60)
858 (* minute 60)
859 second
860 (/ nanosecond priv:nano))
861 priv:sid)))))
862
863(define (date->modified-julian-day date)
864 (- (date->julian-day date)
865 4800001/2))
866
867(define (time-utc->julian-day time)
868 (if (not (eq? (time-type time) time-utc))
869 (priv:time-error 'time->date 'incompatible-time-types time))
870 (+ (/ (+ (time-second time) (/ (time-nanosecond time) priv:nano))
871 priv:sid)
872 priv:tai-epoch-in-jd))
873
874(define (time-utc->modified-julian-day time)
875 (- (time-utc->julian-day time)
876 4800001/2))
877
878(define (time-tai->julian-day time)
879 (if (not (eq? (time-type time) time-tai))
880 (priv:time-error 'time->date 'incompatible-time-types time))
afb47f6d 881 (+ (/ (+ (- (time-second time)
5bbfe8cb
RB
882 (priv:leap-second-delta (time-second time)))
883 (/ (time-nanosecond time) priv:nano))
884 priv:sid)
885 priv:tai-epoch-in-jd))
886
887(define (time-tai->modified-julian-day time)
888 (- (time-tai->julian-day time)
889 4800001/2))
890
891;; this is the same as time-tai->julian-day
892(define (time-monotonic->julian-day time)
893 (if (not (eq? (time-type time) time-monotonic))
894 (priv:time-error 'time->date 'incompatible-time-types time))
afb47f6d 895 (+ (/ (+ (- (time-second time)
5bbfe8cb
RB
896 (priv:leap-second-delta (time-second time)))
897 (/ (time-nanosecond time) priv:nano))
898 priv:sid)
899 priv:tai-epoch-in-jd))
900
901(define (time-monotonic->modified-julian-day time)
902 (- (time-monotonic->julian-day time)
903 4800001/2))
904
905(define (julian-day->time-utc jdn)
906 (let ((secs (* priv:sid (- jdn priv:tai-epoch-in-jd))))
907 (receive (seconds parts)
4549ba4a 908 (priv:split-real secs)
afb47f6d 909 (make-time time-utc
4549ba4a
MV
910 (* parts priv:nano)
911 seconds))))
5bbfe8cb
RB
912
913(define (julian-day->time-tai jdn)
914 (time-utc->time-tai! (julian-day->time-utc jdn)))
915
916(define (julian-day->time-monotonic jdn)
917 (time-utc->time-monotonic! (julian-day->time-utc jdn)))
918
919(define (julian-day->date jdn . tz-offset)
4549ba4a
MV
920 (let* ((time (julian-day->time-utc jdn))
921 (offset (if (null? tz-offset)
922 (priv:local-tz-offset time)
923 (car tz-offset))))
924 (time-utc->date time offset)))
5bbfe8cb
RB
925
926(define (modified-julian-day->date jdn . tz-offset)
4549ba4a
MV
927 (apply julian-day->date (+ jdn 4800001/2)
928 tz-offset))
5bbfe8cb
RB
929
930(define (modified-julian-day->time-utc jdn)
931 (julian-day->time-utc (+ jdn 4800001/2)))
932
933(define (modified-julian-day->time-tai jdn)
934 (julian-day->time-tai (+ jdn 4800001/2)))
935
936(define (modified-julian-day->time-monotonic jdn)
937 (julian-day->time-monotonic (+ jdn 4800001/2)))
938
939(define (current-julian-day)
940 (time-utc->julian-day (current-time time-utc)))
941
942(define (current-modified-julian-day)
943 (time-utc->modified-julian-day (current-time time-utc)))
944
945;; returns a string rep. of number N, of minimum LENGTH, padded with
946;; character PAD-WITH. If PAD-WITH is #f, no padding is done, and it's
947;; as if number->string was used. if string is longer than or equal
948;; in length to LENGTH, it's as if number->string was used.
949
950(define (priv:padding n pad-with length)
951 (let* ((str (number->string n))
952 (str-len (string-length str)))
953 (if (or (>= str-len length)
954 (not pad-with))
955 str
956 (string-append (make-string (- length str-len) pad-with) str))))
957
958(define (priv:last-n-digits i n)
959 (abs (remainder i (expt 10 n))))
960
afb47f6d 961(define (priv:locale-abbr-weekday n)
5bbfe8cb
RB
962 (vector-ref priv:locale-abbr-weekday-vector n))
963
964(define (priv:locale-long-weekday n)
965 (vector-ref priv:locale-long-weekday-vector n))
966
967(define (priv:locale-abbr-month n)
968 (vector-ref priv:locale-abbr-month-vector n))
969
970(define (priv:locale-long-month n)
971 (vector-ref priv:locale-long-month-vector n))
972
973(define (priv:vector-find needle haystack comparator)
974 (let ((len (vector-length haystack)))
975 (define (priv:vector-find-int index)
976 (cond
977 ((>= index len) #f)
978 ((comparator needle (vector-ref haystack index)) index)
979 (else (priv:vector-find-int (+ index 1)))))
980 (priv:vector-find-int 0)))
981
982(define (priv:locale-abbr-weekday->index string)
983 (priv:vector-find string priv:locale-abbr-weekday-vector string=?))
984
985(define (priv:locale-long-weekday->index string)
986 (priv:vector-find string priv:locale-long-weekday-vector string=?))
987
988(define (priv:locale-abbr-month->index string)
989 (priv:vector-find string priv:locale-abbr-month-vector string=?))
990
991(define (priv:locale-long-month->index string)
992 (priv:vector-find string priv:locale-long-month-vector string=?))
993
994
4549ba4a
MV
995;; FIXME: mkoeppe: Put a symbolic time zone in the date structs.
996;; Print it here instead of the numerical offset if available.
5bbfe8cb 997(define (priv:locale-print-time-zone date port)
4549ba4a 998 (priv:tz-printer (date-zone-offset date) port))
5bbfe8cb
RB
999
1000;; FIXME: we should use strftime to determine this dynamically if possible.
1001;; Again, locale specific.
1002(define (priv:locale-am/pm hr)
1003 (if (> hr 11) priv:locale-pm priv:locale-am))
1004
1005(define (priv:tz-printer offset port)
1006 (cond
1007 ((= offset 0) (display "Z" port))
1008 ((negative? offset) (display "-" port))
1009 (else (display "+" port)))
1010 (if (not (= offset 0))
1011 (let ((hours (abs (quotient offset (* 60 60))))
1012 (minutes (abs (quotient (remainder offset (* 60 60)) 60))))
1013 (display (priv:padding hours #\0 2) port)
1014 (display (priv:padding minutes #\0 2) port))))
1015
5bbfe8cb
RB
1016;; A table of output formatting directives.
1017;; the first time is the format char.
1018;; the second is a procedure that takes the date, a padding character
1019;; (which might be #f), and the output port.
1020;;
afb47f6d 1021(define priv:directives
5bbfe8cb
RB
1022 (list
1023 (cons #\~ (lambda (date pad-with port)
1024 (display #\~ port)))
1025 (cons #\a (lambda (date pad-with port)
1026 (display (priv:locale-abbr-weekday (date-week-day date))
1027 port)))
1028 (cons #\A (lambda (date pad-with port)
1029 (display (priv:locale-long-weekday (date-week-day date))
1030 port)))
1031 (cons #\b (lambda (date pad-with port)
1032 (display (priv:locale-abbr-month (date-month date))
1033 port)))
1034 (cons #\B (lambda (date pad-with port)
1035 (display (priv:locale-long-month (date-month date))
1036 port)))
1037 (cons #\c (lambda (date pad-with port)
1038 (display (date->string date priv:locale-date-time-format) port)))
1039 (cons #\d (lambda (date pad-with port)
1040 (display (priv:padding (date-day date)
1041 #\0 2)
1042 port)))
1043 (cons #\D (lambda (date pad-with port)
1044 (display (date->string date "~m/~d/~y") port)))
1045 (cons #\e (lambda (date pad-with port)
1046 (display (priv:padding (date-day date)
1047 #\Space 2)
1048 port)))
1049 (cons #\f (lambda (date pad-with port)
1050 (if (> (date-nanosecond date)
1051 priv:nano)
1052 (display (priv:padding (+ (date-second date) 1)
1053 pad-with 2)
1054 port)
1055 (display (priv:padding (date-second date)
1056 pad-with 2)
1057 port))
afb47f6d
TTN
1058 (receive (i f)
1059 (priv:split-real (/
5bbfe8cb
RB
1060 (date-nanosecond date)
1061 priv:nano 1.0))
1062 (let* ((ns (number->string f))
1063 (le (string-length ns)))
1064 (if (> le 2)
1065 (begin
1066 (display priv:locale-number-separator port)
1067 (display (substring ns 2 le) port)))))))
1068 (cons #\h (lambda (date pad-with port)
1069 (display (date->string date "~b") port)))
1070 (cons #\H (lambda (date pad-with port)
1071 (display (priv:padding (date-hour date)
1072 pad-with 2)
1073 port)))
1074 (cons #\I (lambda (date pad-with port)
1075 (let ((hr (date-hour date)))
1076 (if (> hr 12)
1077 (display (priv:padding (- hr 12)
1078 pad-with 2)
1079 port)
1080 (display (priv:padding hr
1081 pad-with 2)
1082 port)))))
1083 (cons #\j (lambda (date pad-with port)
1084 (display (priv:padding (date-year-day date)
1085 pad-with 3)
1086 port)))
1087 (cons #\k (lambda (date pad-with port)
1088 (display (priv:padding (date-hour date)
1089 #\Space 2)
1090 port)))
1091 (cons #\l (lambda (date pad-with port)
1092 (let ((hr (if (> (date-hour date) 12)
1093 (- (date-hour date) 12) (date-hour date))))
1094 (display (priv:padding hr #\Space 2)
1095 port))))
1096 (cons #\m (lambda (date pad-with port)
1097 (display (priv:padding (date-month date)
1098 pad-with 2)
1099 port)))
1100 (cons #\M (lambda (date pad-with port)
1101 (display (priv:padding (date-minute date)
1102 pad-with 2)
1103 port)))
1104 (cons #\n (lambda (date pad-with port)
1105 (newline port)))
1106 (cons #\N (lambda (date pad-with port)
1107 (display (priv:padding (date-nanosecond date)
1108 pad-with 7)
1109 port)))
1110 (cons #\p (lambda (date pad-with port)
1111 (display (priv:locale-am/pm (date-hour date)) port)))
1112 (cons #\r (lambda (date pad-with port)
1113 (display (date->string date "~I:~M:~S ~p") port)))
1114 (cons #\s (lambda (date pad-with port)
1115 (display (time-second (date->time-utc date)) port)))
1116 (cons #\S (lambda (date pad-with port)
1117 (if (> (date-nanosecond date)
1118 priv:nano)
1119 (display (priv:padding (+ (date-second date) 1)
1120 pad-with 2)
1121 port)
1122 (display (priv:padding (date-second date)
1123 pad-with 2)
1124 port))))
1125 (cons #\t (lambda (date pad-with port)
1126 (display #\Tab port)))
1127 (cons #\T (lambda (date pad-with port)
1128 (display (date->string date "~H:~M:~S") port)))
1129 (cons #\U (lambda (date pad-with port)
1130 (if (> (priv:days-before-first-week date 0) 0)
1131 (display (priv:padding (+ (date-week-number date 0) 1)
1132 #\0 2) port)
1133 (display (priv:padding (date-week-number date 0)
1134 #\0 2) port))))
1135 (cons #\V (lambda (date pad-with port)
1136 (display (priv:padding (date-week-number date 1)
1137 #\0 2) port)))
1138 (cons #\w (lambda (date pad-with port)
1139 (display (date-week-day date) port)))
1140 (cons #\x (lambda (date pad-with port)
1141 (display (date->string date priv:locale-short-date-format) port)))
1142 (cons #\X (lambda (date pad-with port)
1143 (display (date->string date priv:locale-time-format) port)))
1144 (cons #\W (lambda (date pad-with port)
1145 (if (> (priv:days-before-first-week date 1) 0)
1146 (display (priv:padding (+ (date-week-number date 1) 1)
1147 #\0 2) port)
1148 (display (priv:padding (date-week-number date 1)
1149 #\0 2) port))))
1150 (cons #\y (lambda (date pad-with port)
afb47f6d 1151 (display (priv:padding (priv:last-n-digits
5bbfe8cb
RB
1152 (date-year date) 2)
1153 pad-with
1154 2)
1155 port)))
1156 (cons #\Y (lambda (date pad-with port)
1157 (display (date-year date) port)))
1158 (cons #\z (lambda (date pad-with port)
1159 (priv:tz-printer (date-zone-offset date) port)))
1160 (cons #\Z (lambda (date pad-with port)
1161 (priv:locale-print-time-zone date port)))
1162 (cons #\1 (lambda (date pad-with port)
1163 (display (date->string date "~Y-~m-~d") port)))
1164 (cons #\2 (lambda (date pad-with port)
1165 (display (date->string date "~k:~M:~S~z") port)))
1166 (cons #\3 (lambda (date pad-with port)
1167 (display (date->string date "~k:~M:~S") port)))
1168 (cons #\4 (lambda (date pad-with port)
1169 (display (date->string date "~Y-~m-~dT~k:~M:~S~z") port)))
1170 (cons #\5 (lambda (date pad-with port)
1171 (display (date->string date "~Y-~m-~dT~k:~M:~S") port)))))
1172
1173
1174(define (priv:get-formatter char)
1175 (let ((associated (assoc char priv:directives)))
1176 (if associated (cdr associated) #f)))
1177
1178(define (priv:date-printer date index format-string str-len port)
1179 (if (>= index str-len)
1180 (values)
1181 (let ((current-char (string-ref format-string index)))
1182 (if (not (char=? current-char #\~))
1183 (begin
1184 (display current-char port)
1185 (priv:date-printer date (+ index 1) format-string str-len port))
1186 (if (= (+ index 1) str-len) ; bad format string.
afb47f6d 1187 (priv:time-error 'priv:date-printer 'bad-date-format-string
5bbfe8cb
RB
1188 format-string)
1189 (let ((pad-char? (string-ref format-string (+ index 1))))
1190 (cond
1191 ((char=? pad-char? #\-)
1192 (if (= (+ index 2) str-len) ; bad format string.
1193 (priv:time-error 'priv:date-printer
afb47f6d 1194 'bad-date-format-string
5bbfe8cb 1195 format-string)
afb47f6d 1196 (let ((formatter (priv:get-formatter
5bbfe8cb
RB
1197 (string-ref format-string
1198 (+ index 2)))))
1199 (if (not formatter)
1200 (priv:time-error 'priv:date-printer
afb47f6d 1201 'bad-date-format-string
5bbfe8cb
RB
1202 format-string)
1203 (begin
1204 (formatter date #f port)
1205 (priv:date-printer date
1206 (+ index 3)
1207 format-string
1208 str-len
1209 port))))))
afb47f6d 1210
5bbfe8cb
RB
1211 ((char=? pad-char? #\_)
1212 (if (= (+ index 2) str-len) ; bad format string.
1213 (priv:time-error 'priv:date-printer
afb47f6d 1214 'bad-date-format-string
5bbfe8cb 1215 format-string)
afb47f6d 1216 (let ((formatter (priv:get-formatter
5bbfe8cb
RB
1217 (string-ref format-string
1218 (+ index 2)))))
1219 (if (not formatter)
1220 (priv:time-error 'priv:date-printer
afb47f6d 1221 'bad-date-format-string
5bbfe8cb
RB
1222 format-string)
1223 (begin
1224 (formatter date #\Space port)
1225 (priv:date-printer date
1226 (+ index 3)
1227 format-string
1228 str-len
1229 port))))))
1230 (else
afb47f6d 1231 (let ((formatter (priv:get-formatter
5bbfe8cb
RB
1232 (string-ref format-string
1233 (+ index 1)))))
1234 (if (not formatter)
1235 (priv:time-error 'priv:date-printer
afb47f6d 1236 'bad-date-format-string
5bbfe8cb
RB
1237 format-string)
1238 (begin
1239 (formatter date #\0 port)
1240 (priv:date-printer date
1241 (+ index 2)
1242 format-string
1243 str-len
1244 port))))))))))))
1245
1246
1247(define (date->string date . format-string)
1248 (let ((str-port (open-output-string))
5e1fb41f 1249 (fmt-str (if (null? format-string) "~c" (car format-string))))
5bbfe8cb
RB
1250 (priv:date-printer date 0 fmt-str (string-length fmt-str) str-port)
1251 (get-output-string str-port)))
1252
1253(define (priv:char->int ch)
1254 (case ch
1255 ((#\0) 0)
1256 ((#\1) 1)
1257 ((#\2) 2)
1258 ((#\3) 3)
1259 ((#\4) 4)
1260 ((#\5) 5)
1261 ((#\6) 6)
1262 ((#\7) 7)
1263 ((#\8) 8)
1264 ((#\9) 9)
1265 (else (priv:time-error 'bad-date-template-string
1266 (list "Non-integer character" ch i)))))
1267
1268;; read an integer upto n characters long on port; upto -> #f is any length
1269(define (priv:integer-reader upto port)
1270 (let loop ((accum 0) (nchars 0))
1271 (let ((ch (peek-char port)))
1272 (if (or (eof-object? ch)
1273 (not (char-numeric? ch))
1274 (and upto (>= nchars upto)))
1275 accum
4549ba4a 1276 (loop (+ (* accum 10) (priv:char->int (read-char port)))
5bbfe8cb
RB
1277 (+ nchars 1))))))
1278
1279(define (priv:make-integer-reader upto)
1280 (lambda (port)
1281 (priv:integer-reader upto port)))
1282
1283;; read *exactly* n characters and convert to integer; could be padded
1284(define (priv:integer-reader-exact n port)
1285 (let ((padding-ok #t))
1286 (define (accum-int port accum nchars)
1287 (let ((ch (peek-char port)))
5a1920de
RB
1288 (cond
1289 ((>= nchars n) accum)
afb47f6d
TTN
1290 ((eof-object? ch)
1291 (priv:time-error 'string->date 'bad-date-template-string
5bbfe8cb 1292 "Premature ending to integer read."))
5a1920de
RB
1293 ((char-numeric? ch)
1294 (set! padding-ok #f)
1295 (accum-int port
1296 (+ (* accum 10) (priv:char->int (read-char port)))
1297 (+ nchars 1)))
1298 (padding-ok
1299 (read-char port) ; consume padding
1300 (accum-int port accum (+ nchars 1)))
1301 (else ; padding where it shouldn't be
afb47f6d 1302 (priv:time-error 'string->date 'bad-date-template-string
5bbfe8cb
RB
1303 "Non-numeric characters in integer read.")))))
1304 (accum-int port 0 0)))
1305
1306
1307(define (priv:make-integer-exact-reader n)
1308 (lambda (port)
1309 (priv:integer-reader-exact n port)))
1310
afb47f6d
TTN
1311(define (priv:zone-reader port)
1312 (let ((offset 0)
5bbfe8cb
RB
1313 (positive? #f))
1314 (let ((ch (read-char port)))
1315 (if (eof-object? ch)
1316 (priv:time-error 'string->date 'bad-date-template-string
1317 (list "Invalid time zone +/-" ch)))
1318 (if (or (char=? ch #\Z) (char=? ch #\z))
1319 0
1320 (begin
1321 (cond
1322 ((char=? ch #\+) (set! positive? #t))
1323 ((char=? ch #\-) (set! positive? #f))
1324 (else
1325 (priv:time-error 'string->date 'bad-date-template-string
1326 (list "Invalid time zone +/-" ch))))
1327 (let ((ch (read-char port)))
1328 (if (eof-object? ch)
1329 (priv:time-error 'string->date 'bad-date-template-string
1330 (list "Invalid time zone number" ch)))
1331 (set! offset (* (priv:char->int ch)
1332 10 60 60)))
1333 (let ((ch (read-char port)))
1334 (if (eof-object? ch)
1335 (priv:time-error 'string->date 'bad-date-template-string
1336 (list "Invalid time zone number" ch)))
1337 (set! offset (+ offset (* (priv:char->int ch)
1338 60 60))))
1339 (let ((ch (read-char port)))
1340 (if (eof-object? ch)
1341 (priv:time-error 'string->date 'bad-date-template-string
1342 (list "Invalid time zone number" ch)))
1343 (set! offset (+ offset (* (priv:char->int ch)
1344 10 60))))
1345 (let ((ch (read-char port)))
1346 (if (eof-object? ch)
1347 (priv:time-error 'string->date 'bad-date-template-string
1348 (list "Invalid time zone number" ch)))
1349 (set! offset (+ offset (* (priv:char->int ch)
1350 60))))
1351 (if positive? offset (- offset)))))))
1352
1353;; looking at a char, read the char string, run thru indexer, return index
1354(define (priv:locale-reader port indexer)
b4d2a48e
RB
1355
1356 (define (read-char-string result)
1357 (let ((ch (peek-char port)))
1358 (if (char-alphabetic? ch)
1359 (read-char-string (cons (read-char port) result))
1360 (list->string (reverse! result)))))
afb47f6d
TTN
1361
1362 (let* ((str (read-char-string '()))
b4d2a48e
RB
1363 (index (indexer str)))
1364 (if index index (priv:time-error 'string->date
1365 'bad-date-template-string
1366 (list "Invalid string for " indexer)))))
5bbfe8cb
RB
1367
1368(define (priv:make-locale-reader indexer)
1369 (lambda (port)
1370 (priv:locale-reader port indexer)))
1371
1372(define (priv:make-char-id-reader char)
1373 (lambda (port)
1374 (if (char=? char (read-char port))
1375 char
1376 (priv:time-error 'string->date
1377 'bad-date-template-string
1378 "Invalid character match."))))
1379
1380;; A List of formatted read directives.
1381;; Each entry is a list.
afb47f6d 1382;; 1. the character directive;
5bbfe8cb
RB
1383;; a procedure, which takes a character as input & returns
1384;; 2. #t as soon as a character on the input port is acceptable
1385;; for input,
1386;; 3. a port reader procedure that knows how to read the current port
1387;; for a value. Its one parameter is the port.
1388;; 4. a action procedure, that takes the value (from 3.) and some
1389;; object (here, always the date) and (probably) side-effects it.
1390;; In some cases (e.g., ~A) the action is to do nothing
1391
afb47f6d 1392(define priv:read-directives
5bbfe8cb
RB
1393 (let ((ireader4 (priv:make-integer-reader 4))
1394 (ireader2 (priv:make-integer-reader 2))
1395 (ireaderf (priv:make-integer-reader #f))
1396 (eireader2 (priv:make-integer-exact-reader 2))
1397 (eireader4 (priv:make-integer-exact-reader 4))
1398 (locale-reader-abbr-weekday (priv:make-locale-reader
1399 priv:locale-abbr-weekday->index))
1400 (locale-reader-long-weekday (priv:make-locale-reader
1401 priv:locale-long-weekday->index))
1402 (locale-reader-abbr-month (priv:make-locale-reader
1403 priv:locale-abbr-month->index))
1404 (locale-reader-long-month (priv:make-locale-reader
1405 priv:locale-long-month->index))
1406 (char-fail (lambda (ch) #t))
1407 (do-nothing (lambda (val object) (values))))
afb47f6d 1408
5bbfe8cb
RB
1409 (list
1410 (list #\~ char-fail (priv:make-char-id-reader #\~) do-nothing)
1411 (list #\a char-alphabetic? locale-reader-abbr-weekday do-nothing)
1412 (list #\A char-alphabetic? locale-reader-long-weekday do-nothing)
1413 (list #\b char-alphabetic? locale-reader-abbr-month
1414 (lambda (val object)
4549ba4a 1415 (set-date-month! object val)))
5bbfe8cb
RB
1416 (list #\B char-alphabetic? locale-reader-long-month
1417 (lambda (val object)
4549ba4a 1418 (set-date-month! object val)))
5bbfe8cb 1419 (list #\d char-numeric? ireader2 (lambda (val object)
4549ba4a 1420 (set-date-day!
5bbfe8cb
RB
1421 object val)))
1422 (list #\e char-fail eireader2 (lambda (val object)
4549ba4a 1423 (set-date-day! object val)))
5bbfe8cb
RB
1424 (list #\h char-alphabetic? locale-reader-abbr-month
1425 (lambda (val object)
4549ba4a 1426 (set-date-month! object val)))
5bbfe8cb 1427 (list #\H char-numeric? ireader2 (lambda (val object)
4549ba4a 1428 (set-date-hour! object val)))
5bbfe8cb 1429 (list #\k char-fail eireader2 (lambda (val object)
4549ba4a 1430 (set-date-hour! object val)))
5bbfe8cb 1431 (list #\m char-numeric? ireader2 (lambda (val object)
4549ba4a 1432 (set-date-month! object val)))
5bbfe8cb 1433 (list #\M char-numeric? ireader2 (lambda (val object)
4549ba4a 1434 (set-date-minute!
5bbfe8cb
RB
1435 object val)))
1436 (list #\S char-numeric? ireader2 (lambda (val object)
4549ba4a 1437 (set-date-second! object val)))
afb47f6d 1438 (list #\y char-fail eireader2
5bbfe8cb 1439 (lambda (val object)
4549ba4a 1440 (set-date-year! object (priv:natural-year val))))
5bbfe8cb 1441 (list #\Y char-numeric? ireader4 (lambda (val object)
4549ba4a 1442 (set-date-year! object val)))
5bbfe8cb
RB
1443 (list #\z (lambda (c)
1444 (or (char=? c #\Z)
1445 (char=? c #\z)
1446 (char=? c #\+)
1447 (char=? c #\-)))
1448 priv:zone-reader (lambda (val object)
4549ba4a 1449 (set-date-zone-offset! object val))))))
5bbfe8cb
RB
1450
1451(define (priv:string->date date index format-string str-len port template-string)
1452 (define (skip-until port skipper)
1453 (let ((ch (peek-char port)))
1454 (if (eof-object? port)
1455 (priv:time-error 'string->date 'bad-date-format-string template-string)
1456 (if (not (skipper ch))
1457 (begin (read-char port) (skip-until port skipper))))))
1458 (if (>= index str-len)
afb47f6d 1459 (begin
5bbfe8cb
RB
1460 (values))
1461 (let ((current-char (string-ref format-string index)))
1462 (if (not (char=? current-char #\~))
1463 (let ((port-char (read-char port)))
1464 (if (or (eof-object? port-char)
1465 (not (char=? current-char port-char)))
1466 (priv:time-error 'string->date
1467 'bad-date-format-string template-string))
1468 (priv:string->date date
1469 (+ index 1)
1470 format-string
1471 str-len
1472 port
1473 template-string))
1474 ;; otherwise, it's an escape, we hope
1475 (if (> (+ index 1) str-len)
1476 (priv:time-error 'string->date
1477 'bad-date-format-string template-string)
1478 (let* ((format-char (string-ref format-string (+ index 1)))
1479 (format-info (assoc format-char priv:read-directives)))
1480 (if (not format-info)
1481 (priv:time-error 'string->date
1482 'bad-date-format-string template-string)
1483 (begin
1484 (let ((skipper (cadr format-info))
1485 (reader (caddr format-info))
1486 (actor (cadddr format-info)))
1487 (skip-until port skipper)
1488 (let ((val (reader port)))
1489 (if (eof-object? val)
1490 (priv:time-error 'string->date
1491 'bad-date-format-string
1492 template-string)
1493 (actor val date)))
1494 (priv:string->date date
1495 (+ index 2)
afb47f6d 1496 format-string
5bbfe8cb
RB
1497 str-len
1498 port
1499 template-string))))))))))
1500
1501(define (string->date input-string template-string)
1502 (define (priv:date-ok? date)
1503 (and (date-nanosecond date)
1504 (date-second date)
1505 (date-minute date)
1506 (date-hour date)
1507 (date-day date)
1508 (date-month date)
1509 (date-year date)
1510 (date-zone-offset date)))
4549ba4a 1511 (let ((newdate (make-date 0 0 0 0 #f #f #f #f)))
5bbfe8cb
RB
1512 (priv:string->date newdate
1513 0
1514 template-string
1515 (string-length template-string)
1516 (open-input-string input-string)
1517 template-string)
4549ba4a
MV
1518 (if (not (date-zone-offset newdate))
1519 (begin
1520 ;; this is necessary to get DST right -- as far as we can
1521 ;; get it right (think of the double/missing hour in the
1522 ;; night when we are switching between normal time and DST).
1523 (set-date-zone-offset! newdate
afb47f6d 1524 (priv:local-tz-offset
4549ba4a
MV
1525 (make-time time-utc 0 0)))
1526 (set-date-zone-offset! newdate
afb47f6d 1527 (priv:local-tz-offset
4549ba4a 1528 (date->time-utc newdate)))))
5bbfe8cb
RB
1529 (if (priv:date-ok? newdate)
1530 newdate
1531 (priv:time-error
1532 'string->date
1533 'bad-date-format-string
1534 (list "Incomplete date read. " newdate template-string)))))
afb47f6d
TTN
1535
1536;;; srfi-19.scm ends here