r8970: time patch/tests from Marcus Pearce
[clsql.git] / base / time.lisp
1 ;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
2 ;;;; *************************************************************************
3 ;;;;
4 ;;;; $Id$
5 ;;;;
6 ;;;; A variety of structures and function for creating and
7 ;;;; manipulating dates, times, durations and intervals for
8 ;;;; CLSQL.
9 ;;;;
10 ;;;; This file was originally part of ODCL and is Copyright (c) 2002 -
11 ;;;; 2003 onShore Development, Inc.
12 ;;;;
13 ;;;; CLSQL users are granted the rights to distribute and use this software
14 ;;;; as governed by the terms of the Lisp Lesser GNU Public License
15 ;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL.
16 ;;;; *************************************************************************
17
18 (in-package #:clsql-base-sys)
19
20 ;; ------------------------------------------------------------
21 ;; Months
22
23 (defvar *month-keywords*
24   '(:january :february :march :april :may :june :july :august :september
25     :october :november :december))
26
27 (defvar *month-names*
28   '("" "January" "February" "March" "April" "May" "June" "July" "August"
29     "September" "October" "November" "December"))
30
31 (defun month-name (month-index)
32   (nth month-index *month-names*))
33
34 (defun ordinal-month (month-keyword)
35   "Return the zero-based month number for the given MONTH keyword."
36   (position month-keyword *month-keywords*))
37
38
39 ;; ------------------------------------------------------------
40 ;; Days
41
42 (defvar *day-keywords*
43   '(:sunday :monday :tuesday :wednesday :thursday :friday :saturday))
44
45 (defvar *day-names*
46   '("Sunday" "Monday" "Tuesday" "Wednesday" "Thursday" "Friday" "Saturday"))
47
48 (defun day-name (day-index)
49   (nth day-index *day-names*))
50
51 (defun ordinal-day (day-keyword)
52   "Return the zero-based day number for the given DAY keyword."
53   (position day-keyword *day-keywords*))
54
55
56 ;; ------------------------------------------------------------
57 ;; time classes: wall-time, duration
58
59 (eval-when (:compile-toplevel :load-toplevel)
60
61 (defstruct (wall-time (:conc-name time-)
62                       (:constructor %make-wall-time)
63                       (:print-function %print-wall-time))
64   (mjd 0 :type fixnum)
65   (second 0 :type fixnum))
66
67 (defun %print-wall-time (time stream depth)
68   (declare (ignore depth))
69   (format stream "#<WALL-TIME: ~a>" (format-time nil time)))
70
71 (defstruct (duration (:constructor %make-duration)
72                      (:print-function %print-duration))
73   (year 0 :type fixnum)
74   (month 0 :type fixnum)
75   (day 0 :type fixnum)
76   (hour 0 :type fixnum)
77   (second 0 :type fixnum)
78   (minute 0 :type fixnum))
79
80 (defun %print-duration (duration stream depth)
81   (declare (ignore depth))
82   (format stream "#<DURATION: ~a>"
83           (format-duration nil duration :precision :second)))
84
85 );eval-when
86
87 (defun duration-timestring (duration)
88   (let ((second (duration-second duration))
89         (minute (duration-minute duration))
90         (hour (duration-hour duration))
91         (day (duration-day duration)))
92     (format nil "P~dD~dH~dM~dS" day hour minute second)))
93
94
95 ;; ------------------------------------------------------------
96 ;; Constructors
97
98 (defun make-time (&key (year 0) (month 1) (day 1) (hour 0) (minute 0)
99                        (second 0) (offset 0))
100   (let ((mjd (gregorian-to-mjd month day year))
101         (sec (+ (* hour 60 60)
102                 (* minute 60)
103                 second (- offset))))
104     (multiple-value-bind (day-add raw-sec)
105         (floor sec (* 60 60 24))
106       (%make-wall-time :mjd (+ mjd day-add) :second raw-sec))))
107
108 (defun copy-time (time)
109   (%make-wall-time :mjd (time-mjd time)
110                    :second (time-second time)))
111
112 (defun get-time ()
113   "Return a pair: (GREGORIAN DAY . TIME-OF-DAY)"
114   (multiple-value-bind (second minute hour day mon year)
115       (decode-universal-time (get-universal-time))
116     (make-time :year year :month mon :day day :hour hour :minute minute
117                :second second)))
118
119 (defun make-duration (&key (year 0) (month 0) (day 0) (hour 0) (minute 0)
120                            (second 0))
121   (multiple-value-bind (minute-add second-60)
122       (floor second 60)
123     (multiple-value-bind (hour-add minute-60)
124         (floor (+ minute minute-add) 60)
125       (multiple-value-bind (day-add hour-24)
126           (floor (+ hour hour-add) 24)
127         (%make-duration :year year :month month :day (+ day day-add)
128                         :hour hour-24
129                         :minute minute-60
130                         :second second-60)))))
131
132
133 ;; ------------------------------------------------------------
134 ;; Accessors
135
136 (defun time-hms (time)
137   (multiple-value-bind (hourminute second)
138       (floor (time-second time) 60)
139     (multiple-value-bind (hour minute)
140         (floor hourminute 60)
141       (values hour minute second))))
142
143 (defun time-ymd (time)
144   (destructuring-bind (month day year)
145       (mjd-to-gregorian (time-mjd time))
146     (values year month day)))
147
148 (defun time-dow (time)
149   "Return the 0 indexed Day of the week starting with Sunday"
150   (mod (+ 3 (time-mjd time)) 7))
151
152 (defun decode-time (time)
153   "returns the decoded time as multiple values: second, minute, hour, day,
154 month, year, integer day-of-week"
155   (multiple-value-bind (year month day)
156       (time-ymd time)
157     (multiple-value-bind (hour minute second)
158         (time-hms time)
159       (values second minute hour day month year (mod (+ (time-mjd time) 3) 7)))))
160
161 ;; duration specific
162 (defun duration-reduce (duration precision &optional round)
163      (:second
164       (+ (duration-second duration)
165          (* (duration-reduce duration :minute) 60)))
166      (:minute
167       (+ (if round
168              (floor (duration-second duration) 30)
169              0)
170          (duration-minute duration)
171          (* (duration-reduce duration :hour) 60)))
172      (:hour
173       (+ (if round
174              (floor (duration-minute duration) 30)
175              0)
176          (duration-hour duration)
177          (* (duration-reduce duration :day) 24)))
178      (:day
179       (+ (if round
180              (floor (duration-hour duration) 12)
181              0)
182          (duration-day duration))))
183
184
185 ;; ------------------------------------------------------------
186 ;; Arithemetic and comparators
187
188 (defun duration= (duration-a duration-b)
189   (= (duration-reduce duration-a :second)
190      (duration-reduce duration-b :second)))
191
192 (defun duration< (duration-a duration-b)
193   (< (duration-reduce duration-a :second)
194      (duration-reduce duration-b :second)))
195
196 (defun duration<= (duration-a duration-b)
197   (<= (duration-reduce duration-a :second)
198      (duration-reduce duration-b :second)))
199                                                               
200 (defun duration>= (x y)
201   (duration<= y x))
202
203 (defun duration> (x y)
204   (duration< y x))
205
206 (defun %time< (x y)
207   (let ((mjd-x (time-mjd x))
208         (mjd-y (time-mjd y)))
209     (if (/= mjd-x mjd-y)
210         (< mjd-x mjd-y)
211         (< (time-second x) (time-second y)))))
212   
213 (defun %time>= (x y)
214   (if (/= (time-mjd x) (time-mjd y))
215       (>= (time-mjd x) (time-mjd y))
216       (>= (time-second x) (time-second y))))
217
218 (defun %time<= (x y)
219   (if (/= (time-mjd x) (time-mjd y))
220       (<= (time-mjd x) (time-mjd y))
221       (<= (time-second x) (time-second y))))
222
223 (defun %time> (x y)
224   (if (/= (time-mjd x) (time-mjd y))
225       (> (time-mjd x) (time-mjd y))
226       (> (time-second x) (time-second y))))
227
228 (defun %time= (x y)
229   (and (= (time-mjd x) (time-mjd y))
230        (= (time-second x) (time-second y))))
231
232 (defun time= (number &rest more-numbers)
233   "Returns T if all of its arguments are numerically equal, NIL otherwise."
234   (do ((nlist more-numbers (cdr nlist)))
235       ((atom nlist) t)
236      (declare (list nlist))
237      (if (not (%time= (car nlist) number)) (return nil))))
238
239 (defun time/= (number &rest more-numbers)
240   "Returns T if no two of its arguments are numerically equal, NIL otherwise."
241   (do* ((head number (car nlist))
242         (nlist more-numbers (cdr nlist)))
243        ((atom nlist) t)
244      (declare (list nlist))
245      (unless (do* ((nl nlist (cdr nl)))
246                   ((atom nl) t)
247                (declare (list nl))
248                (if (%time= head (car nl)) (return nil)))
249        (return nil))))
250
251 (defun time< (number &rest more-numbers)
252   "Returns T if its arguments are in strictly increasing order, NIL otherwise."
253   (do* ((n number (car nlist))
254         (nlist more-numbers (cdr nlist)))
255        ((atom nlist) t)
256      (declare (list nlist))
257      (if (not (%time< n (car nlist))) (return nil))))
258
259 (defun time> (number &rest more-numbers)
260   "Returns T if its arguments are in strictly decreasing order, NIL otherwise."
261   (do* ((n number (car nlist))
262         (nlist more-numbers (cdr nlist)))
263        ((atom nlist) t)
264      (declare (list nlist))
265      (if (not (%time> n (car nlist))) (return nil))))
266
267 (defun time<= (number &rest more-numbers)
268   "Returns T if arguments are in strictly non-decreasing order, NIL otherwise."
269   (do* ((n number (car nlist))
270         (nlist more-numbers (cdr nlist)))
271        ((atom nlist) t)
272      (declare (list nlist))
273      (if (not (%time<= n (car nlist))) (return nil))))
274
275 (defun time>= (number &rest more-numbers)
276   "Returns T if arguments are in strictly non-increasing order, NIL otherwise."
277   (do* ((n number (car nlist))
278         (nlist more-numbers (cdr nlist)))
279        ((atom nlist) t)
280      (declare (list nlist))
281      (if (not (%time>= n (car nlist))) (return nil))))
282
283 (defun time-max (number &rest more-numbers)
284   "Returns the greatest of its arguments."
285   (do ((nlist more-numbers (cdr nlist))
286        (result number))
287       ((null nlist) (return result))
288      (declare (list nlist))
289      (if (%time> (car nlist) result) (setf result (car nlist)))))
290
291 (defun time-min (number &rest more-numbers)
292   "Returns the least of its arguments."
293   (do ((nlist more-numbers (cdr nlist))
294        (result number))
295       ((null nlist) (return result))
296      (declare (list nlist))
297      (if (%time< (car nlist) result) (setf result (car nlist)))))
298
299 (defun time-compare (time-a time-b)
300   (let ((mjd-a (time-mjd time-a))
301         (mjd-b (time-mjd time-b))
302         (sec-a (time-second time-a))
303         (sec-b (time-second time-b)))
304     (if (= mjd-a mjd-b)
305         (if (= sec-a sec-b)
306             :equal
307             (if (< sec-a sec-b)
308                 :less-than
309                 :greater-than))
310         (if (< mjd-a mjd-b)
311             :less-than
312             :greater-than))))
313
314
315 ;; ------------------------------------------------------------
316 ;; Formatting and output
317
318 (defvar +decimal-printer+ #(#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9))
319
320 (defun db-timestring (time)
321   "return the string to store the given time in the database"
322   (declare (optimize (speed 3)))
323   (let ((output (copy-seq "'XXXX-XX-XX XX:XX:XX'")))
324     (flet ((inscribe-base-10 (output offset size decimal)
325              (declare (type fixnum offset size decimal)
326                       (type (simple-vector 10) +decimal-printer+))
327              (dotimes (x size)
328                (declare (type fixnum x)
329                         (optimize (safety 0)))
330                (multiple-value-bind (next this)
331                    (floor decimal 10)
332                  (setf (aref output (+ (- size x 1) offset))
333                        (aref +decimal-printer+ this))
334                  (setf decimal next)))))
335       (multiple-value-bind (second minute hour day month year)
336           (decode-time time)
337         (inscribe-base-10 output 1 4 year)
338         (inscribe-base-10 output 6 2 month)
339         (inscribe-base-10 output 9 2 day)
340         (inscribe-base-10 output 12 2 hour)
341         (inscribe-base-10 output 15 2 minute)
342         (inscribe-base-10 output 18 2 second)
343         output))))
344
345 (defun iso-timestring (time)
346   "return the string to store the given time in the database"
347   (declare (optimize (speed 3)))
348   (let ((output (copy-seq "XXXX-XX-XX XX:XX:XX")))
349     (flet ((inscribe-base-10 (output offset size decimal)
350              (declare (type fixnum offset size decimal)
351                       (type (simple-vector 10) +decimal-printer+))
352              (dotimes (x size)
353                (declare (type fixnum x)
354                         (optimize (safety 0)))
355                (multiple-value-bind (next this)
356                    (floor decimal 10)
357                  (setf (aref output (+ (- size x 1) offset))
358                        (aref +decimal-printer+ this))
359                  (setf decimal next)))))
360       (multiple-value-bind (second minute hour day month year)
361           (decode-time time)
362         (inscribe-base-10 output 0 4 year)
363         (inscribe-base-10 output 5 2 month)
364         (inscribe-base-10 output 8 2 day)
365         (inscribe-base-10 output 11 2 hour)
366         (inscribe-base-10 output 14 2 minute)
367         (inscribe-base-10 output 17 2 second)
368         output))))
369
370
371 ;; ------------------------------------------------------------
372 ;; Intervals
373
374 (defstruct interval
375   (start nil)
376   (end nil)
377   (name nil) 
378   (contained nil)
379   (type nil)
380   (data nil))
381
382 ;; fix : should also return :contains / :contained
383
384 (defun interval-relation (x y)
385   "Compare the relationship of node x to node y. Returns either
386 :contained :contains :follows :overlaps or :precedes."
387   (let ((xst  (interval-start x))
388         (xend (interval-end x))
389         (yst  (interval-start y))
390         (yend (interval-end y)))
391     (case (time-compare xst yst)
392       (:equal
393        (case (time-compare xend yend)
394          (:less-than
395           :contained)
396          ((:equal :greater-than)
397           :contains)))
398       (:greater-than
399        (case (time-compare xst yend)
400          ((:equal :greater-than)
401           :follows)
402          (:less-than
403           (case (time-compare xend yend)
404             ((:less-than :equal)
405              :contained)
406             ((:greater-than)
407              :overlaps)))))
408       (:less-than
409        (case (time-compare xend yst)
410          ((:equal :less-than)
411           :precedes)
412          (:greater-than
413           (case (time-compare xend yend)
414             (:less-than
415              :overlaps)
416             ((:equal :greater-than)
417              :contains))))))))
418
419 ;; ------------------------------------------------------------
420 ;; interval lists
421
422 (defun sort-interval-list (list)
423   (sort list (lambda (x y)
424                (case (interval-relation x y)
425                  ((:precedes :contains) t)
426                  ((:follows :overlaps :contained) nil)))))
427
428 ;; interval push will return its list of intervals in strict order.
429 (defun interval-push (interval-list interval &optional container-rule)
430   (declare (ignore container-rule))
431   (let ((sorted-list (sort-interval-list interval-list)))
432     (dotimes (x (length sorted-list))
433       (let ((elt (nth x sorted-list)))
434         (case (interval-relation elt interval)
435           (:follows
436            (return-from interval-push (insert-at-index x sorted-list interval)))
437           (:contains
438            (return-from interval-push
439              (replace-at-index x sorted-list
440                                (make-interval :start (interval-start elt)
441                                               :end (interval-end elt)
442                                               :type (interval-type elt)
443                                               :contained (interval-push (interval-contained elt) interval)
444                                               :data (interval-data elt)))))
445           ((:overlaps :contained)
446            (error "Overlap")))))
447     (append sorted-list (list interval))))
448
449 ;; interval lists
450                   
451 (defun interval-match (list time)
452   "Return the index of the first interval in list containing time"
453   ;; this depends on ordering of intervals!
454   (let ((list (sort-interval-list list))) 
455     (dotimes (x (length list))
456       (let ((elt (nth x list)))
457         (when (and (time<= (interval-start elt) time)
458                    (time< time (interval-end elt)))
459           (return-from interval-match x))))))
460   
461 (defun interval-clear (list time)
462   (dotimes (x (length list))
463     (let ((elt (nth x list)))
464       (when (and (time<= (interval-start elt) time)
465                  (time< time (interval-end elt)))
466         (if (interval-match (interval-contained elt) time)
467             (return-from interval-clear
468               (replace-at-index x list
469                                 (make-interval :start (interval-start elt)
470                                                :end (interval-end elt)
471                                                :type (interval-type elt)
472                                                :contained (interval-clear (interval-contained elt) time)
473                                                :data (interval-data elt))))
474             (return-from interval-clear
475               (delete-at-index x list)))))))
476
477 (defun interval-edit (list time start end &optional tag)
478   "Attempts to modify the most deeply nested interval in list which
479 begins at time.  If no changes are made, returns nil."
480   ;; function required sorted interval list
481   (let ((list (sort-interval-list list))) 
482     (if (null list) nil
483       (dotimes (x (length list))
484         (let ((elt (nth x list)))
485           (when (and (time<= (interval-start elt) time)
486                      (time< time (interval-end elt)))
487             (or (interval-edit (interval-contained elt) time start end tag)
488                 (cond ((and (< 0 x)
489                             (time< start (interval-end (nth (1- x) list))))
490                        (error "Overlap of previous interval"))
491                       ((and (< x (1- (length list)))
492                             (time< (interval-start (nth (1+ x) list)) end))
493                        (error "~S ~S ~S ~S Overlap of next interval" x (length list) (interval-start (nth (1+ x) list)) end ))
494                       ((time= (interval-start elt) time)
495                        (return-from interval-edit
496                          (replace-at-index x list
497                                            (make-interval :start start
498                                                           :end end
499                                                           :type (interval-type elt)
500                                                           :contained (restrict-intervals (interval-contained elt) start end)
501                                                           :data (or tag (interval-data elt))))))))))))))
502
503 (defun restrict-intervals (list start end &aux newlist)
504   (let ((test-interval (make-interval :start start :end end)))
505     (dolist (elt list)
506       (when (equal :contained
507                    (interval-relation elt test-interval))
508         (push elt newlist)))
509     (nreverse newlist)))
510
511 ;;; utils from odcl/list.lisp
512
513 (defun replace-at-index (idx list elt)
514   (cond ((= idx 0)
515          (cons elt (cdr list)))
516         ((= idx (1- (length list)))
517          (append (butlast list) (list elt)))
518         (t
519          (append (subseq list 0 idx)
520                  (list elt)
521                  (subseq list (1+ idx))))))
522
523 (defun insert-at-index (idx list elt)
524   (cond ((= idx 0)
525          (cons elt list))
526         ((= idx (1- (length list)))
527          (append list (list elt)))
528         (t
529          (append (subseq list 0 idx)
530                  (list elt)
531                  (subseq list idx)))))
532
533 (defun delete-at-index (idx list)
534   (cond ((= idx 0)
535          (cdr list))
536         ((= idx (1- (length list)))
537          (butlast list))
538         (t
539          (append (subseq list 0 idx)
540                  (subseq list (1+ idx))))))
541
542
543 ;; ------------------------------------------------------------
544 ;; return MJD for Gregorian date
545
546 (defun gregorian-to-mjd (month day year)
547   (let ((b 0)
548         (month-adj month)
549         (year-adj (if (< year 0)
550                       (+ year 1)
551                       year))
552         d
553         c)
554     (when (< month 3)
555       (incf month-adj 12)
556       (decf year-adj))
557     (unless (or (< year 1582)
558                 (and (= year 1582)
559                      (or (< month 10)
560                          (and (= month 10)
561                               (< day 15)))))
562       (let ((a (floor (/ year-adj 100))))
563         (setf b (+ (- 2 a) (floor (/ a 4))))))
564     (if (< year-adj 0)
565         (setf c (floor (- (* 365.25d0 year-adj) 679006.75d0)))
566         (setf c (floor (- (* 365.25d0 year-adj) 679006d0))))
567     (setf d (floor (* 30.6001 (+ 1 month-adj))))
568     ;; (cmsg "b ~s c ~s d ~s day ~s" b c d day)
569     (+ b c d day)))
570
571 ;; convert MJD to Gregorian date
572
573 (defun mjd-to-gregorian (mjd)
574   (let (z r g a b c year month day)
575     (setf z (floor (+ mjd 678882)))
576     (setf r (- (+ mjd 678882) z))
577     (setf g (- z .25))
578     (setf a (floor (/ g 36524.25)))
579     (setf b (- a (floor (/ a 4))))
580     (setf year (floor (/ (+ b g) 365.25)))
581     (setf c (- (+ b z) (floor (* 365.25 year))))
582     (setf month (truncate (/ (+ (* 5 c) 456) 153)))
583     (setf day (+ (- c (truncate (/ (- (* 153 month) 457) 5))) r))
584     (when (> month 12)
585       (incf year)
586       (decf month 12))
587     (list month day year)))
588
589 (defun duration+ (time &rest durations)
590   "Add each DURATION to TIME, returning a new wall-time value."
591   (let ((year   (duration-year time))
592         (month  (duration-month time))
593         (day    (duration-day time))
594         (hour   (duration-hour time))
595         (minute (duration-minute time))
596         (second (duration-second time)))
597     (dolist (duration durations)
598       (incf year    (duration-year duration))
599       (incf month   (duration-month duration))
600       (incf day     (duration-day duration))
601       (incf hour    (duration-hour duration))
602       (incf minute  (duration-minute duration))
603       (incf second  (duration-second duration)))
604     (make-duration :year year :month month :day day :hour hour :minute minute
605                    :second second)))
606
607 (defun duration- (duration &rest durations)
608     "Subtract each DURATION from TIME, returning a new duration value."
609   (let ((year   (duration-year duration))
610         (month  (duration-month duration))
611         (day    (duration-day duration))
612         (hour   (duration-hour duration))
613         (minute (duration-minute duration))
614         (second (duration-second duration)))
615     (dolist (duration durations)
616       (decf year    (duration-year duration))
617       (decf month   (duration-month duration))
618       (decf day     (duration-day duration))
619       (decf hour    (duration-hour duration))
620       (decf minute  (duration-minute duration))
621       (decf second  (duration-second duration)))
622     (make-duration :year year :month month :day day :hour hour :minute minute
623                    :second second)))
624
625 ;; Date + Duration
626
627 (defun time+ (time &rest durations)
628   "Add each DURATION to TIME, returning a new wall-time value."
629   (let ((new-time (copy-time time)))
630     (dolist (duration durations)
631       (roll new-time
632             :year (duration-year duration)
633             :month (duration-month duration)
634             :day (duration-day duration)
635             :hour (duration-hour duration)
636             :minute (duration-minute duration)
637             :second (duration-second duration)
638             :destructive t))
639     new-time))
640
641 (defun time- (time &rest durations)
642   "Subtract each DURATION from TIME, returning a new wall-time value."
643   (let ((new-time (copy-time time)))
644     (dolist (duration durations)
645       (roll new-time
646             :year (- (duration-year duration))
647             :month (- (duration-month duration))
648             :day (- (duration-day duration))
649             :hour (- (duration-hour duration))
650             :minute (- (duration-minute duration))
651             :second (- (duration-second duration))
652             :destructive t))
653     new-time))
654
655 (defun time-difference (time1 time2)
656   "Returns a DURATION representing the difference between TIME1 and
657 TIME2."
658   (flet ((do-diff (time1 time2)
659            
660   (let (day-diff sec-diff)
661     (setf day-diff (- (time-mjd time2)
662                       (time-mjd time1)))
663     (if (> day-diff 0)
664         (progn (decf day-diff)
665                (setf sec-diff (+ (time-second time2)
666                                  (- (* 60 60 24)
667                                     (time-second time1)))))
668       (setf sec-diff (- (time-second time2)
669                         (time-second time1))))
670      (make-duration :day day-diff
671                    :second sec-diff))))
672     (if (time< time1 time2)
673         (do-diff time1 time2)
674       (do-diff time2 time1))))
675
676 (defun format-time (stream time &key format
677                     (date-separator "-")
678                     (time-separator ":")
679                     (internal-separator " "))
680   "produces on stream the timestring corresponding to the wall-time
681 with the given options"
682   (multiple-value-bind (second minute hour day month year dow)
683       (decode-time time)
684     (case format
685       (:pretty
686        (format stream "~A ~A, ~A ~D, ~D"
687                (pretty-time hour minute)
688                (day-name dow)
689                (month-name month)
690                day
691                year))
692       (:short-pretty
693        (format stream "~A, ~D/~D/~D"
694                (pretty-time hour minute)
695                month day year))
696       (:iso
697        (let ((string (iso-timestring time)))
698          (if stream
699              (write-string string stream)
700              string)))
701       (t
702        (format stream "~2,'0D~A~2,'0D~A~2,'0D~A~2,'0D~A~2,'0D~A~2,'0D"
703                year date-separator month date-separator day
704                internal-separator hour time-separator minute time-separator
705                second)))))
706
707 (defun pretty-time (hour minute)
708   (cond
709    ((eq hour 0)
710     (format nil "12:~2,'0D AM" minute))
711    ((eq hour 12)
712     (format nil "12:~2,'0D PM" minute))
713    ((< hour 12)
714     (format nil "~D:~2,'0D AM" hour minute))
715    ((and (> hour 12) (< hour 24))
716     (format nil "~D:~2,'0D PM" (- hour 12) minute))
717    (t
718     (error "pretty-time got bad hour"))))
719
720 (defun leap-days-in-days (days)
721   ;; return the number of leap days between Mar 1 2000 and
722   ;; (Mar 1 2000) + days, where days can be negative
723   (if (< days 0)
724       (ceiling (/ (- days) (* 365 4)))
725       (floor (/ days (* 365 4)))))
726
727 (defun current-year ()
728   (third (mjd-to-gregorian (time-mjd (get-time)))))
729
730 (defun current-month ()
731   (second (mjd-to-gregorian (time-mjd (get-time)))))
732
733 (defun current-day ()
734   (first (mjd-to-gregorian (time-mjd (get-time)))))
735
736 (defun parse-date-time (string)
737   "parses date like 08/08/01, 8.8.2001, eg"
738   (when (> (length string) 1)
739     (let ((m (current-month))
740           (d (current-day))
741           (y (current-year)))
742       (let ((integers (mapcar #'parse-integer (hork-integers string))))
743         (case (length integers)
744           (1
745            (setf y (car integers)))
746           (2
747            (setf m (car integers))
748            (setf y (cadr integers)))
749           (3
750            (setf m (car integers))
751            (setf d (cadr integers))
752            (setf y (caddr integers)))
753           (t
754            (return-from parse-date-time))))
755       (when (< y 100)
756         (incf y 2000))
757       (make-time :year y :month m :day d))))
758
759 (defun hork-integers (input)
760   (let ((output '())
761         (start 0))
762     (dotimes (x (length input))
763       (unless (<= 48 (char-code (aref input x)) 57)
764         (push (subseq input start x) output)
765         (setf start (1+ x))))
766     (nreverse (push (subseq input start) output))))
767     
768 (defun merged-time (day time-of-day)
769   (%make-wall-time :mjd (time-mjd day)
770                    :second (time-second time-of-day)))
771
772 (defun time-meridian (hours)
773   (cond ((= hours 0)
774          (values 12 "AM"))
775         ((= hours 12)
776          (values 12 "PM"))
777         ((< 12 hours)
778          (values (- hours 12) "PM"))
779         (t
780          (values hours "AM"))))
781
782 (defmethod to-string ((time wall-time) &rest keys)
783   (destructuring-bind (&key (style :daytime) &allow-other-keys)
784       keys
785     (print-date time style)))
786
787 (defun print-date (time &optional (style :daytime))
788   (multiple-value-bind (second minute hour day month year dow)
789       (decode-time time)
790     (declare (ignore second))
791     (multiple-value-bind (hours meridian)
792         (time-meridian hour)
793       (ecase style
794         (:time-of-day
795          ;; 2:00 PM
796          (format nil "~d:~2,'0d ~a" hours minute meridian))
797         (:long-day
798          ;; October 11th, 2000
799          (format nil "~a ~d, ~d" (month-name month) day year))
800         (:month
801          ;; October
802          (month-name month))
803         (:month-year
804          ;; October 2000
805          (format nil "~a ~d" (month-name month) year))
806         (:full
807          ;; 11:08 AM, November 22, 2002
808          (format nil "~d:~2,'0d ~a, ~a ~d, ~d"
809                  hours minute meridian (month-name month) day year))
810         (:full+weekday
811          ;; 11:09 AM Friday, November 22, 2002
812          (format nil "~d:~2,'0d ~a ~a, ~a ~d, ~d"
813                  hours minute meridian (nth dow *day-names*)
814                  (month-name month) day year))
815         (:daytime
816          ;; 11:09 AM, 11/22/2002
817          (format-time nil time :format :short-pretty))
818         (:day
819          ;; 11/22/2002
820          (format nil "~d/~d/~d" month day year))))))
821
822 (defun time-element (time element)
823   (multiple-value-bind (second minute hour day month year dow)
824       (decode-time time)
825     (ecase element
826       (:seconds
827        second)
828       (:minutes
829        minute)
830       (:hours
831        hour)
832       (:day-of-month
833        day)
834       (:integer-day-of-week
835        dow)
836       (:day-of-week
837        (nth dow *day-keywords*))
838       (:month
839        month)
840       (:year
841        year))))
842
843 (defun format-duration (stream duration &key (precision :minute))
844   (let ((second (duration-second duration))
845         (minute (duration-minute duration))
846         (hour (duration-hour duration))
847         (day (duration-day duration))
848         (return (null stream))
849         (stream (or stream (make-string-output-stream))))
850     (ecase precision
851       (:day
852        (setf hour 0 second 0 minute 0))
853       (:hour
854        (setf second 0 minute 0))
855       (:minute
856        (setf second 0))
857       (:second
858        t))
859     (if (= 0 day hour minute)
860         (format stream "0 minutes")
861         (let ((sent? nil))
862           (when (< 0 day)
863             (format stream "~d day~p" day day)
864             (setf sent? t))
865           (when (< 0 hour)
866             (when sent?
867               (write-char #\Space stream))
868             (format stream "~d hour~p" hour hour)
869             (setf sent? t))
870           (when (< 0 minute)
871             (when sent?
872               (write-char #\Space stream))
873             (format stream "~d min~p" minute minute)
874             (setf sent? t))
875           (when (< 0 second)
876             (when sent?
877               (write-char #\Space stream))
878             (format stream "~d sec~p" second second))))
879     (when return
880       (get-output-stream-string stream))))
881
882 (defgeneric midnight (self))
883 (defmethod midnight ((self wall-time))
884   "truncate hours, minutes and seconds"
885   (%make-wall-time :mjd (time-mjd self)))
886
887 (defun roll (date &key (year 0) (month 0) (day 0) (second 0) (hour 0)
888                   (minute 0) (destructive nil))
889   (unless (= 0 year month)
890     (multiple-value-bind (year-orig month-orig day-orig)
891         (time-ymd date)
892       (setf date (make-time :year (+ year year-orig)
893                             :month (+ month month-orig)
894                             :day day-orig
895                             :second (time-second date)))))
896   (let ((mjd (time-mjd date))
897         (sec (time-second date)))
898     (multiple-value-bind (mjd-new sec-new)
899         (floor (+ sec second
900                   (* 60 minute)
901                   (* 60 60 hour)) (* 60 60 24))
902       (if destructive
903           (progn
904             (setf (time-mjd date) (+ mjd mjd-new day)
905                   (time-second date) sec-new)
906             date)
907           (%make-wall-time :mjd (+ mjd mjd-new day)
908                            :second sec-new)))))
909
910 (defun roll-to (date size position)
911   (ecase size
912     (:month
913      (ecase position
914        (:beginning
915         (roll date :day (+ 1
916                            (- (time-element date :day-of-month)))))
917        (:end
918         (roll date :day (+ (days-in-month (time-element date :month)
919                                           (time-element date :year))
920                            (- (time-element date :day-of-month)))))))))
921
922 (defun week-containing (time)
923   (let* ((midn (midnight time))
924          (dow (time-element midn :integer-day-of-week)))
925     (list (roll midn :day (- dow))
926           (roll midn :day (- 7 dow)))))
927
928 (defun leap-year? (year)
929   "t if YEAR is a leap yeap in the Gregorian calendar"
930   (and (= 0 (mod year 4))
931        (or (not (= 0 (mod year 100)))
932            (= 0 (mod year 400)))))
933
934 (defun valid-month-p (month)
935   "t if MONTH exists in the Gregorian calendar"
936   (<= 1 month 12))
937
938 (defun valid-gregorian-date-p (date)
939   "t if DATE (year month day) exists in the Gregorian calendar"
940   (let ((max-day (days-in-month (nth 1 date) (nth 0 date))))
941     (<= 1 (nth 2 date) max-day)))
942
943 (defun days-in-month (month year &key (careful t))
944   "the number of days in MONTH of YEAR, observing Gregorian leap year
945 rules"
946   (declare (type fixnum month year))
947   (when careful
948     (check-type month (satisfies valid-month-p)
949                 "between 1 (January) and 12 (December)"))
950   (if (eql month 2)                     ; feb
951       (if (leap-year? year)
952           29 28)
953       (let ((even (mod (1- month) 2)))
954         (if (< month 8)                 ; aug
955             (- 31 even)
956             (+ 30 even)))))
957
958 (defun day-of-year (year month day &key (careful t))
959   "the day number within the year of the date DATE.  For example,
960 1987 1 1 returns 1"
961   (declare (type fixnum year month day))
962   (when careful
963     (let ((date (list year month day)))
964     (check-type date (satisfies valid-gregorian-date-p)
965                 "a valid Gregorian date")))
966   (let ((doy (+ day (* 31 (1- month)))))
967     (declare (type fixnum doy))
968     (when (< 2 month)
969       (setf doy (- doy (floor (+ 23 (* 4 month)) 10)))
970       (when (leap-year? year)
971         (incf doy)))
972     doy))
973
974 (defun parse-yearstring (string)
975   (let ((year (or (parse-integer-insensitively string) 
976                   (extract-roman string))))
977     (when (and year (< 1500 year 2500))
978       (make-time :year year))))
979
980 (defvar *roman-digits*
981   '((#\M . 1000)
982     (#\D . 500)
983     (#\C . 100)
984     (#\L . 50)
985     (#\X . 10)
986     (#\V . 5)
987     (#\I . 1)))
988
989 (defun extract-roman (string &aux parse)
990   (dotimes (x (length string))
991     (when-bind (val (get-alist (aref string x) *roman-digits*))
992       (when (and parse (< (car parse) val))
993         (push (- (pop parse)) parse))
994       (push val parse)))
995   (apply #'+ parse))
996
997
998 ;; ------------------------------------------------------------
999 ;; Parsing iso-8601 timestrings 
1000
1001 (define-condition iso-8601-syntax-error (error)
1002   ((bad-component;; year, month whatever
1003     :initarg :bad-component
1004     :reader bad-component)))
1005
1006 (defun parse-timestring (timestring &key (start 0) end junk-allowed)
1007   "parse a timestring and return the corresponding wall-time.  If the
1008 timestring starts with P, read a duration; otherwise read an ISO 8601
1009 formatted date string."
1010   (declare (ignore junk-allowed))  ;; FIXME
1011   (let ((string (subseq timestring start end)))
1012     (if (char= (aref string 0) #\P)
1013         (parse-iso-8601-duration string)
1014         (parse-iso-8601-time string))))
1015
1016 (defvar *iso-8601-duration-delimiters*
1017   '((#\D . :days)
1018     (#\H . :hours)
1019     (#\M . :minutes)
1020     (#\S . :seconds)))
1021
1022 (defun iso-8601-delimiter (elt)
1023   (cdr (assoc elt *iso-8601-duration-delimiters*)))
1024
1025 (defun iso-8601-duration-subseq (string start)
1026   (let* ((pos (position-if #'iso-8601-delimiter string :start start))
1027          (number (when pos (parse-integer (subseq string start pos)
1028                                           :junk-allowed t))))
1029     (when number
1030       (values number
1031               (1+ pos)
1032               (iso-8601-delimiter (aref string pos))))))
1033
1034 (defun parse-iso-8601-duration (string)
1035   "return a wall-time from a duration string"
1036   (block parse
1037     (let ((days 0) (secs 0) (hours 0) (minutes 0) (index 1))
1038       (loop
1039        (multiple-value-bind (duration next-index duration-type)
1040            (iso-8601-duration-subseq string index)
1041          (case duration-type
1042            (:hours
1043             (incf hours duration))
1044            (:minutes
1045             (incf minutes duration))
1046            (:seconds
1047             (incf secs duration))
1048            (:days
1049             (incf days duration))
1050            (t
1051             (return-from parse (make-duration :day days :hour hours
1052                                               :minute minutes :second secs))))
1053          (setf index next-index))))))
1054
1055 ;; e.g. 2000-11-11 00:00:00-06
1056
1057 (defun parse-iso-8601-time (string)
1058   "return the wall-time corresponding to the given ISO 8601 datestring"
1059   (multiple-value-bind (year month day hour minute second offset)
1060       (syntax-parse-iso-8601 string)
1061     (make-time :year year
1062                :month month
1063                :day day
1064                :hour hour
1065                :minute minute
1066                :second second
1067                :offset offset)))
1068
1069
1070 (defun syntax-parse-iso-8601 (string)
1071   (let (year month day hour minute second gmt-sec-offset)
1072     (handler-case
1073         (progn
1074           (setf year   (parse-integer (subseq string 0 4))
1075                 month  (parse-integer (subseq string 5 7))
1076                 day    (parse-integer (subseq string 8 10))
1077                 hour   (if (<= 13 (length string))
1078                            (parse-integer (subseq string 11 13))
1079                            0)
1080                 minute (if (<= 16 (length string))
1081                            (parse-integer (subseq string 14 16))
1082                            0)
1083                 second (if (<= 19 (length string))
1084                            (parse-integer (subseq string 17 19))
1085                            0)
1086                 gmt-sec-offset (if (<= 22 (length string))
1087                                    (* 60 60
1088                                       (parse-integer (subseq string 19 22)))
1089                                    0))
1090           (unless (< 0 year)
1091             (error 'iso-8601-syntax-error
1092                    :bad-component '(year . 0)))
1093           (unless (< 0 month)
1094             (error 'iso-8601-syntax-error
1095                    :bad-component '(month . 0)))
1096           (unless (< 0 day)
1097             (error 'iso-8601-syntax-error
1098                    :bad-component '(month . 0)))
1099           (values year month day hour minute second gmt-sec-offset))
1100       (simple-error ()
1101         (error 'iso-8601-syntax-error
1102                :bad-component
1103                (car (find-if (lambda (pair) (null (cdr pair)))
1104                              `((year . ,year) (month . ,month)
1105                                (day . ,day) (hour ,hour)
1106                                (minute ,minute) (second ,second)
1107                                (timezone ,gmt-sec-offset)))))))))