X-Git-Url: http://git.kpe.io/?a=blobdiff_plain;ds=sidebyside;f=base%2Ftime.lisp;fp=base%2Ftime.lisp;h=0000000000000000000000000000000000000000;hb=8a8ee2d7d791b7a3efaed06420802a925d16fca3;hp=0b70f2c6f28b9610f7b7ac9875b38dfa72da3f4a;hpb=09f07ac9d914a83f9426609f3264f4e66b5a6d97;p=clsql.git diff --git a/base/time.lisp b/base/time.lisp deleted file mode 100644 index 0b70f2c..0000000 --- a/base/time.lisp +++ /dev/null @@ -1,1122 +0,0 @@ -;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- -;;;; ************************************************************************* -;;;; -;;;; $Id$ -;;;; -;;;; A variety of structures and function for creating and -;;;; manipulating dates, times, durations and intervals for -;;;; CLSQL. -;;;; -;;;; This file was originally part of ODCL and is Copyright (c) 2002 - -;;;; 2003 onShore Development, Inc. -;;;; -;;;; CLSQL users are granted the rights to distribute and use this software -;;;; as governed by the terms of the Lisp Lesser GNU Public License -;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. -;;;; ************************************************************************* - -(in-package #:clsql-base) - -;; ------------------------------------------------------------ -;; Months - -(defvar *month-keywords* - '(:january :february :march :april :may :june :july :august :september - :october :november :december)) - -(defvar *month-names* - '("" "January" "February" "March" "April" "May" "June" "July" "August" - "September" "October" "November" "December")) - -(defun month-name (month-index) - (nth month-index *month-names*)) - -(defun ordinal-month (month-keyword) - "Return the zero-based month number for the given MONTH keyword." - (position month-keyword *month-keywords*)) - - -;; ------------------------------------------------------------ -;; Days - -(defvar *day-keywords* - '(:sunday :monday :tuesday :wednesday :thursday :friday :saturday)) - -(defvar *day-names* - '("Sunday" "Monday" "Tuesday" "Wednesday" "Thursday" "Friday" "Saturday")) - -(defun day-name (day-index) - (nth day-index *day-names*)) - -(defun ordinal-day (day-keyword) - "Return the zero-based day number for the given DAY keyword." - (position day-keyword *day-keywords*)) - - -;; ------------------------------------------------------------ -;; time classes: wall-time, duration - -(eval-when (:compile-toplevel :load-toplevel) - -(defstruct (wall-time (:conc-name time-) - (:constructor %make-wall-time) - (:print-function %print-wall-time)) - (mjd 0 :type fixnum) - (second 0 :type fixnum)) - -(defun %print-wall-time (time stream depth) - (declare (ignore depth)) - (format stream "#" (format-time nil time))) - -(defstruct (duration (:constructor %make-duration) - (:print-function %print-duration)) - (year 0 :type fixnum) - (month 0 :type fixnum) - (day 0 :type fixnum) - (hour 0 :type fixnum) - (second 0 :type fixnum) - (minute 0 :type fixnum)) - -(defun %print-duration (duration stream depth) - (declare (ignore depth)) - (format stream "#" - (format-duration nil duration :precision :second))) - -);eval-when - -(defun duration-timestring (duration) - (let ((second (duration-second duration)) - (minute (duration-minute duration)) - (hour (duration-hour duration)) - (day (duration-day duration))) - (format nil "P~dD~dH~dM~dS" day hour minute second))) - - -;; ------------------------------------------------------------ -;; Constructors - -(defun make-time (&key (year 0) (month 1) (day 1) (hour 0) (minute 0) - (second 0) (offset 0)) - (let ((mjd (gregorian-to-mjd month day year)) - (sec (+ (* hour 60 60) - (* minute 60) - second (- offset)))) - (multiple-value-bind (day-add raw-sec) - (floor sec (* 60 60 24)) - (%make-wall-time :mjd (+ mjd day-add) :second raw-sec)))) - -(defun copy-time (time) - (%make-wall-time :mjd (time-mjd time) - :second (time-second time))) - -(defun utime->time (utime) - "Return a pair: (GREGORIAN DAY . TIME-OF-DAY)" - (multiple-value-bind (second minute hour day mon year) - (decode-universal-time utime) - (make-time :year year :month mon :day day :hour hour :minute minute - :second second))) - -(defun get-time () - "Return a pair: (GREGORIAN DAY . TIME-OF-DAY)" - (utime->time (get-universal-time))) - -(defun make-duration (&key (year 0) (month 0) (day 0) (hour 0) (minute 0) - (second 0)) - (multiple-value-bind (minute-add second-60) - (floor second 60) - (multiple-value-bind (hour-add minute-60) - (floor (+ minute minute-add) 60) - (multiple-value-bind (day-add hour-24) - (floor (+ hour hour-add) 24) - (%make-duration :year year :month month :day (+ day day-add) - :hour hour-24 - :minute minute-60 - :second second-60))))) - - -;; ------------------------------------------------------------ -;; Accessors - -(defun time-hms (time) - (multiple-value-bind (hourminute second) - (floor (time-second time) 60) - (multiple-value-bind (hour minute) - (floor hourminute 60) - (values hour minute second)))) - -(defun time-ymd (time) - (destructuring-bind (month day year) - (mjd-to-gregorian (time-mjd time)) - (values year month day))) - -(defun time-dow (time) - "Return the 0 indexed Day of the week starting with Sunday" - (mod (+ 3 (time-mjd time)) 7)) - -(defun decode-time (time) - "returns the decoded time as multiple values: second, minute, hour, day, -month, year, integer day-of-week" - (multiple-value-bind (year month day) - (time-ymd time) - (multiple-value-bind (hour minute second) - (time-hms time) - (values second minute hour day month year (mod (+ (time-mjd time) 3) 7))))) - -;; duration specific -(defun duration-reduce (duration precision &optional round) - (ecase precision - (:second - (+ (duration-second duration) - (* (duration-reduce duration :minute) 60))) - (:minute - (+ (if round - (floor (duration-second duration) 30) - 0) - (duration-minute duration) - (* (duration-reduce duration :hour) 60))) - (:hour - (+ (if round - (floor (duration-minute duration) 30) - 0) - (duration-hour duration) - (* (duration-reduce duration :day) 24))) - (:day - (+ (if round - (floor (duration-hour duration) 12) - 0) - (duration-day duration))))) - - -;; ------------------------------------------------------------ -;; Arithemetic and comparators - -(defun duration= (duration-a duration-b) - (= (duration-reduce duration-a :second) - (duration-reduce duration-b :second))) - -(defun duration< (duration-a duration-b) - (< (duration-reduce duration-a :second) - (duration-reduce duration-b :second))) - -(defun duration<= (duration-a duration-b) - (<= (duration-reduce duration-a :second) - (duration-reduce duration-b :second))) - -(defun duration>= (x y) - (duration<= y x)) - -(defun duration> (x y) - (duration< y x)) - -(defun %time< (x y) - (let ((mjd-x (time-mjd x)) - (mjd-y (time-mjd y))) - (if (/= mjd-x mjd-y) - (< mjd-x mjd-y) - (< (time-second x) (time-second y))))) - -(defun %time>= (x y) - (if (/= (time-mjd x) (time-mjd y)) - (>= (time-mjd x) (time-mjd y)) - (>= (time-second x) (time-second y)))) - -(defun %time<= (x y) - (if (/= (time-mjd x) (time-mjd y)) - (<= (time-mjd x) (time-mjd y)) - (<= (time-second x) (time-second y)))) - -(defun %time> (x y) - (if (/= (time-mjd x) (time-mjd y)) - (> (time-mjd x) (time-mjd y)) - (> (time-second x) (time-second y)))) - -(defun %time= (x y) - (and (= (time-mjd x) (time-mjd y)) - (= (time-second x) (time-second y)))) - -(defun time= (number &rest more-numbers) - "Returns T if all of its arguments are numerically equal, NIL otherwise." - (do ((nlist more-numbers (cdr nlist))) - ((atom nlist) t) - (declare (list nlist)) - (if (not (%time= (car nlist) number)) (return nil)))) - -(defun time/= (number &rest more-numbers) - "Returns T if no two of its arguments are numerically equal, NIL otherwise." - (do* ((head number (car nlist)) - (nlist more-numbers (cdr nlist))) - ((atom nlist) t) - (declare (list nlist)) - (unless (do* ((nl nlist (cdr nl))) - ((atom nl) t) - (declare (list nl)) - (if (%time= head (car nl)) (return nil))) - (return nil)))) - -(defun time< (number &rest more-numbers) - "Returns T if its arguments are in strictly increasing order, NIL otherwise." - (do* ((n number (car nlist)) - (nlist more-numbers (cdr nlist))) - ((atom nlist) t) - (declare (list nlist)) - (if (not (%time< n (car nlist))) (return nil)))) - -(defun time> (number &rest more-numbers) - "Returns T if its arguments are in strictly decreasing order, NIL otherwise." - (do* ((n number (car nlist)) - (nlist more-numbers (cdr nlist))) - ((atom nlist) t) - (declare (list nlist)) - (if (not (%time> n (car nlist))) (return nil)))) - -(defun time<= (number &rest more-numbers) - "Returns T if arguments are in strictly non-decreasing order, NIL otherwise." - (do* ((n number (car nlist)) - (nlist more-numbers (cdr nlist))) - ((atom nlist) t) - (declare (list nlist)) - (if (not (%time<= n (car nlist))) (return nil)))) - -(defun time>= (number &rest more-numbers) - "Returns T if arguments are in strictly non-increasing order, NIL otherwise." - (do* ((n number (car nlist)) - (nlist more-numbers (cdr nlist))) - ((atom nlist) t) - (declare (list nlist)) - (if (not (%time>= n (car nlist))) (return nil)))) - -(defun time-max (number &rest more-numbers) - "Returns the greatest of its arguments." - (do ((nlist more-numbers (cdr nlist)) - (result number)) - ((null nlist) (return result)) - (declare (list nlist)) - (if (%time> (car nlist) result) (setf result (car nlist))))) - -(defun time-min (number &rest more-numbers) - "Returns the least of its arguments." - (do ((nlist more-numbers (cdr nlist)) - (result number)) - ((null nlist) (return result)) - (declare (list nlist)) - (if (%time< (car nlist) result) (setf result (car nlist))))) - -(defun time-compare (time-a time-b) - (let ((mjd-a (time-mjd time-a)) - (mjd-b (time-mjd time-b)) - (sec-a (time-second time-a)) - (sec-b (time-second time-b))) - (if (= mjd-a mjd-b) - (if (= sec-a sec-b) - :equal - (if (< sec-a sec-b) - :less-than - :greater-than)) - (if (< mjd-a mjd-b) - :less-than - :greater-than)))) - - -;; ------------------------------------------------------------ -;; Formatting and output - -(defvar +decimal-printer+ #(#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9)) - -(defun db-timestring (time) - "return the string to store the given time in the database" - (declare (optimize (speed 3))) - (let ((output (copy-seq "'XXXX-XX-XX XX:XX:XX'"))) - (flet ((inscribe-base-10 (output offset size decimal) - (declare (type fixnum offset size decimal) - (type (simple-vector 10) +decimal-printer+)) - (dotimes (x size) - (declare (type fixnum x) - (optimize (safety 0))) - (multiple-value-bind (next this) - (floor decimal 10) - (setf (aref output (+ (- size x 1) offset)) - (aref +decimal-printer+ this)) - (setf decimal next))))) - (multiple-value-bind (second minute hour day month year) - (decode-time time) - (inscribe-base-10 output 1 4 year) - (inscribe-base-10 output 6 2 month) - (inscribe-base-10 output 9 2 day) - (inscribe-base-10 output 12 2 hour) - (inscribe-base-10 output 15 2 minute) - (inscribe-base-10 output 18 2 second) - output)))) - -(defun iso-timestring (time) - "return the string to store the given time in the database" - (declare (optimize (speed 3))) - (let ((output (copy-seq "XXXX-XX-XX XX:XX:XX"))) - (flet ((inscribe-base-10 (output offset size decimal) - (declare (type fixnum offset size decimal) - (type (simple-vector 10) +decimal-printer+)) - (dotimes (x size) - (declare (type fixnum x) - (optimize (safety 0))) - (multiple-value-bind (next this) - (floor decimal 10) - (setf (aref output (+ (- size x 1) offset)) - (aref +decimal-printer+ this)) - (setf decimal next))))) - (multiple-value-bind (second minute hour day month year) - (decode-time time) - (inscribe-base-10 output 0 4 year) - (inscribe-base-10 output 5 2 month) - (inscribe-base-10 output 8 2 day) - (inscribe-base-10 output 11 2 hour) - (inscribe-base-10 output 14 2 minute) - (inscribe-base-10 output 17 2 second) - output)))) - - -;; ------------------------------------------------------------ -;; Intervals - -(defstruct interval - (start nil) - (end nil) - (name nil) - (contained nil) - (type nil) - (data nil)) - -;; fix : should also return :contains / :contained - -(defun interval-relation (x y) - "Compare the relationship of node x to node y. Returns either -:contained :contains :follows :overlaps or :precedes." - (let ((xst (interval-start x)) - (xend (interval-end x)) - (yst (interval-start y)) - (yend (interval-end y))) - (case (time-compare xst yst) - (:equal - (case (time-compare xend yend) - (:less-than - :contained) - ((:equal :greater-than) - :contains))) - (:greater-than - (case (time-compare xst yend) - ((:equal :greater-than) - :follows) - (:less-than - (case (time-compare xend yend) - ((:less-than :equal) - :contained) - ((:greater-than) - :overlaps))))) - (:less-than - (case (time-compare xend yst) - ((:equal :less-than) - :precedes) - (:greater-than - (case (time-compare xend yend) - (:less-than - :overlaps) - ((:equal :greater-than) - :contains)))))))) - -;; ------------------------------------------------------------ -;; interval lists - -(defun sort-interval-list (list) - (sort list (lambda (x y) - (case (interval-relation x y) - ((:precedes :contains) t) - ((:follows :overlaps :contained) nil))))) - -;; interval push will return its list of intervals in strict order. -(defun interval-push (interval-list interval &optional container-rule) - (declare (ignore container-rule)) - (let ((sorted-list (sort-interval-list interval-list))) - (dotimes (x (length sorted-list)) - (let ((elt (nth x sorted-list))) - (case (interval-relation elt interval) - (:follows - (return-from interval-push (insert-at-index x sorted-list interval))) - (:contains - (return-from interval-push - (replace-at-index x sorted-list - (make-interval :start (interval-start elt) - :end (interval-end elt) - :type (interval-type elt) - :contained (interval-push (interval-contained elt) interval) - :data (interval-data elt))))) - ((:overlaps :contained) - (error "Overlap"))))) - (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))) - (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))) - (when (and (time<= (interval-start elt) time) - (time< time (interval-end elt))) - (if (interval-match (interval-contained elt) time) - (return-from interval-clear - (replace-at-index x list - (make-interval :start (interval-start elt) - :end (interval-end elt) - :type (interval-type elt) - :contained (interval-clear (interval-contained elt) time) - :data (interval-data elt)))) - (return-from interval-clear - (delete-at-index x list))))))) - -(defun interval-edit (list time start end &optional tag) - "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))) - (if (null list) nil - (dotimes (x (length list)) - (let ((elt (nth x list))) - (when (and (time<= (interval-start elt) time) - (time< time (interval-end elt))) - (or (interval-edit (interval-contained elt) time start end tag) - (cond ((and (< 0 x) - (time< start (interval-end (nth (1- x) list)))) - (error "Overlap of previous interval")) - ((and (< x (1- (length list))) - (time< (interval-start (nth (1+ x) list)) end)) - (error "~S ~S ~S ~S Overlap of next interval" x (length list) (interval-start (nth (1+ x) list)) end )) - ((time= (interval-start elt) time) - (return-from interval-edit - (replace-at-index x list - (make-interval :start start - :end end - :type (interval-type elt) - :contained (restrict-intervals (interval-contained elt) start end) - :data (or tag (interval-data elt)))))))))))))) - -(defun restrict-intervals (list start end &aux newlist) - (let ((test-interval (make-interval :start start :end end))) - (dolist (elt list) - (when (equal :contained - (interval-relation elt test-interval)) - (push elt newlist))) - (nreverse newlist))) - -;;; utils from odcl/list.lisp - -(defun replace-at-index (idx list elt) - (cond ((= idx 0) - (cons elt (cdr list))) - ((= idx (1- (length list))) - (append (butlast list) (list elt))) - (t - (append (subseq list 0 idx) - (list elt) - (subseq list (1+ idx)))))) - -(defun insert-at-index (idx list elt) - (cond ((= idx 0) - (cons elt list)) - ((= idx (1- (length list))) - (append list (list elt))) - (t - (append (subseq list 0 idx) - (list elt) - (subseq list idx))))) - -(defun delete-at-index (idx list) - (cond ((= idx 0) - (cdr list)) - ((= idx (1- (length list))) - (butlast list)) - (t - (append (subseq list 0 idx) - (subseq list (1+ idx)))))) - - -;; ------------------------------------------------------------ -;; return MJD for Gregorian date - -(defun gregorian-to-mjd (month day year) - (let ((b 0) - (month-adj month) - (year-adj (if (< year 0) - (+ year 1) - year)) - d - c) - (when (< month 3) - (incf month-adj 12) - (decf year-adj)) - (unless (or (< year 1582) - (and (= year 1582) - (or (< month 10) - (and (= month 10) - (< day 15))))) - (let ((a (floor (/ year-adj 100)))) - (setf b (+ (- 2 a) (floor (/ a 4)))))) - (if (< year-adj 0) - (setf c (floor (- (* 365.25d0 year-adj) 679006.75d0))) - (setf c (floor (- (* 365.25d0 year-adj) 679006d0)))) - (setf d (floor (* 30.6001 (+ 1 month-adj)))) - ;; (cmsg "b ~s c ~s d ~s day ~s" b c d day) - (+ b c d day))) - -;; convert MJD to Gregorian date - -(defun mjd-to-gregorian (mjd) - (let (z r g a b c year month day) - (setf z (floor (+ mjd 678882))) - (setf r (- (+ mjd 678882) z)) - (setf g (- z .25)) - (setf a (floor (/ g 36524.25))) - (setf b (- a (floor (/ a 4)))) - (setf year (floor (/ (+ b g) 365.25))) - (setf c (- (+ b z) (floor (* 365.25 year)))) - (setf month (truncate (/ (+ (* 5 c) 456) 153))) - (setf day (+ (- c (truncate (/ (- (* 153 month) 457) 5))) r)) - (when (> month 12) - (incf year) - (decf month 12)) - (list month day year))) - -(defun duration+ (time &rest durations) - "Add each DURATION to TIME, returning a new wall-time value." - (let ((year (duration-year time)) - (month (duration-month time)) - (day (duration-day time)) - (hour (duration-hour time)) - (minute (duration-minute time)) - (second (duration-second time))) - (dolist (duration durations) - (incf year (duration-year duration)) - (incf month (duration-month duration)) - (incf day (duration-day duration)) - (incf hour (duration-hour duration)) - (incf minute (duration-minute duration)) - (incf second (duration-second duration))) - (make-duration :year year :month month :day day :hour hour :minute minute - :second second))) - -(defun duration- (duration &rest durations) - "Subtract each DURATION from TIME, returning a new duration value." - (let ((year (duration-year duration)) - (month (duration-month duration)) - (day (duration-day duration)) - (hour (duration-hour duration)) - (minute (duration-minute duration)) - (second (duration-second duration))) - (dolist (duration durations) - (decf year (duration-year duration)) - (decf month (duration-month duration)) - (decf day (duration-day duration)) - (decf hour (duration-hour duration)) - (decf minute (duration-minute duration)) - (decf second (duration-second duration))) - (make-duration :year year :month month :day day :hour hour :minute minute - :second second))) - -;; Date + Duration - -(defun time+ (time &rest durations) - "Add each DURATION to TIME, returning a new wall-time value." - (let ((new-time (copy-time time))) - (dolist (duration durations) - (roll new-time - :year (duration-year duration) - :month (duration-month duration) - :day (duration-day duration) - :hour (duration-hour duration) - :minute (duration-minute duration) - :second (duration-second duration) - :destructive t)) - new-time)) - -(defun time- (time &rest durations) - "Subtract each DURATION from TIME, returning a new wall-time value." - (let ((new-time (copy-time time))) - (dolist (duration durations) - (roll new-time - :year (- (duration-year duration)) - :month (- (duration-month duration)) - :day (- (duration-day duration)) - :hour (- (duration-hour duration)) - :minute (- (duration-minute duration)) - :second (- (duration-second duration)) - :destructive t)) - new-time)) - -(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))) - (if (> day-diff 0) - (progn (decf day-diff) - (setf sec-diff (+ (time-second time2) - (- (* 60 60 24) - (time-second time1))))) - (setf sec-diff (- (time-second time2) - (time-second time1)))) - (make-duration :day day-diff - :second sec-diff)))) - (if (time< time1 time2) - (do-diff time1 time2) - (do-diff time2 time1)))) - -(defun format-time (stream time &key format - (date-separator "-") - (time-separator ":") - (internal-separator " ")) - "produces on stream the timestring corresponding to the wall-time -with the given options" - (let ((*print-circle* nil)) - (multiple-value-bind (second minute hour day month year dow) - (decode-time time) - (case format - (:pretty - (format stream "~A ~A, ~A ~D, ~D" - (pretty-time hour minute) - (day-name dow) - (month-name month) - day - year)) - (:short-pretty - (format stream "~A, ~D/~D/~D" - (pretty-time hour minute) - month day year)) - (:iso - (let ((string (iso-timestring time))) - (if stream - (write-string string stream) - string))) - (t - (format stream "~2,'0D~A~2,'0D~A~2,'0D~A~2,'0D~A~2,'0D~A~2,'0D" - year date-separator month date-separator day - internal-separator hour time-separator minute time-separator - second)))))) - -(defun pretty-time (hour minute) - (cond - ((eq hour 0) - (format nil "12:~2,'0D AM" minute)) - ((eq hour 12) - (format nil "12:~2,'0D PM" minute)) - ((< hour 12) - (format nil "~D:~2,'0D AM" hour minute)) - ((and (> hour 12) (< hour 24)) - (format nil "~D:~2,'0D PM" (- hour 12) minute)) - (t - (error "pretty-time got bad hour")))) - -(defun leap-days-in-days (days) - ;; return the number of leap days between Mar 1 2000 and - ;; (Mar 1 2000) + days, where days can be negative - (if (< days 0) - (ceiling (/ (- days) (* 365 4))) - (floor (/ days (* 365 4))))) - -(defun current-year () - (third (mjd-to-gregorian (time-mjd (get-time))))) - -(defun current-month () - (second (mjd-to-gregorian (time-mjd (get-time))))) - -(defun current-day () - (first (mjd-to-gregorian (time-mjd (get-time))))) - -(defun parse-date-time (string) - "parses date like 08/08/01, 8.8.2001, eg" - (when (> (length string) 1) - (let ((m (current-month)) - (d (current-day)) - (y (current-year))) - (let ((integers (mapcar #'parse-integer (hork-integers string)))) - (case (length integers) - (1 - (setf y (car integers))) - (2 - (setf m (car integers)) - (setf y (cadr integers))) - (3 - (setf m (car integers)) - (setf d (cadr integers)) - (setf y (caddr integers))) - (t - (return-from parse-date-time)))) - (when (< y 100) - (incf y 2000)) - (make-time :year y :month m :day d)))) - -(defun hork-integers (input) - (let ((output '()) - (start 0)) - (dotimes (x (length input)) - (unless (<= 48 (char-code (aref input x)) 57) - (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))) - -(defun time-meridian (hours) - (cond ((= hours 0) - (values 12 "AM")) - ((= hours 12) - (values 12 "PM")) - ((< 12 hours) - (values (- hours 12) "PM")) - (t - (values hours "AM")))) - -(defgeneric to-string (val &rest keys) - ) - -(defmethod to-string ((time wall-time) &rest keys) - (destructuring-bind (&key (style :daytime) &allow-other-keys) - keys - (print-date time style))) - -(defun print-date (time &optional (style :daytime)) - (multiple-value-bind (second minute hour day month year dow) - (decode-time time) - (declare (ignore second)) - (multiple-value-bind (hours meridian) - (time-meridian hour) - (ecase style - (:time-of-day - ;; 2:00 PM - (format nil "~d:~2,'0d ~a" hours minute meridian)) - (:long-day - ;; October 11th, 2000 - (format nil "~a ~d, ~d" (month-name month) day year)) - (:month - ;; October - (month-name month)) - (:month-year - ;; October 2000 - (format nil "~a ~d" (month-name month) year)) - (:full - ;; 11:08 AM, November 22, 2002 - (format nil "~d:~2,'0d ~a, ~a ~d, ~d" - hours minute meridian (month-name month) day year)) - (:full+weekday - ;; 11:09 AM Friday, November 22, 2002 - (format nil "~d:~2,'0d ~a ~a, ~a ~d, ~d" - hours minute meridian (nth dow *day-names*) - (month-name month) day year)) - (:daytime - ;; 11:09 AM, 11/22/2002 - (format-time nil time :format :short-pretty)) - (:day - ;; 11/22/2002 - (format nil "~d/~d/~d" month day year)))))) - -(defun time-element (time element) - (multiple-value-bind (second minute hour day month year dow) - (decode-time time) - (ecase element - (:seconds - second) - (:minutes - minute) - (:hours - hour) - (:day-of-month - day) - (:integer-day-of-week - dow) - (:day-of-week - (nth dow *day-keywords*)) - (:month - month) - (:year - year)))) - -(defun format-duration (stream duration &key (precision :minute)) - (let ((second (duration-second duration)) - (minute (duration-minute duration)) - (hour (duration-hour duration)) - (day (duration-day duration)) - (return (null stream)) - (stream (or stream (make-string-output-stream)))) - (ecase precision - (:day - (setf hour 0 second 0 minute 0)) - (:hour - (setf second 0 minute 0)) - (:minute - (setf second 0)) - (:second - t)) - (if (= 0 day hour minute) - (format stream "0 minutes") - (let ((sent? nil)) - (when (< 0 day) - (format stream "~d day~p" day day) - (setf sent? t)) - (when (< 0 hour) - (when sent? - (write-char #\Space stream)) - (format stream "~d hour~p" hour hour) - (setf sent? t)) - (when (< 0 minute) - (when sent? - (write-char #\Space stream)) - (format stream "~d min~p" minute minute) - (setf sent? t)) - (when (< 0 second) - (when sent? - (write-char #\Space stream)) - (format stream "~d sec~p" second second)))) - (when return - (get-output-stream-string stream)))) - -(defgeneric midnight (self)) -(defmethod midnight ((self wall-time)) - "truncate hours, minutes and seconds" - (%make-wall-time :mjd (time-mjd self))) - -(defun roll (date &key (year 0) (month 0) (day 0) (second 0) (hour 0) - (minute 0) (destructive nil)) - (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))))) - (let ((mjd (time-mjd date)) - (sec (time-second date))) - (multiple-value-bind (mjd-new sec-new) - (floor (+ sec second - (* 60 minute) - (* 60 60 hour)) (* 60 60 24)) - (if destructive - (progn - (setf (time-mjd date) (+ mjd mjd-new day) - (time-second date) sec-new) - date) - (%make-wall-time :mjd (+ mjd mjd-new day) - :second sec-new))))) - -(defun roll-to (date size position) - (ecase size - (:month - (ecase position - (:beginning - (roll date :day (+ 1 - (- (time-element date :day-of-month))))) - (:end - (roll date :day (+ (days-in-month (time-element date :month) - (time-element date :year)) - (- (time-element date :day-of-month))))))))) - -(defun week-containing (time) - (let* ((midn (midnight time)) - (dow (time-element midn :integer-day-of-week))) - (list (roll midn :day (- dow)) - (roll midn :day (- 7 dow))))) - -(defun leap-year? (year) - "t if YEAR is a leap yeap in the Gregorian calendar" - (and (= 0 (mod year 4)) - (or (not (= 0 (mod year 100))) - (= 0 (mod year 400))))) - -(defun valid-month-p (month) - "t if MONTH exists in the Gregorian calendar" - (<= 1 month 12)) - -(defun valid-gregorian-date-p (date) - "t if DATE (year month day) exists in the Gregorian calendar" - (let ((max-day (days-in-month (nth 1 date) (nth 0 date)))) - (<= 1 (nth 2 date) max-day))) - -(defun days-in-month (month year &key (careful t)) - "the number of days in MONTH of YEAR, observing Gregorian leap year -rules" - (declare (type fixnum month year)) - (when careful - (check-type month (satisfies valid-month-p) - "between 1 (January) and 12 (December)")) - (if (eql month 2) ; feb - (if (leap-year? year) - 29 28) - (let ((even (mod (1- month) 2))) - (if (< month 8) ; aug - (- 31 even) - (+ 30 even))))) - -(defun day-of-year (year month day &key (careful t)) - "the day number within the year of the date DATE. For example, -1987 1 1 returns 1" - (declare (type fixnum year month day)) - (when careful - (let ((date (list year month day))) - (check-type date (satisfies valid-gregorian-date-p) - "a valid Gregorian date"))) - (let ((doy (+ day (* 31 (1- month))))) - (declare (type fixnum doy)) - (when (< 2 month) - (setf doy (- doy (floor (+ 23 (* 4 month)) 10))) - (when (leap-year? year) - (incf doy))) - doy)) - -(defun parse-yearstring (string) - (let ((year (or (parse-integer-insensitively string) - (extract-roman string)))) - (when (and year (< 1500 year 2500)) - (make-time :year year)))) - -(defun parse-integer-insensitively (string) - (let ((start (position-if #'digit-char-p string)) - (end (position-if #'digit-char-p string :from-end t))) - (when (and start end) - (parse-integer (subseq string start (1+ end)) :junk-allowed t)))) - -(defvar *roman-digits* - '((#\M . 1000) - (#\D . 500) - (#\C . 100) - (#\L . 50) - (#\X . 10) - (#\V . 5) - (#\I . 1))) - -(defun extract-roman (string &aux parse) - (dotimes (x (length string)) - (let ((val (cdr (assoc (aref string x) *roman-digits*)))) - (when (and val parse (< (car parse) val)) - (push (- (pop parse)) parse)) - (push val parse))) - (apply #'+ parse)) - - -;; ------------------------------------------------------------ -;; Parsing iso-8601 timestrings - -(define-condition iso-8601-syntax-error (error) - ((bad-component;; year, month whatever - :initarg :bad-component - :reader bad-component))) - -(defun parse-timestring (timestring &key (start 0) end junk-allowed) - "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)) ;; FIXME - (let ((string (subseq timestring start end))) - (if (char= (aref string 0) #\P) - (parse-iso-8601-duration string) - (parse-iso-8601-time string)))) - -(defvar *iso-8601-duration-delimiters* - '((#\D . :days) - (#\H . :hours) - (#\M . :minutes) - (#\S . :seconds))) - -(defun iso-8601-delimiter (elt) - (cdr (assoc elt *iso-8601-duration-delimiters*))) - -(defun iso-8601-duration-subseq (string start) - (let* ((pos (position-if #'iso-8601-delimiter string :start start)) - (number (when pos (parse-integer (subseq string start pos) - :junk-allowed t)))) - (when number - (values number - (1+ pos) - (iso-8601-delimiter (aref string pos)))))) - -(defun parse-iso-8601-duration (string) - "return a wall-time from a duration string" - (block parse - (let ((days 0) (secs 0) (hours 0) (minutes 0) (index 1)) - (loop - (multiple-value-bind (duration next-index duration-type) - (iso-8601-duration-subseq string index) - (case duration-type - (:hours - (incf hours duration)) - (:minutes - (incf minutes duration)) - (:seconds - (incf secs duration)) - (:days - (incf days duration)) - (t - (return-from parse (make-duration :day days :hour hours - :minute minutes :second secs)))) - (setf index next-index)))))) - -;; e.g. 2000-11-11 00:00:00-06 - -(defun parse-iso-8601-time (string) - "return the wall-time corresponding to the given ISO 8601 datestring" - (multiple-value-bind (year month day hour minute second offset) - (syntax-parse-iso-8601 string) - (make-time :year year - :month month - :day day - :hour hour - :minute minute - :second second - :offset offset))) - - -(defun syntax-parse-iso-8601 (string) - (let (year month day hour minute second gmt-sec-offset) - (handler-case - (progn - (setf year (parse-integer (subseq string 0 4)) - month (parse-integer (subseq string 5 7)) - day (parse-integer (subseq string 8 10)) - hour (if (<= 13 (length string)) - (parse-integer (subseq string 11 13)) - 0) - minute (if (<= 16 (length string)) - (parse-integer (subseq string 14 16)) - 0) - second (if (<= 19 (length string)) - (parse-integer (subseq string 17 19)) - 0) - gmt-sec-offset (if (<= 22 (length string)) - (* 60 60 - (parse-integer (subseq string 19 22))) - 0)) - (unless (< 0 year) - (error 'iso-8601-syntax-error - :bad-component '(year . 0))) - (unless (< 0 month) - (error 'iso-8601-syntax-error - :bad-component '(month . 0))) - (unless (< 0 day) - (error 'iso-8601-syntax-error - :bad-component '(month . 0))) - (values year month day hour minute second gmt-sec-offset)) - (simple-error () - (error 'iso-8601-syntax-error - :bad-component - (car (find-if (lambda (pair) (null (cdr pair))) - `((year . ,year) (month . ,month) - (day . ,day) (hour ,hour) - (minute ,minute) (second ,second) - (timezone ,gmt-sec-offset)))))))))