(defun %print-wall-time (time stream depth)
(declare (ignore depth))
- (format stream "#<WALL-TIME: ~a>" (format-time nil time)))
+ (if *print-escape*
+ (format stream "#<WALL-TIME: ~a>" (format-time nil time))
+ (format-time stream time :format :pretty)))
(defstruct (duration (:constructor %make-duration)
(:print-function %print-duration))
(defun %print-duration (duration stream depth)
(declare (ignore depth))
- (format stream "#<DURATION: ~a>"
- (format-duration nil duration :precision :second)))
+ (if *print-escape*
+ (format stream "#<DURATION: ~a>"
+ (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 "#<DATE: ~a>" (format-date nil date))
+ (format-date stream date :format :pretty)))
);eval-when
(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)))
(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)
(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
(defun duration<= (duration-a duration-b)
(<= (duration-reduce duration-a :usec)
(duration-reduce duration-b :usec)))
-
+
(defun duration>= (x y)
(duration<= y x))
(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))
: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
(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
(defstruct interval
(start nil)
(end nil)
- (name nil)
+ (name nil)
(contained nil)
(type nil)
(data nil))
(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)))
"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)))
: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)))
: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)))
(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 ":")
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)
(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)))
(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
(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)
(: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))
(* 60 minute)
(* 60 60 hour))))
1000000)
- (declare (ignore sec-new))
(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)
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))))
;; ------------------------------------------------------------
-;; Parsing iso-8601 timestrings
+;; Parsing iso-8601 timestrings
(define-condition iso-8601-syntax-error (sql-user-error)
((bad-component;; year, month whatever
"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)
(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))))