~peter-pearse/ubuntu/natty/guile-1.8/prop001

« back to all changes in this revision

Viewing changes to srfi/srfi-19.scm

  • Committer: Bazaar Package Importer
  • Author(s): Daniel Schepler
  • Date: 2006-11-09 03:11:16 UTC
  • Revision ID: james.westby@ubuntu.com-20061109031116-hu0q1jxqg12y6yeg
Tags: upstream-1.8.1+1
ImportĀ upstreamĀ versionĀ 1.8.1+1

Show diffs side-by-side

added added

removed removed

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