Work to add UTC tracking to wall-times
[clsql.git] / tests / test-time.lisp
1 ;;; -*- Mode: Lisp -*-
2 ;;;
3 ;;; Copyright (c) 2000, 2001 onShore Development, Inc.
4 ;;;
5 ;;; Test time functions (time.lisp)
6
7 (in-package #:clsql-tests)
8 (clsql-sys:file-enable-sql-reader-syntax)
9
10 (def-view-class datetest ()
11   ((id :column "id"
12                 :type integer
13                 :db-kind :key
14                 :db-constraints (:not-null :unique)
15                 :accessor id :initarg :id
16                 :initform nil
17                 :db-type "int4")
18    (testtimetz :column "testtimetz"
19                 :type clsql-sys:wall-time
20                 :db-kind :base
21                 :db-constraints nil
22                 :accessor testtimetz :initarg :testtimetz
23                 :initform nil
24                 :db-type "timestamp with time zone")
25    (testtime :column "testtime"
26              :type clsql-sys:wall-time
27              :db-kind :base
28              :db-constraints nil
29              :accessor testtime :initarg :testtime
30              :initform nil
31              :db-type "timestamp without time zone")))
32
33 (def-dataset *ds-datetest*
34   (:setup (lambda () (clsql-sys:create-view-from-class 'datetest)))
35   (:cleanup "DROP TABLE datetest"))
36
37
38 (def-dataset *cross-platform-datetest*
39   (:setup (lambda () (create-table [datetest]
40                                    '(([testtime] wall-time)))))
41   (:cleanup (lambda ()
42               (drop-table [datetest]))))
43
44
45 (setq *rt-time*
46       '(
47
48 ;; we use parse timestring a lot through here verifying other things
49 ;; start off just checking that.
50 (deftest :time/iso-parse/0
51     (let* ((time1 (parse-timestring "2010-01-23")))
52       (decode-time time1))
53   0 0 0 0 23 1 2010 6 nil)
54
55 (deftest :time/iso-parse/1
56     (let* ((time1 (parse-timestring "2010-01-23T14:56:32Z")))
57       (decode-time time1))
58  0 32 56 14 23 1 2010 6 T)
59
60 (deftest :time/iso-parse/2
61     (let* ((time1 (parse-timestring "2008-02-29 12:46:32")))
62       (decode-time time1))
63   0 32 46 12 29 2 2008 5 nil)
64
65 (deftest :time/iso-parse/3
66     (let* ((time1 (parse-timestring "2010-01-23 14:56:32.44")))
67       (decode-time time1))
68   440000 32 56 14 23 1 2010 6 nil)
69
70 (deftest :time/iso-parse/4
71     (let* ((time1 (parse-timestring "2010-01-23 14:56:32.0044")))
72       (decode-time time1))
73   4400 32 56 14 23 1 2010 6 nil)
74
75 (deftest :time/iso-parse/5
76     (let* ((time1 (parse-timestring "2010-01-23 14:56:32.000003")))
77       (decode-time time1))
78  3 32 56 14 23 1 2010 6 nil)
79
80 (deftest :time/iso-parse/6
81     (let* ((time1 (parse-timestring "2010-01-23T14:56:32-05")))
82       (decode-time time1))
83  0 32 56 19 23 1 2010 6 t)
84
85 (deftest :time/iso-parse/7
86     (let* ((time1 (parse-timestring "2010-01-23T14:56:32-05")))
87       (decode-time time1))
88  0 32 56 19 23 1 2010 6 t)
89
90 (deftest :time/iso-parse/8
91     (let* ((time1 (parse-timestring "2010-01-23T14:56:32-05:30")))
92       (decode-time time1))
93   0 32 26 20 23 1 2010 6 t)
94
95 (deftest :time/print-parse/1
96     ;;make sure when we print and parse we get the same time.
97     (let* ((time (clsql-sys:make-time :year 2010 :month 1 :day 4
98                                       :hour 14 :minute 15 :second 44))
99            (string-time (iso-timestring time))
100            (time2 (parse-timestring string-time)))
101       (decode-time time2))
102   0 44 15 14 4 1 2010 1 nil)
103
104 (deftest :time/print-parse/2
105     ;;make sure when we print and parse we get the same time.
106     (let* ((time (clsql-sys:make-time :year 2010 :month 1 :day 4
107                                       :hour 14 :minute 15 :second 44 :usec 3))
108            (string-time (iso-timestring time))
109            (time2 (parse-timestring string-time)))
110       (decode-time time2))
111   3 44 15 14 4 1 2010 1 nil)
112
113
114 ;; relations of intervals
115 (deftest :time/1
116     (let* ((time-1 (clsql:parse-timestring "2002-01-01 10:00:00"))
117            (time-2 (clsql:parse-timestring "2002-01-01 11:00:00"))
118            (time-3 (clsql:parse-timestring "2002-01-01 12:00:00"))
119            (time-4 (clsql:parse-timestring "2002-01-01 13:00:00"))
120            (interval-1 (clsql:make-interval :start time-1 :end time-2))
121            (interval-2 (clsql:make-interval :start time-2 :end time-3))
122            (interval-3 (clsql:make-interval :start time-3 :end time-4))
123            (interval-4 (clsql:make-interval :start time-1 :end time-3))
124            (interval-5 (clsql:make-interval :start time-2 :end time-4))
125            (interval-6 (clsql:make-interval :start time-1 :end time-4)))
126       (flet ((my-assert (number relation i1 i2)
127                (declare (ignore number))
128                (let ((found-relation (clsql:interval-relation i1 i2)))
129                  (equal relation found-relation))))
130         (and
131          (my-assert 1 :contains interval-1 interval-1)
132          (my-assert 2 :precedes interval-1 interval-2)
133          (my-assert 3 :precedes interval-1 interval-3)
134          (my-assert 4 :contained interval-1 interval-4)
135          (my-assert 5 :precedes interval-1 interval-5)
136          (my-assert 6 :contained interval-1 interval-6)
137          (my-assert 7 :follows interval-2 interval-1)
138          (my-assert 8 :contains interval-2 interval-2)
139          (my-assert 9 :precedes interval-2 interval-3)
140          (my-assert 10 :contained interval-2 interval-4)
141          (my-assert 11 :contained interval-2 interval-5)
142          (my-assert 12 :contained interval-2 interval-6)
143          (my-assert 13 :follows interval-3 interval-1)
144          (my-assert 14 :follows interval-3 interval-2)
145          (my-assert 15 :contains interval-3 interval-3)
146          (my-assert 16 :follows interval-3 interval-4)
147          (my-assert 17 :contained interval-3 interval-5)
148          (my-assert 18 :contained interval-3 interval-6)
149          (my-assert 19 :contains interval-4 interval-1)
150          (my-assert 20 :contains interval-4 interval-2)
151          (my-assert 21 :precedes interval-4 interval-3)
152          (my-assert 22 :contains interval-4 interval-4)
153          (my-assert 23 :overlaps interval-4 interval-5)
154          (my-assert 24 :contained interval-4 interval-6)
155          (my-assert 25 :follows interval-5 interval-1)
156          (my-assert 26 :contains interval-5 interval-2)
157          (my-assert 27 :contains interval-5 interval-3)
158          (my-assert 28 :overlaps interval-5 interval-4)
159          (my-assert 29 :contains interval-5 interval-5)
160          (my-assert 30 :contained interval-5 interval-6)
161          (my-assert 31 :contains interval-6 interval-1)
162          (my-assert 32 :contains interval-6 interval-2)
163          (my-assert 33 :contains interval-6 interval-3)
164          (my-assert 34 :contains interval-6 interval-4)
165          (my-assert 35 :contains interval-6 interval-5)
166          (my-assert 36 :contains interval-6 interval-6))))
167   t)
168
169 ;; adjacent intervals in list
170 (deftest :time/2
171   (let* ((interval-list nil)
172          (time-1 (clsql:parse-timestring "2002-01-01 10:00:00"))
173          (time-3 (clsql:parse-timestring "2002-01-01 12:00:00"))
174          (time-4 (clsql:parse-timestring "2002-01-01 13:00:00")))
175     (setf interval-list
176           (clsql:interval-push interval-list (clsql:make-interval :start time-1 :end time-3
177                                                       :type :open)))
178     (setf interval-list
179           (clsql:interval-push interval-list (clsql:make-interval :start time-3 :end time-4
180                                                       :type :open)))
181     (clsql:interval-relation (car interval-list) (cadr interval-list)))
182   :precedes)
183
184 ;; nested intervals in list
185 (deftest :time/3
186     (let* ((interval-list nil)
187            (time-1 (clsql:parse-timestring "2002-01-01 10:00:00"))
188            (time-2 (clsql:parse-timestring "2002-01-01 11:00:00"))
189            (time-3 (clsql:parse-timestring "2002-01-01 12:00:00"))
190            (time-4 (clsql:parse-timestring "2002-01-01 13:00:00")))
191       (setf interval-list
192             (clsql:interval-push interval-list (clsql:make-interval :start time-1
193                                                         :end time-4
194                                                         :type :open)))
195       (setf interval-list
196             (clsql:interval-push interval-list (clsql:make-interval :start time-2
197                                                         :end time-3
198                                                         :type :closed)))
199       (let* ((interval (car interval-list))
200              (interval-contained
201               (when interval (car (clsql:interval-contained interval)))))
202         (when (and interval interval-contained)
203           (and (clsql:time= (clsql:interval-start interval) time-1)
204                (clsql:time= (clsql:interval-end interval) time-4)
205                (eq (clsql:interval-type interval) :open)
206                (clsql:time= (clsql:interval-start interval-contained) time-2)
207                (clsql:time= (clsql:interval-end interval-contained) time-3)
208                (eq (clsql:interval-type interval-contained) :closed)))))
209   t)
210
211 ;; interval-edit - nonoverlapping
212 (deftest :time/4
213     (let* ((interval-list nil)
214            (time-1 (clsql:parse-timestring "2002-01-01 10:00:00"))
215            (time-2 (clsql:parse-timestring "2002-01-01 11:00:00"))
216            (time-3 (clsql:parse-timestring "2002-01-01 12:00:00"))
217            (time-4 (clsql:parse-timestring "2002-01-01 13:00:00")))
218       (setf interval-list (clsql:interval-push interval-list (clsql:make-interval :start time-1 :end time-2 :type :open)))
219       (setf interval-list (clsql:interval-push interval-list (clsql:make-interval :start time-3 :end time-4 :type :closed)))
220       (setf interval-list (clsql:interval-edit interval-list time-1 time-1 time-3))
221       ;; should be time-3 not time-2
222       (clsql:time= (clsql:interval-end (car interval-list)) time-3))
223   t)
224
225 ;; interval-edit - overlapping
226 (deftest :time/5
227     (let* ((interval-list nil)
228            (time-1 (clsql:parse-timestring "2002-01-01 10:00:00"))
229            (time-2 (clsql:parse-timestring "2002-01-01 11:00:00"))
230            (time-3 (clsql:parse-timestring "2002-01-01 12:00:00"))
231            (time-4 (clsql:parse-timestring "2002-01-01 13:00:00")))
232       (setf interval-list (clsql:interval-push interval-list (clsql:make-interval :start time-1 :end time-2 :type :open)))
233       (setf interval-list (clsql:interval-push interval-list (clsql:make-interval :start time-2 :end time-4 :type :closed)))
234       (let ((pass t))
235         (handler-case
236             (progn
237               (setf interval-list
238                     (clsql:interval-edit interval-list time-1 time-1 time-3))
239               (setf pass nil))
240           (error nil))
241         pass))
242   t)
243
244 ;; interval-edit - nested intervals in list
245 (deftest :time/6
246     (let* ((interval-list nil)
247            (time-1 (clsql:parse-timestring "2002-01-01 10:00:00"))
248            (time-2 (clsql:parse-timestring "2002-01-01 11:00:00"))
249            (time-3 (clsql:parse-timestring "2002-01-01 12:00:00"))
250            (time-4 (clsql:parse-timestring "2002-01-01 13:00:00"))
251            (time-5 (clsql:parse-timestring "2002-01-01 14:00:00"))
252            (time-6 (clsql:parse-timestring "2002-01-01 15:00:00")))
253       (setf interval-list (clsql:interval-push interval-list (clsql:make-interval :start time-1 :end time-6 :type :open)))
254       (setf interval-list (clsql:interval-push interval-list (clsql:make-interval :start time-2 :end time-3 :type :closed)))
255       (setf interval-list (clsql:interval-push interval-list (clsql:make-interval :start time-4 :end time-5 :type :closed)))
256       (setf interval-list (clsql:interval-edit interval-list time-1 time-1 time-4))
257       ;; should be time-4 not time-6
258       (clsql:time= (clsql:interval-end (car interval-list)) time-4))
259   t)
260
261 ;; Test the boundaries of Local Time with granularity of 1 year
262 (deftest :time/7
263     (let ((sec-in-year (* 60 60 24 365))
264           (year (clsql:time-element (clsql:make-time) :year)))
265       (dotimes (n 50 n)
266         (let ((date (clsql:make-time :second (* n sec-in-year))))
267           (unless (= (+ year n)
268                      (clsql:time-element date :year))
269             (return n)))))
270   50)
271
272 ;; Test db-timestring
273 (deftest :time/9
274     (flet ((grab-year (dbstring)
275              (parse-integer (subseq dbstring 1 5))))
276       (let ((second-in-year (* 60 60 24 365)))
277         (dotimes (n 2000 n)
278           (let* ((second (* -1 n second-in-year))
279                  (date (clsql:make-time :year 2525 :second second)))
280             (unless
281                 (= (grab-year (clsql:db-timestring date))
282                    (clsql:time-element date :year))
283               (return n))))))
284   2000)
285
286 ;; Conversion between MJD and Gregorian
287 (deftest :time/10
288     (dotimes (base 10000 base)
289       (unless (= (apply #'clsql:gregorian-to-mjd (clsql:mjd-to-gregorian base))
290                  base)
291         (return base)))
292   10000)
293
294 ;; Clsql:Roll by minutes: +90
295 (deftest :time/11
296     (let ((now (clsql:get-time)))
297       (clsql:time= (clsql:time+ now (clsql:make-duration :minute 90))
298              (clsql:roll now :minute 90)))
299   t)
300
301 ;;Clsql:Roll by minutes: +900
302 (deftest :time/12
303     (let ((now (clsql:get-time)))
304       (clsql:time= (clsql:time+ now (clsql:make-duration :minute 900))
305              (clsql:roll now :minute 900)))
306   t)
307
308
309 ;; Clsql:Roll by minutes: +900
310 (deftest :time/13
311     (let* ((now (clsql:get-time))
312            (add-time (clsql:time+ now (clsql:make-duration :minute 9000)))
313            (roll-time (clsql:roll now :minute 9000)))
314       (clsql:time= add-time roll-time))
315   t)
316
317
318
319 ;;; The cross platform dataset uses the 'timestamp' column type which is
320 ;;; in sql-92, for all that means.
321 (deftest :time/cross-platform/no-usec/no-tz
322     (with-dataset *cross-platform-datetest*
323       (let ((time (parse-timestring "2008-09-09T14:37:29")))
324         (clsql-sys:insert-records :into [datetest]
325                                   :attributes '([testtime])
326                                   :values (list time))
327         (let ((testtime
328                (first (clsql:select [testtime]
329                                     :from [datetest] :flatp t
330                                     :where [= [testtime] time] ))))
331           (format-time nil (parse-timestring testtime) :format :iso)
332           )))
333   #.(format-time nil (parse-timestring "2008-09-09T14:37:29") :format :iso))
334
335  ;; I think the reasonable thing is that timezones be stripped and dates be
336  ;; converted to UTC, as the DB should be returning a zoneless stamp
337 (deftest :time/cross-platform/no-usec/tz
338     (with-dataset *cross-platform-datetest*
339       (let ((time (parse-timestring "2008-09-09T14:37:29-04:00")))
340         (clsql-sys:insert-records :into [datetest]
341                                   :attributes '([testtime])
342                                   :values (list time))
343         (let ((testtime
344                (first (clsql:select [testtime]
345                                     :from [datetest] :flatp t
346                                     :where [= [testtime] time] ))))
347           (format-time nil (parse-timestring testtime) :format :iso)
348           )))
349  ;; I think the reasonable thing is that timezones be stripped, as the DB should
350  ;; be returning a zoneless stamp
351   #.(format-time nil (parse-timestring "2008-09-09T18:37:29") :format :iso))
352
353 ;;;This test gets at the databases that only support miliseconds,
354 ;;; not microseconds.
355 (deftest :time/cross-platform/msec
356     (with-dataset *cross-platform-datetest*
357       (let ((time (parse-timestring "2008-09-09T14:37:29.423")))
358         (clsql-sys:insert-records :into [datetest]
359                                   :attributes '([testtime])
360                                   :values (list time))
361         (let ((testtime
362                (first (clsql:select [testtime]
363                                     :from [datetest] :flatp t
364                                     :where [= [testtime] time] ))))
365           (format-time nil (parse-timestring testtime) :format :iso)
366           )))
367   #.(format-time nil (parse-timestring "2008-09-09T14:37:29.423") :format :iso))
368
369 (deftest :time/cross-platform/usec/no-tz
370     (with-dataset *cross-platform-datetest*
371       (let ((time (parse-timestring "2008-09-09T14:37:29.000213")))
372         (clsql-sys:insert-records :into [datetest]
373                                   :attributes '([testtime])
374                                   :values (list time))
375         (let ((testtime
376                (first (clsql:select [testtime]
377                                     :from [datetest] :flatp t
378                                     :where [= [testtime] time] ))))
379           (format-time nil (parse-timestring testtime) :format :iso)
380           )))
381   #.(format-time nil (parse-timestring "2008-09-09T14:37:29.000213") :format :iso))
382
383 (deftest :time/cross-platform/usec/tz
384     (with-dataset *cross-platform-datetest*
385       (let ((time (parse-timestring "2008-09-09T14:37:29.000213-04:00")))
386         (clsql-sys:insert-records :into [datetest]
387                                   :attributes '([testtime])
388                                   :values (list time))
389         (let ((testtime
390                (first (clsql:select [testtime]
391                                     :from [datetest]
392                                     :limit 1 :flatp t
393                                     :where [= [testtime] time] ))))
394           (format-time nil (parse-timestring testtime) :format :iso)
395           )))
396   #.(format-time nil (parse-timestring "2008-09-09T18:37:29.000213") :format :iso))
397
398
399
400
401 ;;; All odbc databases use local times exclusively (they do not send timezone info)
402 ;;; Postgresql can use timezones, except when being used over odbc.  This test when
403 ;;; run through both postgres socket and postgres odbc should test a fairly
404 ;;; broad swath of available problem space, Timestamptz should return UTC times,
405 ;;; timestamps should return zoneless local times
406 ;;;
407 ;;; Things the following tests try to prove correct
408 ;;;  * Reading and writing usec and usec-less times
409 ;;;  * reading and writing timezones (Z=utc) when appropriate (eg: postgresql-socket)
410 ;;;  * reading and writing localtimes when appropriate (eg: ODBC)
411 ;;;  * reading and writing through both the oodml and fdml layers
412
413
414
415 (deftest :time/pg/fdml/usec
416   (with-dataset *ds-datetest*
417     (let ((time (parse-timestring "2008-09-09T14:37:29.000213-04:00")))
418       (clsql-sys:insert-records :into [datetest]
419                                 :attributes '([testtimetz] [testtime] [id])
420                                 :values (list time time 1))
421       (destructuring-bind (testtimetz testtime)
422           (first (clsql:select [testtimetz] [testtime]
423                                :from [datetest]
424                                :limit 1 :flatp t
425                                :where [= [testtime] time] ))
426         (values (iso-timestring (parse-timestring testtime))
427                 (iso-timestring (parse-timestring testtimetz))))))
428   #.(iso-timestring (parse-timestring "2008-09-09T18:37:29.000213"))
429   #.(iso-timestring (parse-timestring "2008-09-09T14:37:29.000213-04:00")))
430
431 (deftest :time/pg/oodml/no-usec
432   (with-dataset *ds-datetest*
433     (let ((time (parse-timestring "2008-09-09T14:37:29-04:00")))
434       (clsql-sys:update-records-from-instance
435        (make-instance 'datetest :testtimetz time :testtime time :id 1))
436       (let ((o (first (clsql:select
437                           'datetest
438                         :limit 1 :flatp t
439                         :where [= [testtime] time] ))))
440         (assert o (o) "o shouldnt be null here (we should have just inserted)")
441         (update-records-from-instance o)
442         (update-instance-from-records o)
443         (values (iso-timestring (testtime o))
444                 (iso-timestring (testtimetz o))))))
445   #.(iso-timestring (parse-timestring "2008-09-09T18:37:29"))
446   #.(iso-timestring (parse-timestring "2008-09-09T14:37:29-04:00")))
447
448 (deftest :time/pg/oodml/usec
449     (with-dataset *ds-datetest*
450       (let ((time (parse-timestring "2008-09-09T14:37:29.000278-04:00")))
451         (clsql-sys:update-records-from-instance
452          (make-instance 'datetest :testtimetz time :testtime time :id 1))
453         (let ((o (first (clsql:select
454                          'datetest
455                          :limit 1 :flatp t
456                          :where [= [testtime] time] ))))
457           (assert o (o) "o shouldnt be null here (we should have just inserted)")
458           (update-records-from-instance o)
459           (update-instance-from-records o)
460           (values (iso-timestring (testtime o))
461                   (iso-timestring (testtimetz o)))
462           )))
463     #.(iso-timestring (parse-timestring "2008-09-09T18:37:29.000278"))
464     #.(iso-timestring (parse-timestring "2008-09-09T14:37:29.000278-04:00")))
465
466 (deftest :time/historic-datetimes
467   (with-dataset *cross-platform-datetest*
468     (let ((time (parse-timestring "1800-09-09T14:37:29")))
469       (clsql-sys:insert-records :into [datetest]
470                                 :attributes '([testtime])
471                                 :values (list time))
472       (let ((testtime
473               (first (clsql:select [testtime]
474                        :from [datetest] :flatp t
475                        :where [= [testtime] time] ))))
476         (format-time nil (parse-timestring testtime) :format :iso)
477         )))
478   #.(format-time nil (parse-timestring "1800-09-09T14:37:29") :format :iso))
479
480 ))
481
482
483
484
485