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