source: release/1.10/cocoa-ide-contrib/krueger/InterfaceProjects/Utilities/date.lisp

Last change on this file was 16179, checked in by R. Matthew Emerson, 10 years ago

Merge from trunk.

  • Property svn:executable set to *
File size: 13.7 KB
RevLine 
[13390]1;; date.lisp
2
3#|
4The MIT license.
5
[15808]6Copyright (c) 2013 Paul L. Krueger
[13390]7
8Permission is hereby granted, free of charge, to any person obtaining a copy of this software
9and associated documentation files (the "Software"), to deal in the Software without restriction,
10including without limitation the rights to use, copy, modify, merge, publish, distribute,
11sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
12furnished to do so, subject to the following conditions:
13
14The above copyright notice and this permission notice shall be included in all copies or substantial
15portions of the Software.
16
17THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
18LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
23|#
[15808]24(eval-when (:compile-toplevel :load-toplevel :execute)
25 ;; (require :tagged-dates) See note at end of this file if you are using this code on a CCL before 1.9 released
26 (require :iu-classes))
[13390]27
28(in-package :iu)
29
30;;; some general date utility routines
31
32(defun now ()
33 (get-universal-time))
34
[16077]35(defun today-date ()
36 (multiple-value-bind (sec min hr dd mm yy day dst zone)
37 (decode-universal-time (get-universal-time))
38 (declare (ignore sec min hr day dst))
39 (encode-universal-time 0 0 12 dd mm yy zone)))
40
[13390]41(defun dt (mm dd yy &optional (hour 12) (min 0) (sec 0) (zone nil))
42 ;; yy can be 2 or 4 digit year
[15808]43 ;; any dd greater than the end of the month is reduced to the last day of the month
[16179]44 ;; To avoid complications associated with daylight savings time, we use 12 noon as
45 ;; the time if not specified.
[15808]46 (let ((max-dd (if (and (eql mm 2)
47 (not (logtest yy 3))
48 (or (eql 0 (mod yy 400))
49 (not (eql 0 (mod yy 100)))))
50 29
51 (svref #(31 28 31 30 31 30 31 31 30 31 30 31) (1- mm)))))
52 (if zone
53 (encode-universal-time sec min hour (min max-dd dd) mm yy zone)
54 (encode-universal-time sec min hour (min max-dd dd) mm yy))))
[13390]55
56(defun dt-diff (dt1 dt2)
57 ;; computes the number of days between the two dates
58 (round (abs (- dt1 dt2)) 86400))
59
60(defun day-of-wk (dt)
61 (multiple-value-bind (sec min hr dd mm yr day dst zone)
62 (decode-universal-time dt)
63 (declare (ignore sec min hr dd mm yr dst zone))
64 (nth day '(monday tuesday wednesday thursday friday saturday sunday))))
65
66(defun abbrev-day-of-wk (dt)
67 (multiple-value-bind (sec min hr dd mm yr day dst zone)
68 (decode-universal-time dt)
69 (declare (ignore sec min hr dd mm yr dst zone))
70 (nth day '(mon tue wed thu fri sat sun))))
71
72(defun day-char (dt)
73 (multiple-value-bind (sec min hr dd mm yr day dst zone)
74 (decode-universal-time dt)
75 (declare (ignore sec min hr dd mm yr dst zone))
76 (nth day '(m t w t f s s))))
77
78(defun next-day (dt)
79 (+ 86400 dt))
80
81(defun prev-day (dt)
82 (- dt 86400))
83
[15808]84(defun inc-date (dt num interval-type)
85 ;; interval-type should be one of :day :month :quarter :year
86 (case interval-type
87 (:day (inc-days dt num))
88 (:month (inc-months dt num))
89 (:quarter (inc-months dt (* 3 num)))
90 (:year (inc-years dt num))))
91
92(defun inc-days (dt num-dd)
93 (+ (* 86400 num-dd) dt))
94
[13390]95(defun inc-months (dt num-mm)
96 (multiple-value-bind (sec min hr dd mm yr day dst zone)
97 (decode-universal-time dt)
98 (declare (ignore day dst))
99 (multiple-value-bind (yy-inc new-mm)
100 (floor (+ num-mm mm -1) 12)
[15808]101 (dt (1+ new-mm) dd (+ yr yy-inc) hr min sec zone))))
102
[13390]103(defun next-month (dt)
104 (inc-months dt 1))
105
106(defun prev-month (dt)
107 (inc-months dt -1))
108
109(defun inc-years (dt num-yy)
110 (multiple-value-bind (sec min hr dd mm yr day dst zone)
111 (decode-universal-time dt)
112 (declare (ignore day dst))
[15808]113 (dt mm dd (+ yr num-yy) hr min sec zone)))
[13390]114
115(defun next-year (dt)
116 (inc-years dt 1))
117
118(defun prev-year (dt)
119 (inc-years dt -1))
120
[15808]121(defun last-dt-of-month (mm yy)
122 (dt mm 31 yy))
123
[13390]124(defun same-day-p (dt1 dt2)
125 (multiple-value-bind (sec min hr dd1 mm1 yr1 day dst zone)
126 (decode-universal-time dt1)
127 (declare (ignore sec min hr day dst zone))
128 (multiple-value-bind (sec min hr dd2 mm2 yr2 day dst zone)
129 (decode-universal-time dt2)
130 (declare (ignore sec min hr day dst zone))
131 (and (eql dd1 dd2) (eql mm1 mm2) (eql yr1 yr2)))))
132
133(defun days-to-sec (num-days)
134 ;; convert a number of days to seconds, which can be added/subtracted
135 ;; from a date to get a new date
136 (* num-days 86400))
137
138(defmacro do-dates ((dt start end &optional (return-form nil ret-p)) &rest forms)
139 `(do* ((,dt ,start (+ ,dt 86400)))
140 ((> ,dt ,end) (if ,ret-p ,return-form (values)))
141 ,@forms))
142
143(defmacro do-interval-dates ((dt start end interval &optional (return-form nil ret-p)) &rest forms)
144 `(do* ((,dt ,start (+ ,dt (days-to-sec ,interval))))
145 ((> ,dt ,end) (if ,ret-p ,return-form (values)))
146 ,@forms))
147
148(defmacro do-months ((dt start end mm-interval &optional (return-form nil ret-p)) &rest forms)
149 `(do* ((,dt ,start (inc-months ,dt ,mm-interval)))
150 ((> ,dt ,end) (if ,ret-p ,return-form (values)))
151 ,@forms))
152
153(defun days-between (dt1 dt2)
154 (round (abs (- dt2 dt1)) 86400))
155
[15808]156(defun months-between (dt1 dt2)
157 ;; computes a floating point value of months where the fraction of a month is
158 ;; computed using 7 days as .25 of 1 month
159 ;; round the result if you want an integer
160 ;; This has some use if you want to treat each month as a constant span on a graph
161 ;; or something like that. For example (iu::months-between (iu::dt 2 3 2012) (iu::dt 4 3 2012))
162 ;; will return 2.0.
163 (multiple-value-bind (sec min hr dd1 mm1 yr1 day dst zone)
164 (decode-universal-time dt1)
165 (declare (ignore sec min hr day dst zone))
166 (multiple-value-bind (sec min hr dd2 mm2 yr2 day dst zone)
167 (decode-universal-time dt2)
168 (declare (ignore sec min hr day dst zone))
169 (float (abs (+ (* 12 (- yr2 yr1)) (- mm2 mm1) (/ (- dd2 dd1) 28)))))))
170
171(defun years-between (dt1 dt2)
172 ;; computes a floating point value of years where the fraction of a year is
173 ;; computed treating each month as 1/12 of a year and each day as 1/365 of 1 year
174 ;; round the result if you want an integer
175 (multiple-value-bind (sec min hr dd1 mm1 yr1 day dst zone)
176 (decode-universal-time dt1)
177 (declare (ignore sec min hr day dst zone))
178 (multiple-value-bind (sec min hr dd2 mm2 yr2 day dst zone)
179 (decode-universal-time dt2)
180 (declare (ignore sec min hr day dst zone))
181 (float (abs (+ (- yr2 yr1) (/ (- mm2 mm1) 12) (/ (- dd2 dd1) 365)))))))
182
[13390]183(defmethod days-from ((dt1 integer) (dt2 integer))
184 (days-between dt1 dt2))
185
186(defmethod days-from ((dt integer) (day symbol))
187 (let* ((abbrev-days '(sun mon tue wed thu fri sat))
188 (days '(sunday monday tuesday wednesday thursday friday saturday))
189 (dow (abbrev-day-of-wk dt))
190 (today (position dow abbrev-days))
191 (day-pos (or (position day days)
192 (position day abbrev-days))))
193 (if (<= today day-pos)
194 (- day-pos today)
195 (- 7 (- today day-pos)))))
196
197(defmethod days-from ((day symbol) (dt integer))
198 (let* ((abbrev-days '(sun mon tue wed thu fri sat))
199 (days '(sunday monday tuesday wednesday thursday friday saturday))
200 (dow (abbrev-day-of-wk dt))
201 (day-pos (position dow abbrev-days))
202 (today (or (position day days)
203 (position day abbrev-days))))
204 (if (<= today day-pos)
205 (- day-pos today)
206 (- 7 (- today day-pos)))))
207
208(defmethod days-from ((day1 symbol) (day2 symbol))
209 (let* ((abbrev-days '(sun mon tue wed thu fri sat))
210 (days '(sunday monday tuesday wednesday thursday friday saturday))
211 (today (or (position day1 days)
212 (position day1 abbrev-days)))
213 (day-pos (or (position day2 days)
214 (position day2 abbrev-days))))
215 (if (<= today day-pos)
216 (- day-pos today)
217 (- 7 (- today day-pos)))))
218
219(defun +days (dt days)
220 (+ dt (days-to-sec days)))
221
222(defun date-string (dt)
223 (multiple-value-bind (sec min hr dd mm yr day dst zone)
224 (decode-universal-time dt)
225 (declare (ignore sec min hr dst zone))
226 (format nil
[14632]227 "~9a ~2,'0d/~2,'0d/~2,'0d"
228 (nth day '("Monday" "Tuesday" "Wednesday" "Thursday" "Friday" "Saturday" "Sunday"))
[13390]229 mm dd (mod yr 100))))
230
231(defun short-date-string (dt)
232 (multiple-value-bind (sec min hr dd mm yr day dst zone)
233 (decode-universal-time dt)
234 (declare (ignore sec min hr day dst zone))
235 (format nil
236 "~2,'0d/~2,'0d/~2,'0d" mm dd (mod yr 100))))
237
[15808]238(defun short-time-string (dt)
239 (multiple-value-bind (sec min hr dd mm yr day dst zone)
240 (decode-universal-time dt)
241 (declare (ignore sec dd mm yr day dst zone))
242 (format nil
243 "~2,'0d:~2,'0d" hr min)))
244
245(defun time-string (dt)
246 (multiple-value-bind (sec min hr dd mm yr day dst zone)
247 (decode-universal-time dt)
248 (declare (ignore dd mm yr day dst zone))
249 (format nil
250 "~2,'0d:~2,'0d:~2,'0d on ~a" hr min sec (date-string dt))))
251
[13390]252(defun mmdd-string (dt)
253 (multiple-value-bind (sec min hr dd mm yr day dst zone)
254 (decode-universal-time dt)
255 (declare (ignore sec min hr yr day dst zone))
256 (format nil
257 "~2,'0d/~2,'0d" mm dd)))
258
[15808]259(defun mmyy-string (dt)
260 (multiple-value-bind (sec min hr dd mm yr day dst zone)
261 (decode-universal-time dt)
262 (declare (ignore sec min hr dd day dst zone))
263 (format nil
264 "~2,'0d/~2,'0d" mm (mod yr 100))))
265
266(defun yr-string (dt)
267 (multiple-value-bind (sec min hr dd mm yr day dst zone)
268 (decode-universal-time dt)
269 (declare (ignore sec min hr dd mm day dst zone))
270 (format nil
271 "~4,'0d" yr)))
272
[13390]273(defun mmddyy-list (dt)
274 (multiple-value-bind (sec min hr dd mm yr day dst zone)
275 (decode-universal-time dt)
276 (declare (ignore sec min hr day dst zone))
277 (list mm dd yr)))
278
279(defun dt-yr (dt)
280 (multiple-value-bind (sec min hr dd mm yr day dst zone)
281 (decode-universal-time dt)
282 (declare (ignore sec min hr dd mm day dst zone))
283 yr))
284
285(defun date-list (strt end)
286 (do* ((res (list strt) (cons next res))
287 (next (next-day strt) (next-day next)))
288 ((> next end) (nreverse res))))
289
290;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
291
292;; utility functions supporting the use of an integer as a set days of
293;; the week. The low-order 7 bits of the integer represent sun thru sat
294;; Bit 0 = sun ... Bit 6 = sat.
295
296(defun day-set-bit (day)
297 (or (position day '(sun mon tue wed thu fri sat))
298 (position day '(sunday monday tuesday wednesday thursday friday saturday))))
299
300(defun add-day (day day-set)
301 (dpb 1 (byte 1 (day-set-bit day)) day-set))
302
303(defun remove-day (day day-set)
304 (dpb 0 (byte 1 (day-set-bit day)) day-set))
305
306(defun has-day-p (day-set day)
307 (logbitp (day-set-bit day) day-set))
308
309(defun in-dayset-p (dt day-set)
310 (has-day-p day-set (abbrev-day-of-wk dt)))
311
312(defun day-set (&rest days)
313 (let ((ds 0))
314 (dolist (day days ds)
315 (setf ds (dpb 1 (byte 1 (day-set-bit day)) ds)))))
316
317(defun num-days-in-dayset (dayset strt end)
318 (count-if #'(lambda (dt)
319 (in-dayset-p dt dayset))
320 (date-list strt end)))
321
322(defun random-date (begin-dt end-dt)
323 (+ begin-dt (random (- end-dt begin-dt))))
324
[15808]325(defun intl-date-string (dt)
[13390]326 ;; A string that specifies a date and time value in the international string representation format—
327 ;; YYYY-MM-DD HH:MM:SS ±HHMM, where ±HHMM is a time zone offset in hours and minutes from GMT
[15808]328 ;; (for example, “2001-03-24 10:45:32 -0600” is a date in the USA central time zone).
[13390]329 (multiple-value-bind (sec min hr dd mm yr day dst zone)
330 (decode-universal-time dt)
331 (declare (ignore dst day))
332 (format nil "~4d-~2,'0d-~2,'0d ~2,'0d:~2,'0d:~2,'0d ~a~4,'0d"
333 yr
334 mm
335 dd
336 hr
337 min
338 sec
[15808]339 (if (plusp zone) "-" "+" ) ;; lisp zone offset is opposite from typical universal formats
340 (multiple-value-bind (h frac) (floor (abs zone))
341 (floor (+ (* h 100) (* frac 60)))))))
[13390]342
343;; The reference used for NSDate objects is 01/01/2001
344;; This represents the difference between lisp's reference date and NSDate's
345(defconstant $ns-lisp-ref-date-diff$ (dt 01 01 2001 0 0 0 0))
346
347(defun lisp-to-ns-date (dt)
348 (#/dateWithTimeIntervalSinceReferenceDate:
349 ns:ns-date
[15808]350 (coerce (- (or dt (now)) $ns-lisp-ref-date-diff$) 'double-float)))
[13390]351
[15808]352
[13390]353(defun ns-to-lisp-date (ns-date)
354 (+ (round (#/timeIntervalSinceReferenceDate ns-date)) $ns-lisp-ref-date-diff$))
355
[15808]356(defun string-to-ns-date (str)
357 (let ((obj (#/dateWithNaturalLanguageString: ns:ns-date
358 (iu::lisp-to-temp-nsstring str))))
359 (if (eql obj (%null-ptr))
360 (error "~s is not a suitable date string" str)
361 obj)))
362
363(defun string-to-date (str)
364 ;; make use of Cocoa's date construction method
365 (ns-to-lisp-date (string-to-ns-date str)))
366
367;; OSX is now using tagged dates that CCL doesn't know about before the released version of 1.9. So if you are running
368;; something less than this, you must inform CCL about these objects. Do (require :tagged-dates) and then make the call below
369;; when this is loaded.
370
371;; (ccl::setup-class-info-for-tagged-instance (lisp-to-ns-date (now)))
372
[14632]373(provide :date)
Note: See TracBrowser for help on using the repository browser.