Use :verbose nil for asdf:operate invocation
[clsql/s11.git] / sql / time.lisp
blob7024fe31c13e4c61010e54f8acec7482cc493ad2
1 ;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
2 ;;;; *************************************************************************
3 ;;;;
4 ;;;; A variety of structures and function for creating and
5 ;;;; manipulating dates, times, durations and intervals for
6 ;;;; CLSQL.
7 ;;;;
8 ;;;; This file was originally part of ODCL and is Copyright (c) 2002 -
9 ;;;; 2003 onShore Development, Inc.
10 ;;;;
11 ;;;; CLSQL users are granted the rights to distribute and use this software
12 ;;;; as governed by the terms of the Lisp Lesser GNU Public License
13 ;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL.
14 ;;;; *************************************************************************
16 (in-package #:clsql-sys)
18 ;; ------------------------------------------------------------
19 ;; Months
21 (defvar *month-keywords*
22 '(:january :february :march :april :may :june :july :august :september
23 :october :november :december))
25 (defvar *month-names*
26 '("" "January" "February" "March" "April" "May" "June" "July" "August"
27 "September" "October" "November" "December"))
29 (defun month-name (month-index)
30 (nth month-index *month-names*))
32 (defun ordinal-month (month-keyword)
33 "Return the zero-based month number for the given MONTH keyword."
34 (position month-keyword *month-keywords*))
37 ;; ------------------------------------------------------------
38 ;; Days
40 (defvar *day-keywords*
41 '(:sunday :monday :tuesday :wednesday :thursday :friday :saturday))
43 (defvar *day-names*
44 '("Sunday" "Monday" "Tuesday" "Wednesday" "Thursday" "Friday" "Saturday"))
46 (defun day-name (day-index)
47 (nth day-index *day-names*))
49 (defun ordinal-day (day-keyword)
50 "Return the zero-based day number for the given DAY keyword."
51 (position day-keyword *day-keywords*))
54 ;; ------------------------------------------------------------
55 ;; time classes: wall-time, duration
57 (eval-when (:compile-toplevel :load-toplevel)
59 (defstruct (wall-time (:conc-name time-)
60 (:constructor %make-wall-time)
61 (:print-function %print-wall-time))
62 (mjd 0 :type fixnum)
63 (second 0 :type fixnum)
64 (usec 0 :type fixnum))
66 (defun %print-wall-time (time stream depth)
67 (declare (ignore depth))
68 (if *print-escape*
69 (format stream "#<WALL-TIME: ~a>" (format-time nil time))
70 (format-time stream time :format :pretty)))
72 (defstruct (duration (:constructor %make-duration)
73 (:print-function %print-duration))
74 (year 0 :type fixnum)
75 (month 0 :type fixnum)
76 (day 0 :type fixnum)
77 (hour 0 :type fixnum)
78 (second 0 :type fixnum)
79 (minute 0 :type fixnum)
80 (usec 0 :type fixnum))
82 (defun %print-duration (duration stream depth)
83 (declare (ignore depth))
84 (if *print-escape*
85 (format stream "#<DURATION: ~a>"
86 (format-duration nil duration :precision :second))
87 (format-duration stream duration :precision :second)))
89 (defstruct (date (:constructor %make-date)
90 (:print-function %print-date))
91 (mjd 0 :type fixnum))
93 (defun %print-date (date stream depth)
94 (declare (ignore depth))
95 (if *print-escape*
96 (format stream "#<DATE: ~a>" (format-date nil date))
97 (format-date stream date :format :pretty)))
99 );eval-when
101 (defun duration-timestring (duration)
102 (let ((second (duration-second duration))
103 (minute (duration-minute duration))
104 (hour (duration-hour duration))
105 (day (duration-day duration))
106 (month (duration-month duration))
107 (year (duration-year duration)))
108 (format nil "P~dY~dM~dD~dH~dM~dS" year month day hour minute second)))
111 ;; ------------------------------------------------------------
112 ;; Constructors
114 (defun make-time (&key (year 0) (month 1) (day 1) (hour 0) (minute 0)
115 (second 0) (usec 0) (offset 0))
116 (let ((mjd (gregorian-to-mjd month day year))
117 (sec (+ (* hour 60 60)
118 (* minute 60)
119 second (- offset))))
120 (multiple-value-bind (day-add raw-sec)
121 (floor sec (* 60 60 24))
122 (%make-wall-time :mjd (+ mjd day-add) :second raw-sec :usec usec))))
124 (defun make-date (&key (year 0) (month 1) (day 1) (hour 0) (minute 0)
125 (second 0) (usec 0) (offset 0))
126 (time->date (make-time :year year :month month :day day :hour hour
127 :minute minute :second second :usec usec :offset offset)))
129 (defun copy-time (time)
130 (%make-wall-time :mjd (time-mjd time)
131 :second (time-second time)))
133 (defun utime->time (utime)
134 "Return a pair: (GREGORIAN DAY . TIME-OF-DAY)"
135 (multiple-value-bind (second minute hour day mon year)
136 (decode-universal-time utime)
137 (make-time :year year :month mon :day day :hour hour :minute minute
138 :second second)))
140 (defun date->time (date)
141 "Returns a walltime for the given date"
142 (%make-wall-time :mjd (date-mjd date)))
144 (defun time->date (time)
145 "Returns a date for the given wall time (obvious loss in resolution)"
146 (%make-date :mjd (time-mjd time)))
148 (defun get-time ()
149 "Return a pair: (GREGORIAN DAY . TIME-OF-DAY)"
150 (utime->time (get-universal-time)))
152 (defun get-date ()
153 "Returns a date for today"
154 (time->date (get-time)))
156 (defun make-duration (&key (year 0) (month 0) (day 0) (hour 0) (minute 0)
157 (second 0) (usec 0))
158 (multiple-value-bind (second-add usec-1000000)
159 (floor usec 1000000)
160 (multiple-value-bind (minute-add second-60)
161 (floor (+ second second-add) 60)
162 (multiple-value-bind (hour-add minute-60)
163 (floor (+ minute minute-add) 60)
164 (multiple-value-bind (day-add hour-24)
165 (floor (+ hour hour-add) 24)
166 (%make-duration :year year :month month :day (+ day day-add)
167 :hour hour-24
168 :minute minute-60
169 :second second-60
170 :usec usec-1000000))))))
173 ;; ------------------------------------------------------------
174 ;; Accessors
176 (defun time-hms (time)
177 (multiple-value-bind (hourminute second)
178 (floor (time-second time) 60)
179 (multiple-value-bind (hour minute)
180 (floor hourminute 60)
181 (values hour minute second))))
183 (defun time-ymd (time)
184 (destructuring-bind (month day year)
185 (mjd-to-gregorian (time-mjd time))
186 (values year month day)))
188 (defun time-dow (time)
189 "Return the 0 indexed Day of the week starting with Sunday"
190 (mod (+ 3 (time-mjd time)) 7))
192 (defun decode-time (time)
193 "returns the decoded time as multiple values: usec, second, minute, hour,
194 day, month, year, integer day-of-week"
195 (multiple-value-bind (year month day)
196 (time-ymd time)
197 (multiple-value-bind (hour minute second)
198 (time-hms time)
199 (values (time-usec time) second minute hour day month year (mod (+ (time-mjd time) 3) 7)))))
201 (defun date-ymd (date)
202 (time-ymd (date->time date)))
204 (defun date-dow (date)
205 (time-dow (date->time date)))
207 (defun decode-date (date)
208 "returns the decoded date as multiple values: day month year integer day-of-week"
209 (multiple-value-bind (year month day)
210 (time-ymd (date->time date))
211 (values day month year (date-dow date))))
213 ;; duration specific
214 (defun duration-reduce (duration precision &optional round)
215 (ecase precision
216 (:usec
217 (+ (duration-usec duration)
218 (* (duration-reduce duration :second) 1000000)))
219 (:second
220 (+ (if round
221 (floor (duration-usec duration) 500000)
223 (duration-second duration)
224 (* (duration-reduce duration :minute) 60)))
225 (:minute
226 (+ (if round
227 (floor (duration-second duration) 30)
229 (duration-minute duration)
230 (* (duration-reduce duration :hour) 60)))
231 (:hour
232 (+ (if round
233 (floor (duration-minute duration) 30)
235 (duration-hour duration)
236 (* (duration-reduce duration :day) 24)))
237 (:day
238 (+ (if round
239 (floor (duration-hour duration) 12)
241 (duration-day duration)))))
244 ;; ------------------------------------------------------------
245 ;; Arithemetic and comparators
247 (defun duration= (duration-a duration-b)
248 (= (duration-reduce duration-a :usec)
249 (duration-reduce duration-b :usec)))
251 (defun duration< (duration-a duration-b)
252 (< (duration-reduce duration-a :usec)
253 (duration-reduce duration-b :usec)))
255 (defun duration<= (duration-a duration-b)
256 (<= (duration-reduce duration-a :usec)
257 (duration-reduce duration-b :usec)))
259 (defun duration>= (x y)
260 (duration<= y x))
262 (defun duration> (x y)
263 (duration< y x))
265 (defun %time< (x y)
266 (let ((mjd-x (time-mjd x))
267 (mjd-y (time-mjd y)))
268 (if (/= mjd-x mjd-y)
269 (< mjd-x mjd-y)
270 (if (/= (time-second x) (time-second y))
271 (< (time-second x) (time-second y))
272 (< (time-usec x) (time-usec y))))))
274 (defun %time>= (x y)
275 (if (/= (time-mjd x) (time-mjd y))
276 (>= (time-mjd x) (time-mjd y))
277 (if (/= (time-second x) (time-second y))
278 (>= (time-second x) (time-second y))
279 (>= (time-usec x) (time-usec y)))))
281 (defun %time<= (x y)
282 (if (/= (time-mjd x) (time-mjd y))
283 (<= (time-mjd x) (time-mjd y))
284 (if (/= (time-second x) (time-second y))
285 (<= (time-second x) (time-second y))
286 (<= (time-usec x) (time-usec y)))))
288 (defun %time> (x y)
289 (if (/= (time-mjd x) (time-mjd y))
290 (> (time-mjd x) (time-mjd y))
291 (if (/= (time-second x) (time-second y))
292 (> (time-second x) (time-second y))
293 (> (time-usec x) (time-usec y)))))
295 (defun %time= (x y)
296 (and (= (time-mjd x) (time-mjd y))
297 (= (time-second x) (time-second y))
298 (= (time-usec x) (time-usec y))))
300 (defun time= (number &rest more-numbers)
301 "Returns T if all of its arguments are numerically equal, NIL otherwise."
302 (do ((nlist more-numbers (cdr nlist)))
303 ((atom nlist) t)
304 (declare (list nlist))
305 (if (not (%time= (car nlist) number)) (return nil))))
307 (defun time/= (number &rest more-numbers)
308 "Returns T if no two of its arguments are numerically equal, NIL otherwise."
309 (do* ((head number (car nlist))
310 (nlist more-numbers (cdr nlist)))
311 ((atom nlist) t)
312 (declare (list nlist))
313 (unless (do* ((nl nlist (cdr nl)))
314 ((atom nl) t)
315 (declare (list nl))
316 (if (%time= head (car nl)) (return nil)))
317 (return nil))))
319 (defun time< (number &rest more-numbers)
320 "Returns T if its arguments are in strictly increasing order, NIL otherwise."
321 (do* ((n number (car nlist))
322 (nlist more-numbers (cdr nlist)))
323 ((atom nlist) t)
324 (declare (list nlist))
325 (if (not (%time< n (car nlist))) (return nil))))
327 (defun time> (number &rest more-numbers)
328 "Returns T if its arguments are in strictly decreasing order, NIL otherwise."
329 (do* ((n number (car nlist))
330 (nlist more-numbers (cdr nlist)))
331 ((atom nlist) t)
332 (declare (list nlist))
333 (if (not (%time> n (car nlist))) (return nil))))
335 (defun time<= (number &rest more-numbers)
336 "Returns T if arguments are in strictly non-decreasing order, NIL otherwise."
337 (do* ((n number (car nlist))
338 (nlist more-numbers (cdr nlist)))
339 ((atom nlist) t)
340 (declare (list nlist))
341 (if (not (%time<= n (car nlist))) (return nil))))
343 (defun time>= (number &rest more-numbers)
344 "Returns T if arguments are in strictly non-increasing order, NIL otherwise."
345 (do* ((n number (car nlist))
346 (nlist more-numbers (cdr nlist)))
347 ((atom nlist) t)
348 (declare (list nlist))
349 (if (not (%time>= n (car nlist))) (return nil))))
351 (defun time-max (number &rest more-numbers)
352 "Returns the greatest of its arguments."
353 (do ((nlist more-numbers (cdr nlist))
354 (result number))
355 ((null nlist) (return result))
356 (declare (list nlist))
357 (if (%time> (car nlist) result) (setf result (car nlist)))))
359 (defun time-min (number &rest more-numbers)
360 "Returns the least of its arguments."
361 (do ((nlist more-numbers (cdr nlist))
362 (result number))
363 ((null nlist) (return result))
364 (declare (list nlist))
365 (if (%time< (car nlist) result) (setf result (car nlist)))))
367 (defun time-compare (time-a time-b)
368 (let ((mjd-a (time-mjd time-a))
369 (mjd-b (time-mjd time-b))
370 (sec-a (time-second time-a))
371 (sec-b (time-second time-b))
372 (usec-a (time-usec time-a))
373 (usec-b (time-usec time-b)))
374 (if (= mjd-a mjd-b)
375 (if (= sec-a sec-b)
376 (if (= usec-a usec-b)
377 :equal
378 (if (< usec-a usec-b)
379 :less-than
380 :greater-than))
381 (if (< sec-a sec-b)
382 :less-than
383 :greater-than))
384 (if (< mjd-a mjd-b)
385 :less-than
386 :greater-than))))
388 ; now the same for dates
389 (eval-when (:compile-toplevel :load-toplevel)
390 (defun replace-string (string1 search-string replace-string &key (test #'string=))
391 "Search within string1 for search-string, replace with replace-string, non-destructively."
392 (let ((replace-string-length (length replace-string))
393 (search-string-length (length search-string)))
394 (labels ((sub-replace-string (current-string position)
395 (let ((found-position (search search-string current-string :test test :start2 position)))
396 (if (null found-position)
397 current-string
398 (sub-replace-string (concatenate 'string
399 (subseq current-string 0 found-position)
400 replace-string
401 (subseq current-string (+ found-position search-string-length)))
402 (+ position replace-string-length))))))
403 (sub-replace-string string1 0))))
404 );eval-when
406 (defmacro wrap-time-for-date (time-func &key (result-func))
407 (let ((date-func (intern (replace-string (symbol-name time-func)
408 (symbol-name-default-case "TIME")
409 (symbol-name-default-case "DATE")))))
410 `(defun ,date-func (number &rest more-numbers)
411 (let ((result (apply #',time-func (mapcar #'date->time (cons number more-numbers)))))
412 ,(if result-func
413 `(funcall #',result-func result)
414 'result)))))
416 (wrap-time-for-date time=)
417 (wrap-time-for-date time/=)
418 (wrap-time-for-date time<)
419 (wrap-time-for-date time>)
420 (wrap-time-for-date time<=)
421 (wrap-time-for-date time>=)
422 (wrap-time-for-date time-max :result-func time->date)
423 (wrap-time-for-date time-min :result-func time->date)
425 (defun date-compare (date-a date-b)
426 (time-compare (date->time date-a) (date->time date-b)))
428 ;; ------------------------------------------------------------
429 ;; Formatting and output
431 (defvar +decimal-printer+ #(#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9))
433 (defun db-timestring (time)
434 "return the string to store the given time in the database"
435 (declare (optimize (speed 3)))
436 (let ((output (copy-seq "'XXXX-XX-XX XX:XX:XX.")))
437 (flet ((inscribe-base-10 (output offset size decimal)
438 (declare (type fixnum offset size decimal)
439 (type (simple-vector 10) +decimal-printer+))
440 (dotimes (x size)
441 (declare (type fixnum x)
442 (optimize (safety 0)))
443 (multiple-value-bind (next this)
444 (floor decimal 10)
445 (setf (aref output (+ (- size x 1) offset))
446 (aref +decimal-printer+ this))
447 (setf decimal next)))))
448 (multiple-value-bind (usec second minute hour day month year)
449 (decode-time time)
450 (inscribe-base-10 output 1 4 year)
451 (inscribe-base-10 output 6 2 month)
452 (inscribe-base-10 output 9 2 day)
453 (inscribe-base-10 output 12 2 hour)
454 (inscribe-base-10 output 15 2 minute)
455 (inscribe-base-10 output 18 2 second)
456 (format nil "~a~d'" output usec)))))
458 (defun iso-timestring (time)
459 "return the string to store the given time in the database"
460 (declare (optimize (speed 3)))
461 (let ((output (copy-seq "XXXX-XX-XX XX:XX:XX,")))
462 (flet ((inscribe-base-10 (output offset size decimal)
463 (declare (type fixnum offset size decimal)
464 (type (simple-vector 10) +decimal-printer+))
465 (dotimes (x size)
466 (declare (type fixnum x)
467 (optimize (safety 0)))
468 (multiple-value-bind (next this)
469 (floor decimal 10)
470 (setf (aref output (+ (- size x 1) offset))
471 (aref +decimal-printer+ this))
472 (setf decimal next)))))
473 (multiple-value-bind (usec second minute hour day month year)
474 (decode-time time)
475 (inscribe-base-10 output 0 4 year)
476 (inscribe-base-10 output 5 2 month)
477 (inscribe-base-10 output 8 2 day)
478 (inscribe-base-10 output 11 2 hour)
479 (inscribe-base-10 output 14 2 minute)
480 (inscribe-base-10 output 17 2 second)
481 (format nil "~a,~d" output usec)))))
483 (defun db-datestring (date)
484 (db-timestring (date->time date)))
485 (defun iso-datestring (date)
486 (iso-timestring (date->time date)))
489 ;; ------------------------------------------------------------
490 ;; Intervals
492 (defstruct interval
493 (start nil)
494 (end nil)
495 (name nil)
496 (contained nil)
497 (type nil)
498 (data nil))
500 ;; fix : should also return :contains / :contained
502 (defun interval-relation (x y)
503 "Compare the relationship of node x to node y. Returns either
504 :contained :contains :follows :overlaps or :precedes."
505 (let ((xst (interval-start x))
506 (xend (interval-end x))
507 (yst (interval-start y))
508 (yend (interval-end y)))
509 (case (time-compare xst yst)
510 (:equal
511 (case (time-compare xend yend)
512 (:less-than
513 :contained)
514 ((:equal :greater-than)
515 :contains)))
516 (:greater-than
517 (case (time-compare xst yend)
518 ((:equal :greater-than)
519 :follows)
520 (:less-than
521 (case (time-compare xend yend)
522 ((:less-than :equal)
523 :contained)
524 ((:greater-than)
525 :overlaps)))))
526 (:less-than
527 (case (time-compare xend yst)
528 ((:equal :less-than)
529 :precedes)
530 (:greater-than
531 (case (time-compare xend yend)
532 (:less-than
533 :overlaps)
534 ((:equal :greater-than)
535 :contains))))))))
537 ;; ------------------------------------------------------------
538 ;; interval lists
540 (defun sort-interval-list (list)
541 (sort list (lambda (x y)
542 (case (interval-relation x y)
543 ((:precedes :contains) t)
544 ((:follows :overlaps :contained) nil)))))
546 ;; interval push will return its list of intervals in strict order.
547 (defun interval-push (interval-list interval &optional container-rule)
548 (declare (ignore container-rule))
549 (let ((sorted-list (sort-interval-list interval-list)))
550 (dotimes (x (length sorted-list))
551 (let ((elt (nth x sorted-list)))
552 (case (interval-relation elt interval)
553 (:follows
554 (return-from interval-push (insert-at-index x sorted-list interval)))
555 (:contains
556 (return-from interval-push
557 (replace-at-index x sorted-list
558 (make-interval :start (interval-start elt)
559 :end (interval-end elt)
560 :type (interval-type elt)
561 :contained (interval-push (interval-contained elt) interval)
562 :data (interval-data elt)))))
563 ((:overlaps :contained)
564 (error "Overlap")))))
565 (append sorted-list (list interval))))
567 ;; interval lists
569 (defun interval-match (list time)
570 "Return the index of the first interval in list containing time"
571 ;; this depends on ordering of intervals!
572 (let ((list (sort-interval-list list)))
573 (dotimes (x (length list))
574 (let ((elt (nth x list)))
575 (when (and (time<= (interval-start elt) time)
576 (time< time (interval-end elt)))
577 (return-from interval-match x))))))
579 (defun interval-clear (list time)
580 (dotimes (x (length list))
581 (let ((elt (nth x list)))
582 (when (and (time<= (interval-start elt) time)
583 (time< time (interval-end elt)))
584 (if (interval-match (interval-contained elt) time)
585 (return-from interval-clear
586 (replace-at-index x list
587 (make-interval :start (interval-start elt)
588 :end (interval-end elt)
589 :type (interval-type elt)
590 :contained (interval-clear (interval-contained elt) time)
591 :data (interval-data elt))))
592 (return-from interval-clear
593 (delete-at-index x list)))))))
595 (defun interval-edit (list time start end &optional tag)
596 "Attempts to modify the most deeply nested interval in list which
597 begins at time. If no changes are made, returns nil."
598 ;; function required sorted interval list
599 (let ((list (sort-interval-list list)))
600 (if (null list) nil
601 (dotimes (x (length list))
602 (let ((elt (nth x list)))
603 (when (and (time<= (interval-start elt) time)
604 (time< time (interval-end elt)))
605 (or (interval-edit (interval-contained elt) time start end tag)
606 (cond ((and (< 0 x)
607 (time< start (interval-end (nth (1- x) list))))
608 (error "Overlap of previous interval"))
609 ((and (< x (1- (length list)))
610 (time< (interval-start (nth (1+ x) list)) end))
611 (error "~S ~S ~S ~S Overlap of next interval" x (length list) (interval-start (nth (1+ x) list)) end ))
612 ((time= (interval-start elt) time)
613 (return-from interval-edit
614 (replace-at-index x list
615 (make-interval :start start
616 :end end
617 :type (interval-type elt)
618 :contained (restrict-intervals (interval-contained elt) start end)
619 :data (or tag (interval-data elt))))))))))))))
621 (defun restrict-intervals (list start end &aux newlist)
622 (let ((test-interval (make-interval :start start :end end)))
623 (dolist (elt list)
624 (when (equal :contained
625 (interval-relation elt test-interval))
626 (push elt newlist)))
627 (nreverse newlist)))
629 ;;; utils from odcl/list.lisp
631 (defun replace-at-index (idx list elt)
632 (cond ((= idx 0)
633 (cons elt (cdr list)))
634 ((= idx (1- (length list)))
635 (append (butlast list) (list elt)))
637 (append (subseq list 0 idx)
638 (list elt)
639 (subseq list (1+ idx))))))
641 (defun insert-at-index (idx list elt)
642 (cond ((= idx 0)
643 (cons elt list))
644 ((= idx (1- (length list)))
645 (append list (list elt)))
647 (append (subseq list 0 idx)
648 (list elt)
649 (subseq list idx)))))
651 (defun delete-at-index (idx list)
652 (cond ((= idx 0)
653 (cdr list))
654 ((= idx (1- (length list)))
655 (butlast list))
657 (append (subseq list 0 idx)
658 (subseq list (1+ idx))))))
661 ;; ------------------------------------------------------------
662 ;; return MJD for Gregorian date
664 (defun gregorian-to-mjd (month day year)
665 (let ((b 0)
666 (month-adj month)
667 (year-adj (if (< year 0)
668 (+ year 1)
669 year))
672 (when (< month 3)
673 (incf month-adj 12)
674 (decf year-adj))
675 (unless (or (< year 1582)
676 (and (= year 1582)
677 (or (< month 10)
678 (and (= month 10)
679 (< day 15)))))
680 (let ((a (floor (/ year-adj 100))))
681 (setf b (+ (- 2 a) (floor (/ a 4))))))
682 (if (< year-adj 0)
683 (setf c (floor (- (* 365.25d0 year-adj) 679006.75d0)))
684 (setf c (floor (- (* 365.25d0 year-adj) 679006d0))))
685 (setf d (floor (* 30.6001 (+ 1 month-adj))))
686 ;; (cmsg "b ~s c ~s d ~s day ~s" b c d day)
687 (+ b c d day)))
689 ;; convert MJD to Gregorian date
691 (defun mjd-to-gregorian (mjd)
692 (let (z r g a b c year month day)
693 (setf z (floor (+ mjd 678882)))
694 (setf r (- (+ mjd 678882) z))
695 (setf g (- z .25))
696 (setf a (floor (/ g 36524.25)))
697 (setf b (- a (floor (/ a 4))))
698 (setf year (floor (/ (+ b g) 365.25)))
699 (setf c (- (+ b z) (floor (* 365.25 year))))
700 (setf month (truncate (/ (+ (* 5 c) 456) 153)))
701 (setf day (+ (- c (truncate (/ (- (* 153 month) 457) 5))) r))
702 (when (> month 12)
703 (incf year)
704 (decf month 12))
705 (list month day year)))
707 (defun duration+ (time &rest durations)
708 "Add each DURATION to TIME, returning a new wall-time value."
709 (let ((year (duration-year time))
710 (month (duration-month time))
711 (day (duration-day time))
712 (hour (duration-hour time))
713 (minute (duration-minute time))
714 (second (duration-second time))
715 (usec (duration-usec time)))
716 (dolist (duration durations)
717 (incf year (duration-year duration))
718 (incf month (duration-month duration))
719 (incf day (duration-day duration))
720 (incf hour (duration-hour duration))
721 (incf minute (duration-minute duration))
722 (incf second (duration-second duration))
723 (incf usec (duration-usec duration)))
724 (make-duration :year year :month month :day day :hour hour :minute minute
725 :second second :usec usec)))
727 (defun duration- (duration &rest durations)
728 "Subtract each DURATION from TIME, returning a new duration value."
729 (let ((year (duration-year duration))
730 (month (duration-month duration))
731 (day (duration-day duration))
732 (hour (duration-hour duration))
733 (minute (duration-minute duration))
734 (second (duration-second duration))
735 (usec (duration-usec duration)))
736 (dolist (duration durations)
737 (decf year (duration-year duration))
738 (decf month (duration-month duration))
739 (decf day (duration-day duration))
740 (decf hour (duration-hour duration))
741 (decf minute (duration-minute duration))
742 (decf second (duration-second duration))
743 (decf usec (duration-usec duration)))
744 (make-duration :year year :month month :day day :hour hour :minute minute
745 :second second :usec usec)))
747 ;; Date + Duration
749 (defun time+ (time &rest durations)
750 "Add each DURATION to TIME, returning a new wall-time value."
751 (let ((new-time (copy-time time)))
752 (dolist (duration durations)
753 (roll new-time
754 :year (duration-year duration)
755 :month (duration-month duration)
756 :day (duration-day duration)
757 :hour (duration-hour duration)
758 :minute (duration-minute duration)
759 :second (duration-second duration)
760 :usec (duration-usec duration)
761 :destructive t))
762 new-time))
764 (defun date+ (date &rest durations)
765 "Add each DURATION to DATE, returning a new date value.
766 Note that (barring daylight saving time) 12h + 12h will result in a new day, but doing
767 it as separate calculations will not, as the time is chopped to a date before being returned."
768 (time->date (apply #'time+ (cons (date->time date) durations))))
770 (defun time- (time &rest durations)
771 "Subtract each DURATION from TIME, returning a new wall-time value."
772 (let ((new-time (copy-time time)))
773 (dolist (duration durations)
774 (roll new-time
775 :year (- (duration-year duration))
776 :month (- (duration-month duration))
777 :day (- (duration-day duration))
778 :hour (- (duration-hour duration))
779 :minute (- (duration-minute duration))
780 :second (- (duration-second duration))
781 :usec (- (duration-usec duration))
782 :destructive t))
783 new-time))
785 (defun date- (date &rest durations)
786 "Subtract each DURATION to DATE, returning a new date value.
787 Note that (barring daylight saving time) 12h + 12h will result in a new day, but doing
788 it as separate calculations will not, as the time is chopped to a date before being returned."
789 (time->date (apply #'time- (cons (date->time date) durations))))
791 (defun time-difference (time1 time2)
792 "Returns a DURATION representing the difference between TIME1 and
793 TIME2."
794 (flet ((do-diff (time1 time2)
796 (let (day-diff sec-diff)
797 (setf day-diff (- (time-mjd time2)
798 (time-mjd time1)))
799 (if (> day-diff 0)
800 (progn (decf day-diff)
801 (setf sec-diff (+ (time-second time2)
802 (- (* 60 60 24)
803 (time-second time1)))))
804 (setf sec-diff (- (time-second time2)
805 (time-second time1))))
806 (make-duration :day day-diff
807 :second sec-diff))))
808 (if (time< time1 time2)
809 (do-diff time1 time2)
810 (do-diff time2 time1))))
812 (defun date-difference (date1 date2)
813 "Returns a DURATION representing the difference between TIME1 and
814 TIME2."
815 (time-difference (date->time date1) (date->time date2)))
817 (defun format-date (stream date &key format
818 (date-separator "-")
819 (internal-separator " "))
820 "produces on stream the datestring corresponding to the date
821 with the given options"
822 (format-time stream (date->time date)
823 :format format
824 :date-separator date-separator
825 :internal-separator internal-separator))
827 (defun format-time (stream time &key format
828 (date-separator "-")
829 (time-separator ":")
830 (internal-separator " "))
831 "produces on stream the timestring corresponding to the wall-time
832 with the given options"
833 (let ((*print-circle* nil))
834 (multiple-value-bind (usec second minute hour day month year dow)
835 (decode-time time)
836 (case format
837 (:pretty
838 (format stream "~A ~A, ~A ~D, ~D"
839 (pretty-time hour minute)
840 (day-name dow)
841 (month-name month)
843 year))
844 (:short-pretty
845 (format stream "~A, ~D/~D/~D"
846 (pretty-time hour minute)
847 month day year))
848 (:iso
849 (let ((string (iso-timestring time)))
850 (if stream
851 (write-string string stream)
852 string)))
854 (format stream "~2,'0D~A~2,'0D~A~2,'0D~A~2,'0D~A~2,'0D~A~2,'0D.~6,'0D"
855 year date-separator month date-separator day
856 internal-separator hour time-separator minute time-separator
857 second usec))))))
859 (defun pretty-time (hour minute)
860 (cond
861 ((eq hour 0)
862 (format nil "12:~2,'0D AM" minute))
863 ((eq hour 12)
864 (format nil "12:~2,'0D PM" minute))
865 ((< hour 12)
866 (format nil "~D:~2,'0D AM" hour minute))
867 ((and (> hour 12) (< hour 24))
868 (format nil "~D:~2,'0D PM" (- hour 12) minute))
870 (error "pretty-time got bad hour"))))
872 (defun leap-days-in-days (days)
873 ;; return the number of leap days between Mar 1 2000 and
874 ;; (Mar 1 2000) + days, where days can be negative
875 (if (< days 0)
876 (ceiling (/ (- days) (* 365 4)))
877 (floor (/ days (* 365 4)))))
879 (defun current-year ()
880 (third (mjd-to-gregorian (time-mjd (get-time)))))
882 (defun current-month ()
883 (first (mjd-to-gregorian (time-mjd (get-time)))))
885 (defun current-day ()
886 (second (mjd-to-gregorian (time-mjd (get-time)))))
888 (defun parse-date-time (string)
889 "parses date like 08/08/01, 8.8.2001, eg"
890 (when (> (length string) 1)
891 (let ((m (current-month))
892 (d (current-day))
893 (y (current-year)))
894 (let ((integers (mapcar #'parse-integer (hork-integers string))))
895 (case (length integers)
897 (setf y (car integers)))
899 (setf m (car integers))
900 (setf y (cadr integers)))
902 (setf m (car integers))
903 (setf d (cadr integers))
904 (setf y (caddr integers)))
906 (return-from parse-date-time))))
907 (when (< y 100)
908 (incf y 2000))
909 (make-time :year y :month m :day d))))
911 (defun hork-integers (input)
912 (let ((output '())
913 (start 0))
914 (dotimes (x (length input))
915 (unless (<= 48 (char-code (aref input x)) 57)
916 (push (subseq input start x) output)
917 (setf start (1+ x))))
918 (nreverse (push (subseq input start) output))))
920 (defun merged-time (day time-of-day)
921 (%make-wall-time :mjd (time-mjd day)
922 :second (time-second time-of-day)))
924 (defun time-meridian (hours)
925 (cond ((= hours 0)
926 (values 12 "AM"))
927 ((= hours 12)
928 (values 12 "PM"))
929 ((< 12 hours)
930 (values (- hours 12) "PM"))
932 (values hours "AM"))))
934 (defgeneric to-string (val &rest keys)
937 (defmethod to-string ((time wall-time) &rest keys)
938 (destructuring-bind (&key (style :daytime) &allow-other-keys)
939 keys
940 (print-date time style)))
942 (defun print-date (time &optional (style :daytime))
943 (multiple-value-bind (usec second minute hour day month year dow)
944 (decode-time time)
945 (declare (ignore usec second))
946 (multiple-value-bind (hours meridian)
947 (time-meridian hour)
948 (ecase style
949 (:time-of-day
950 ;; 2:00 PM
951 (format nil "~d:~2,'0d ~a" hours minute meridian))
952 (:long-day
953 ;; October 11th, 2000
954 (format nil "~a ~d, ~d" (month-name month) day year))
955 (:month
956 ;; October
957 (month-name month))
958 (:month-year
959 ;; October 2000
960 (format nil "~a ~d" (month-name month) year))
961 (:full
962 ;; 11:08 AM, November 22, 2002
963 (format nil "~d:~2,'0d ~a, ~a ~d, ~d"
964 hours minute meridian (month-name month) day year))
965 (:full+weekday
966 ;; 11:09 AM Friday, November 22, 2002
967 (format nil "~d:~2,'0d ~a ~a, ~a ~d, ~d"
968 hours minute meridian (nth dow *day-names*)
969 (month-name month) day year))
970 (:daytime
971 ;; 11:09 AM, 11/22/2002
972 (format-time nil time :format :short-pretty))
973 (:day
974 ;; 11/22/2002
975 (format nil "~d/~d/~d" month day year))))))
977 (defun time-element (time element)
978 (multiple-value-bind (usec second minute hour day month year dow)
979 (decode-time time)
980 (declare (ignore usec))
981 (ecase element
982 (:seconds
983 second)
984 (:minutes
985 minute)
986 (:hours
987 hour)
988 (:day-of-month
989 day)
990 (:integer-day-of-week
991 dow)
992 (:day-of-week
993 (nth dow *day-keywords*))
994 (:month
995 month)
996 (:year
997 year))))
999 (defun date-element (date element)
1000 (time-element (date->time date) element))
1002 (defun format-duration (stream duration &key (precision :minute))
1003 (let ((second (duration-second duration))
1004 (minute (duration-minute duration))
1005 (hour (duration-hour duration))
1006 (day (duration-day duration))
1007 (month (duration-month duration))
1008 (year (duration-year duration))
1009 (return (null stream))
1010 (stream (or stream (make-string-output-stream))))
1011 (ecase precision
1012 (:day
1013 (setf hour 0 second 0 minute 0))
1014 (:hour
1015 (setf second 0 minute 0))
1016 (:minute
1017 (setf second 0))
1018 (:second
1020 (if (= 0 year month day hour minute)
1021 (format stream "0 minutes")
1022 (let ((sent? nil))
1023 (when (< 0 year)
1024 (format stream "~d year~p" year year)
1025 (setf sent? t))
1026 (when (< 0 month)
1027 (when sent?
1028 (write-char #\Space stream))
1029 (format stream "~d month~p" month month)
1030 (setf sent? t))
1031 (when (< 0 day)
1032 (when sent?
1033 (write-char #\Space stream))
1034 (format stream "~d day~p" day day)
1035 (setf sent? t))
1036 (when (< 0 hour)
1037 (when sent?
1038 (write-char #\Space stream))
1039 (format stream "~d hour~p" hour hour)
1040 (setf sent? t))
1041 (when (< 0 minute)
1042 (when sent?
1043 (write-char #\Space stream))
1044 (format stream "~d min~p" minute minute)
1045 (setf sent? t))
1046 (when (< 0 second)
1047 (when sent?
1048 (write-char #\Space stream))
1049 (format stream "~d sec~p" second second))))
1050 (when return
1051 (get-output-stream-string stream))))
1053 (defgeneric midnight (self))
1054 (defmethod midnight ((self wall-time))
1055 "truncate hours, minutes and seconds"
1056 (%make-wall-time :mjd (time-mjd self)))
1058 (defun roll (date &key (year 0) (month 0) (day 0) (second 0) (hour 0)
1059 (minute 0) (usec 0) (destructive nil))
1060 (unless (= 0 year month)
1061 (multiple-value-bind (year-orig month-orig day-orig)
1062 (time-ymd date)
1063 (multiple-value-bind (new-year new-month)
1064 (floor (+ month month-orig (* 12 (+ year year-orig))) 12)
1065 (let ((new-date (make-time :year new-year
1066 :month new-month
1067 :day day-orig
1068 :second (time-second date)
1069 :usec usec)))
1070 (if destructive
1071 (setf (time-mjd date) (time-mjd new-date))
1072 (setq date new-date))))))
1073 (let ((mjd (time-mjd date))
1074 (sec (time-second date))
1075 (usec (time-usec date)))
1076 (multiple-value-bind (sec-new usec-new)
1077 (floor (+ usec
1078 (* 1000000
1079 (+ sec second
1080 (* 60 minute)
1081 (* 60 60 hour))))
1082 1000000)
1083 (multiple-value-bind (mjd-new sec-new)
1084 (floor sec-new (* 60 60 24))
1085 (if destructive
1086 (progn
1087 (setf (time-mjd date) (+ mjd mjd-new day)
1088 (time-second date) sec-new
1089 (time-usec date) usec-new)
1090 date)
1091 (%make-wall-time :mjd (+ mjd mjd-new day)
1092 :second sec-new
1093 :usec usec-new))))))
1095 (defun roll-to (date size position)
1096 (ecase size
1097 (:month
1098 (ecase position
1099 (:beginning
1100 (roll date :day (+ 1
1101 (- (time-element date :day-of-month)))))
1102 (:end
1103 (roll date :day (+ (days-in-month (time-element date :month)
1104 (time-element date :year))
1105 (- (time-element date :day-of-month)))))))))
1107 (defun week-containing (time)
1108 (let* ((midn (midnight time))
1109 (dow (time-element midn :integer-day-of-week)))
1110 (list (roll midn :day (- dow))
1111 (roll midn :day (- 7 dow)))))
1113 (defun leap-year? (year)
1114 "t if YEAR is a leap yeap in the Gregorian calendar"
1115 (and (= 0 (mod year 4))
1116 (or (not (= 0 (mod year 100)))
1117 (= 0 (mod year 400)))))
1119 (defun valid-month-p (month)
1120 "t if MONTH exists in the Gregorian calendar"
1121 (<= 1 month 12))
1123 (defun valid-gregorian-date-p (date)
1124 "t if DATE (year month day) exists in the Gregorian calendar"
1125 (let ((max-day (days-in-month (nth 1 date) (nth 0 date))))
1126 (<= 1 (nth 2 date) max-day)))
1128 (defun days-in-month (month year &key (careful t))
1129 "the number of days in MONTH of YEAR, observing Gregorian leap year
1130 rules"
1131 (declare (type fixnum month year))
1132 (when careful
1133 (check-type month (satisfies valid-month-p)
1134 "between 1 (January) and 12 (December)"))
1135 (if (eql month 2) ; feb
1136 (if (leap-year? year)
1137 29 28)
1138 (let ((even (mod (1- month) 2)))
1139 (if (< month 8) ; aug
1140 (- 31 even)
1141 (+ 30 even)))))
1143 (defun day-of-year (year month day &key (careful t))
1144 "the day number within the year of the date DATE. For example,
1145 1987 1 1 returns 1"
1146 (declare (type fixnum year month day))
1147 (when careful
1148 (let ((date (list year month day)))
1149 (check-type date (satisfies valid-gregorian-date-p)
1150 "a valid Gregorian date")))
1151 (let ((doy (+ day (* 31 (1- month)))))
1152 (declare (type fixnum doy))
1153 (when (< 2 month)
1154 (setf doy (- doy (floor (+ 23 (* 4 month)) 10)))
1155 (when (leap-year? year)
1156 (incf doy)))
1157 doy))
1159 (defun parse-yearstring (string)
1160 (let ((year (or (parse-integer-insensitively string)
1161 (extract-roman string))))
1162 (when (and year (< 1500 year 2500))
1163 (make-time :year year))))
1165 (defun parse-integer-insensitively (string)
1166 (let ((start (position-if #'digit-char-p string))
1167 (end (position-if #'digit-char-p string :from-end t)))
1168 (when (and start end)
1169 (parse-integer (subseq string start (1+ end)) :junk-allowed t))))
1171 (defvar *roman-digits*
1172 '((#\M . 1000)
1173 (#\D . 500)
1174 (#\C . 100)
1175 (#\L . 50)
1176 (#\X . 10)
1177 (#\V . 5)
1178 (#\I . 1)))
1180 (defun extract-roman (string &aux parse)
1181 (dotimes (x (length string))
1182 (let ((val (cdr (assoc (aref string x) *roman-digits*))))
1183 (when (and val parse (< (car parse) val))
1184 (push (- (pop parse)) parse))
1185 (push val parse)))
1186 (apply #'+ parse))
1189 ;; ------------------------------------------------------------
1190 ;; Parsing iso-8601 timestrings
1192 (define-condition iso-8601-syntax-error (sql-user-error)
1193 ((bad-component;; year, month whatever
1194 :initarg :bad-component
1195 :reader bad-component))
1196 (:report (lambda (c stream)
1197 (format stream "Bad component: ~A " (bad-component c)))))
1199 (defun parse-timestring (timestring &key (start 0) end junk-allowed)
1200 "parse a timestring and return the corresponding wall-time. If the
1201 timestring starts with P, read a duration; otherwise read an ISO 8601
1202 formatted date string."
1203 (declare (ignore junk-allowed))
1204 (let ((string (subseq timestring start end)))
1205 (if (char= (aref string 0) #\P)
1206 (parse-iso-8601-duration string)
1207 (parse-iso-8601-time string))))
1209 (defun parse-datestring (datestring &key (start 0) end junk-allowed)
1210 "parse a ISO 8601 timestring and return the corresponding date.
1211 Will throw a hissy fit if the date string is a duration. Will ignore any precision beyond day (hour/min/sec/usec)."
1212 (let ((parsed-value (parse-timestring datestring :start start :end end :junk-allowed junk-allowed)))
1213 (ecase (type-of parsed-value)
1214 (wall-time (%make-date :mjd (time-mjd parsed-value))))))
1217 (defvar *iso-8601-duration-delimiters*
1218 '((#\Y . :years)
1219 (#\D . :days)
1220 (#\H . :hours)
1221 (#\M . :months/minutes)
1222 (#\S . :seconds)))
1224 (defun iso-8601-delimiter (elt)
1225 (cdr (assoc elt *iso-8601-duration-delimiters*)))
1227 (defun iso-8601-duration-subseq (string end)
1228 (let* ((pos (position-if #'iso-8601-delimiter string :end end :from-end t))
1229 (pos2 (when pos
1230 (position-if-not #'digit-char-p string :end pos :from-end t)))
1231 (number (when pos2
1232 (parse-integer
1233 (subseq string (1+ pos2) pos) :junk-allowed t))))
1234 (when number
1235 (values number
1236 (1+ pos)
1237 (1+ pos2)
1238 (iso-8601-delimiter (aref string pos))))))
1240 (defun parse-iso-8601-duration (string)
1241 "return a wall-time from a duration string"
1242 (block parse
1243 (let ((years 0)
1244 (months 0)
1245 (days 0)
1246 (secs 0)
1247 (hours 0)
1248 (minutes 0)
1249 (index (length string))
1250 (months/minutes nil))
1251 (loop
1252 (multiple-value-bind (duration end next-index duration-type)
1253 (iso-8601-duration-subseq string index)
1254 (declare (ignore end))
1255 (case duration-type
1256 (:years
1257 (incf years duration))
1258 (:months/minutes
1259 (if months/minutes
1260 (incf months duration)
1261 (progn
1262 (setq months/minutes t)
1263 (incf minutes duration))))
1264 (:days
1265 (setq months/minutes t)
1266 (incf days duration))
1267 (:hours
1268 (setq months/minutes t)
1269 (incf hours duration))
1270 (:seconds
1271 (incf secs duration))
1273 (return-from parse
1274 (make-duration
1275 :year years :month months :day days :hour hours
1276 :minute minutes :second secs))))
1277 (setf index next-index))))))
1279 ;; e.g. 2000-11-11 00:00:00-06
1281 (defun parse-iso-8601-time (string)
1282 "return the wall-time corresponding to the given ISO 8601 datestring"
1283 (multiple-value-bind (year month day hour minute second usec offset)
1284 (syntax-parse-iso-8601 string)
1285 (make-time :year year
1286 :month month
1287 :day day
1288 :hour hour
1289 :minute minute
1290 :second second
1291 :usec usec
1292 :offset offset)))
1295 (defun syntax-parse-iso-8601 (string)
1296 ;; use strlen to determine if fractional seconds are present in timestamp
1297 (let ((strlen (length string))
1298 year month day hour minute second usec gmt-sec-offset)
1299 (handler-case
1300 (progn
1301 (setf year (parse-integer string :start 0 :end 4)
1302 month (parse-integer string :start 5 :end 7)
1303 day (parse-integer string :start 8 :end 10)
1304 hour (if (<= 13 strlen)
1305 (parse-integer string :start 11 :end 13)
1307 minute (if (<= 16 strlen)
1308 (parse-integer string :start 14 :end 16)
1310 second (if (<= 19 strlen)
1311 (parse-integer string :start 17 :end 19)
1313 (cond
1314 ((and (> strlen 19)
1315 (or (char= #\, (char string 19))
1316 (char= #\. (char string 19))))
1317 (multiple-value-bind (parsed-usec usec-end)
1318 (parse-integer string :start 20 :junk-allowed t)
1319 (setf usec (or parsed-usec 0)
1320 gmt-sec-offset (if (<= (+ 3 usec-end) strlen)
1321 (let ((skip-to (or (position #\+ string :start 19)
1322 (position #\- string :start 19))))
1323 (if skip-to
1324 (* 60 60
1325 (parse-integer string :start skip-to
1326 :end (+ skip-to 3)))
1328 0))))
1330 (setf usec 0
1331 gmt-sec-offset (if (<= 22 strlen)
1332 (let ((skip-to (or (position #\+ string :start 19)
1333 (position #\- string :start 19))))
1334 (if skip-to
1335 (* 60 60
1336 (parse-integer string :start skip-to
1337 :end (+ skip-to 3)))
1339 0))))
1340 (unless (< 0 year)
1341 (error 'iso-8601-syntax-error
1342 :bad-component '(year . 0)))
1343 (unless (< 0 month)
1344 (error 'iso-8601-syntax-error
1345 :bad-component '(month . 0)))
1346 (unless (< 0 day)
1347 (error 'iso-8601-syntax-error
1348 :bad-component '(month . 0)))
1349 (values year month day hour minute second usec gmt-sec-offset))
1350 (simple-error ()
1351 (error 'iso-8601-syntax-error
1352 :bad-component
1353 (car (find-if (lambda (pair) (null (cdr pair)))
1354 `((year . ,year) (month . ,month)
1355 (day . ,day) (hour . ,hour)
1356 (minute . ,minute) (second . ,second)
1357 (usec . ,usec)
1358 (timezone . ,gmt-sec-offset)))))))))