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