X-Git-Url: http://git.kpe.io/?p=clsql.git;a=blobdiff_plain;f=sql%2Ftime.lisp;h=e04525889f54d301f4a428488d304e1cb37a7395;hp=078e4aa5c39efe888bbfa539c3dc2afb46042d68;hb=6b34e2293a52b03e8611c85e4e53a0ab5c8a3c1a;hpb=0e0e260d3c6a995e46336c0b9b08ef19d941bb8b diff --git a/sql/time.lisp b/sql/time.lisp index 078e4aa..e045258 100644 --- a/sql/time.lisp +++ b/sql/time.lisp @@ -67,7 +67,9 @@ (defun %print-wall-time (time stream depth) (declare (ignore depth)) - (format stream "#" (format-time nil time))) + (if *print-escape* + (format stream "#" (format-time nil time)) + (format-time stream time :format :pretty))) (defstruct (duration (:constructor %make-duration) (:print-function %print-duration)) @@ -81,8 +83,20 @@ (defun %print-duration (duration stream depth) (declare (ignore depth)) - (format stream "#" - (format-duration nil duration :precision :second))) + (if *print-escape* + (format stream "#" + (format-duration nil duration :precision :second)) + (format-duration stream duration :precision :second))) + +(defstruct (date (:constructor %make-date) + (:print-function %print-date)) + (mjd 0 :type fixnum)) + +(defun %print-date (date stream depth) + (declare (ignore depth)) + (if *print-escape* + (format stream "#" (format-date nil date)) + (format-date stream date :format :pretty))) );eval-when @@ -107,6 +121,11 @@ (floor sec (* 60 60 24)) (%make-wall-time :mjd (+ mjd day-add) :second raw-sec :usec usec)))) +(defun make-date (&key (year 0) (month 1) (day 1) (hour 0) (minute 0) + (second 0) (usec 0) (offset 0)) + (time->date (make-time :year year :month month :day day :hour hour + :minute minute :second second :usec usec :offset offset))) + (defun copy-time (time) (%make-wall-time :mjd (time-mjd time) :second (time-second time))) @@ -118,10 +137,22 @@ (make-time :year year :month mon :day day :hour hour :minute minute :second second))) +(defun date->time (date) + "Returns a walltime for the given date" + (%make-wall-time :mjd (date-mjd date))) + +(defun time->date (time) + "Returns a date for the given wall time (obvious loss in resolution)" + (%make-date :mjd (time-mjd time))) + (defun get-time () "Return a pair: (GREGORIAN DAY . TIME-OF-DAY)" (utime->time (get-universal-time))) +(defun get-date () + "Returns a date for today" + (time->date (get-time))) + (defun make-duration (&key (year 0) (month 0) (day 0) (hour 0) (minute 0) (second 0) (usec 0)) (multiple-value-bind (second-add usec-1000000) @@ -167,6 +198,18 @@ (time-hms time) (values (time-usec time) second minute hour day month year (mod (+ (time-mjd time) 3) 7))))) +(defun date-ymd (date) + (time-ymd (date->time date))) + +(defun date-dow (date) + (time-dow (date->time date))) + +(defun decode-date (date) + "returns the decoded date as multiple values: day month year integer day-of-week" + (multiple-value-bind (year month day) + (time-ymd (date->time date)) + (values day month year (date-dow date)))) + ;; duration specific (defun duration-reduce (duration precision &optional round) (ecase precision @@ -212,7 +255,7 @@ (defun duration<= (duration-a duration-b) (<= (duration-reduce duration-a :usec) (duration-reduce duration-b :usec))) - + (defun duration>= (x y) (duration<= y x)) @@ -227,7 +270,7 @@ (if (/= (time-second x) (time-second y)) (< (time-second x) (time-second y)) (< (time-usec x) (time-usec y)))))) - + (defun %time>= (x y) (if (/= (time-mjd x) (time-mjd y)) (>= (time-mjd x) (time-mjd y)) @@ -342,6 +385,43 @@ :less-than :greater-than)))) +; now the same for dates +(eval-when (:compile-toplevel :load-toplevel) +(defun replace-string (string1 search-string replace-string &key (test #'string=)) + "Search within string1 for search-string, replace with replace-string, non-destructively." + (let ((replace-string-length (length replace-string)) + (search-string-length (length search-string))) + (labels ((sub-replace-string (current-string position) + (let ((found-position (search search-string current-string :test test :start2 position))) + (if (null found-position) + current-string + (sub-replace-string (concatenate 'string + (subseq current-string 0 found-position) + replace-string + (subseq current-string (+ found-position search-string-length))) + (+ position replace-string-length)))))) + (sub-replace-string string1 0)))) +);eval-when + +(defmacro wrap-time-for-date (time-func &key (result-func)) + (let ((date-func (intern (replace-string (symbol-name time-func) "TIME" "DATE")))) + `(defun ,date-func (number &rest more-numbers) + (let ((result (apply #',time-func (mapcar #'date->time (cons number more-numbers))))) + ,(if result-func + `(funcall #',result-func result) + 'result))))) + +(wrap-time-for-date time=) +(wrap-time-for-date time/=) +(wrap-time-for-date time<) +(wrap-time-for-date time>) +(wrap-time-for-date time<=) +(wrap-time-for-date time>=) +(wrap-time-for-date time-max :result-func time->date) +(wrap-time-for-date time-min :result-func time->date) + +(defun date-compare (date-a date-b) + (time-compare (date->time date-a) (date->time date-b))) ;; ------------------------------------------------------------ ;; Formatting and output @@ -398,6 +478,11 @@ (inscribe-base-10 output 17 2 second) (format nil "~a,~d" output usec))))) +(defun db-datestring (date) + (db-timestring (date->time date))) +(defun iso-datestring (date) + (iso-timestring (date->time date))) + ;; ------------------------------------------------------------ ;; Intervals @@ -405,7 +490,7 @@ (defstruct interval (start nil) (end nil) - (name nil) + (name nil) (contained nil) (type nil) (data nil)) @@ -478,17 +563,17 @@ (append sorted-list (list interval)))) ;; interval lists - + (defun interval-match (list time) "Return the index of the first interval in list containing time" ;; this depends on ordering of intervals! - (let ((list (sort-interval-list list))) + (let ((list (sort-interval-list list))) (dotimes (x (length list)) (let ((elt (nth x list))) (when (and (time<= (interval-start elt) time) (time< time (interval-end elt))) (return-from interval-match x)))))) - + (defun interval-clear (list time) (dotimes (x (length list)) (let ((elt (nth x list))) @@ -509,7 +594,7 @@ "Attempts to modify the most deeply nested interval in list which begins at time. If no changes are made, returns nil." ;; function required sorted interval list - (let ((list (sort-interval-list list))) + (let ((list (sort-interval-list list))) (if (null list) nil (dotimes (x (length list)) (let ((elt (nth x list))) @@ -674,6 +759,12 @@ begins at time. If no changes are made, returns nil." :destructive t)) new-time)) +(defun date+ (date &rest durations) + "Add each DURATION to DATE, returning a new date value. +Note that (barring daylight saving time) 12h + 12h will result in a new day, but doing +it as separate calculations will not, as the time is chopped to a date before being returned." + (time->date (apply #'time+ (cons (date->time date) durations)))) + (defun time- (time &rest durations) "Subtract each DURATION from TIME, returning a new wall-time value." (let ((new-time (copy-time time))) @@ -689,11 +780,17 @@ begins at time. If no changes are made, returns nil." :destructive t)) new-time)) +(defun date- (date &rest durations) + "Subtract each DURATION to DATE, returning a new date value. +Note that (barring daylight saving time) 12h + 12h will result in a new day, but doing +it as separate calculations will not, as the time is chopped to a date before being returned." + (time->date (apply #'time- (cons (date->time date) durations)))) + (defun time-difference (time1 time2) "Returns a DURATION representing the difference between TIME1 and TIME2." (flet ((do-diff (time1 time2) - + (let (day-diff sec-diff) (setf day-diff (- (time-mjd time2) (time-mjd time1))) @@ -710,6 +807,21 @@ TIME2." (do-diff time1 time2) (do-diff time2 time1)))) +(defun date-difference (date1 date2) + "Returns a DURATION representing the difference between TIME1 and +TIME2." + (time-difference (date->time date1) (date->time date2))) + +(defun format-date (stream date &key format + (date-separator "-") + (internal-separator " ")) + "produces on stream the datestring corresponding to the date +with the given options" + (format-time stream (date->time date) + :format format + :date-separator date-separator + :internal-separator internal-separator)) + (defun format-time (stream time &key format (date-separator "-") (time-separator ":") @@ -741,7 +853,7 @@ with the given options" year date-separator month date-separator day internal-separator hour time-separator minute time-separator second usec)))))) - + (defun pretty-time (hour minute) (cond ((eq hour 0) @@ -802,7 +914,7 @@ with the given options" (push (subseq input start x) output) (setf start (1+ x)))) (nreverse (push (subseq input start) output)))) - + (defun merged-time (day time-of-day) (%make-wall-time :mjd (time-mjd day) :second (time-second time-of-day))) @@ -826,9 +938,9 @@ with the given options" (print-date time style))) (defun print-date (time &optional (style :daytime)) - (multiple-value-bind (second minute hour day month year dow) + (multiple-value-bind (usec second minute hour day month year dow) (decode-time time) - (declare (ignore second)) + (declare (ignore usec second)) (multiple-value-bind (hours meridian) (time-meridian hour) (ecase style @@ -861,8 +973,9 @@ with the given options" (format nil "~d/~d/~d" month day year)))))) (defun time-element (time element) - (multiple-value-bind (second minute hour day month year dow) + (multiple-value-bind (usec second minute hour day month year dow) (decode-time time) + (declare (ignore usec)) (ecase element (:seconds second) @@ -881,6 +994,9 @@ with the given options" (:year year)))) +(defun date-element (date element) + (time-element (date->time date) element)) + (defun format-duration (stream duration &key (precision :minute)) (let ((second (duration-second duration)) (minute (duration-minute duration)) @@ -930,11 +1046,16 @@ with the given options" (unless (= 0 year month) (multiple-value-bind (year-orig month-orig day-orig) (time-ymd date) - (setf date (make-time :year (+ year year-orig) - :month (+ month month-orig) - :day day-orig - :second (time-second date) - :usec usec)))) + (multiple-value-bind (new-year new-month) + (floor (+ month month-orig (* 12 (+ year year-orig))) 12) + (let ((new-date (make-time :year new-year + :month new-month + :day day-orig + :second (time-second date) + :usec usec))) + (if destructive + (setf (time-mjd date) (time-mjd new-date)) + (setq date new-date)))))) (let ((mjd (time-mjd date)) (sec (time-second date)) (usec (time-usec date))) @@ -946,7 +1067,7 @@ with the given options" (* 60 60 hour)))) 1000000) (multiple-value-bind (mjd-new sec-new) - (floor sec (* 60 60 24)) + (floor sec-new (* 60 60 24)) (if destructive (progn (setf (time-mjd date) (+ mjd mjd-new day) @@ -1022,7 +1143,7 @@ rules" doy)) (defun parse-yearstring (string) - (let ((year (or (parse-integer-insensitively string) + (let ((year (or (parse-integer-insensitively string) (extract-roman string)))) (when (and year (< 1500 year 2500)) (make-time :year year)))) @@ -1052,7 +1173,7 @@ rules" ;; ------------------------------------------------------------ -;; Parsing iso-8601 timestrings +;; Parsing iso-8601 timestrings (define-condition iso-8601-syntax-error (sql-user-error) ((bad-component;; year, month whatever @@ -1065,12 +1186,20 @@ rules" "parse a timestring and return the corresponding wall-time. If the timestring starts with P, read a duration; otherwise read an ISO 8601 formatted date string." - (declare (ignore junk-allowed)) + (declare (ignore junk-allowed)) (let ((string (subseq timestring start end))) (if (char= (aref string 0) #\P) (parse-iso-8601-duration string) (parse-iso-8601-time string)))) +(defun parse-datestring (datestring &key (start 0) end junk-allowed) + "parse a ISO 8601 timestring and return the corresponding date. +Will throw a hissy fit if the date string is a duration. Will ignore any precision beyond day (hour/min/sec/usec)." + (let ((parsed-value (parse-timestring datestring :start start :end end :junk-allowed junk-allowed))) + (ecase (type-of parsed-value) + (wall-time (%make-date :mjd (time-mjd parsed-value)))))) + + (defvar *iso-8601-duration-delimiters* '((#\D . :days) (#\H . :hours) @@ -1150,7 +1279,7 @@ formatted date string." (char= #\. (char string 19)))) (multiple-value-bind (parsed-usec usec-end) (parse-integer string :start 20 :junk-allowed t) - (setf usec parsed-usec + (setf usec (or parsed-usec 0) gmt-sec-offset (if (<= (+ 3 usec-end) strlen) (let ((skip-to (or (position #\+ string :start 19) (position #\- string :start 19))))