Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Ignore timezone when adjusting by units smaller than a day.
Undo scientific time toggle that enables the same, but manually.
Fix issue #8.

Scientific time has a well defined unit, viz. a second, and a timezone with constant day length, viz. UTC. Duration of a second, and also of a minute and an hour, is independent of the timezone. So adjusting by these units always gives a “scientific” result; and if one wants to adjust by larger units, treating them as constant in a timezone where they are not constant, they should either switch to UTC, or convert the adjustement to constant units (e.g. add 24 hours to adjust by one astronomical day).
  • Loading branch information
orivej committed Jan 10, 2015
1 parent 3ca8cb1 commit 73aec8a
Show file tree
Hide file tree
Showing 5 changed files with 29 additions and 86 deletions.
21 changes: 0 additions & 21 deletions doc/local-time.texinfo
Expand Up @@ -466,27 +466,6 @@ Returns the decoded part of the timestamp.
@node Manipulating Date and Time Values
@section Manipulating Date and Time Values

@itindex *use-political-time*
@defvr Default *use-political-time*

When political time is enabled, changes in timezone offset are
considered when adjusting timestamp values. Adding 24 hours to a
timestamp across a DST boundary will result in the same wall-clock
time.

When scientific time is used, changes in timezone offset are not
considered. Adding 24 hours to a timestamp across a DST boundary will
result in a different wall-clock time.
@end defvr

@defmac with-scientific-time &rest body
@defmacx with-political-time &rest body

These macros make it simple to temporarily select political or
scientific time.

@end defmac

@itindex timestamp+
@itindex timestamp-
@defun timestamp+ time amount unit
Expand Down
67 changes: 18 additions & 49 deletions src/local-time.lisp
Expand Up @@ -108,22 +108,6 @@
(when path
(try (merge-pathnames "../" path)))))))

;;; Per Naggum we use the terms Political Time and Scientific Time to
;;; distinguish between two ways to think about adjusting times around
;;; DST boundaries. If *use-political-time* is nil, we do not
;;; consider changes in timezone offset when adjusting timestamp
;;; values.

(defparameter *use-political-time* t)

(defmacro with-scientific-time (&rest body)
`(let ((*use-political-time* nil))
,@body))

(defmacro with-political-time (&rest body)
`(let ((*use-political-time* t))
,@body))

;;; Month information
(defparameter +month-names+
#("" "January" "February" "March" "April" "May" "June" "July" "August"
Expand Down Expand Up @@ -428,23 +412,21 @@ In other words:
(let* ((root-directory timezone-repository)
(cutoff-position (length (princ-to-string root-directory))))
(flet ((visitor (file)
(let* ((full-name (subseq (princ-to-string file) cutoff-position))
(name (pathname-name file))
timezone)
(handler-case
(progn
(setf timezone (%realize-timezone (make-timezone :path file :name name)))
(setf (gethash full-name *location-name->timezone*) timezone)
(map nil (lambda (subzone)
(push timezone (gethash (subzone-abbrev subzone)
*abbreviated-subzone-name->timezone-list*)))
(timezone-subzones timezone)))
(invalid-timezone-file () nil)))))
(handler-case
(let* ((full-name (subseq (princ-to-string file) cutoff-position))
(name (pathname-name file))
(timezone (%realize-timezone (make-timezone :path file :name name))))
(setf (gethash full-name *location-name->timezone*) timezone)
(map nil (lambda (subzone)
(push timezone (gethash (subzone-abbrev subzone)
*abbreviated-subzone-name->timezone-list*)))
(timezone-subzones timezone)))
(invalid-timezone-file () nil))))
(setf *location-name->timezone* (make-hash-table :test 'equal))
(setf *abbreviated-subzone-name->timezone-list* (make-hash-table :test 'equal))
(cl-fad:walk-directory root-directory #'visitor :directories nil
:test (lambda (file)
(not (find "etc" (pathname-directory file) :test #'string=))))
(not (find "Etc" (pathname-directory file) :test #'string=))))
(cl-fad:walk-directory (merge-pathnames "Etc/" root-directory) #'visitor :directories nil))))

(defmacro make-timestamp (&rest args)
Expand Down Expand Up @@ -502,10 +484,9 @@ In other words:
"Returns two values, the values of new DAY and SEC slots of the timestamp adjusted to the given timezone."
(declare (type integer sec day offset))
(multiple-value-bind (offset-day offset-sec)
(truncate (abs offset) +seconds-per-day+)
(let* ((offset-sign (signum offset))
(new-sec (+ sec (* offset-sign offset-sec)))
(new-day (+ day (* offset-sign offset-day))))
(truncate offset +seconds-per-day+)
(let* ((new-sec (+ sec offset-sec))
(new-day (+ day offset-day)))
(cond ((minusp new-sec)
(incf new-sec +seconds-per-day+)
(decf new-day))
Expand Down Expand Up @@ -793,26 +774,14 @@ the previous day given by OFFSET."
(:minute +seconds-per-minute+)
(:hour +seconds-per-hour+))))
+seconds-per-day+)
(cond
(*use-political-time*
(setf part :day
offset days-offset
sec new-sec)
(when (= offset 0)
(return-from direct-adjust (values nsec sec day)))
(go top))
(t
(setf sec new-sec)
(incf day days-offset)
(return-from direct-adjust (values nsec sec day))))))
(return-from direct-adjust (values nsec new-sec (+ day days-offset)))))
(:day
(incf day offset)
(setf new-utc-offset (or utc-offset
(timestamp-subtimezone (make-timestamp :nsec nsec :sec sec :day day)
timezone)))
(when (and *use-political-time*
(not (= old-utc-offset
new-utc-offset)))
(when (not (= old-utc-offset
new-utc-offset))
;; We hit the DST boundary. We need to restart again
;; with :sec, but this time we know both old and new
;; UTC offset will be the same, so it's safe to do
Expand Down Expand Up @@ -1064,7 +1033,7 @@ It should be an instance of a class that responds to one or more of the methods
(defgeneric clock-today (clock)
(:documentation "Returns a timestamp for the current date given a
clock. The date is encoded by convention as a timestamp with the
time set to 12:00UTC."))
time set to 00:00:00UTC."))

(defmethod clock-now (clock)
(declare (ignore clock))
Expand Down
5 changes: 1 addition & 4 deletions src/package.lisp
Expand Up @@ -86,7 +86,4 @@
#:+iso-week-date-format+
#:astronomical-julian-date
#:modified-julian-date
#:astronomical-modified-julian-date
#:*use-political-time*
#:with-scientific-time
#:with-political-time))
#:astronomical-modified-julian-date))
12 changes: 0 additions & 12 deletions test/simple.lisp
Expand Up @@ -122,18 +122,6 @@
(modified-timestamp (adjust-timestamp timestamp (timezone +utc-zone+) (offset :day-of-week :wednesday))))
(is (timestamp= (parse-timestring "2014-01-01T00:00:00.000000+00:00") modified-timestamp))))

(deftest adjust-timestamp/across-dst/scientific ()
(let* ((local-time::*use-political-time* nil)
(timestamp (parse-timestring "2014-03-09T01:00:00.000000-05:00"))
(modified-timestamp (adjust-timestamp timestamp (timezone eastern-tz) (offset :day 1))))
(is (timestamp= (parse-timestring "2014-03-10T02:00:00.000000-04:00") modified-timestamp))))

(deftest adjust-timestamp/across-dst/political ()
(let* ((eastern-tz (local-time:find-timezone-by-location-name "US/Eastern"))
(timestamp (parse-timestring "2014-03-09T01:00:00.000000-05:00"))
(modified-timestamp (adjust-timestamp timestamp (timezone eastern-tz) (offset :day 1))))
(is (timestamp= (parse-timestring "2014-03-10T01:00:00.000000-04:00") modified-timestamp))))

#+nil
(deftest test/adjust-days ()
(let ((sunday (parse-timestring "2006-12-17T01:02:03Z")))
Expand Down
10 changes: 10 additions & 0 deletions test/timezone.lisp
Expand Up @@ -56,3 +56,13 @@
`(0 0 ,@(reverse (first test-case)) :offset 0))))
(local-time:decode-timestamp timestamp :timezone eastern-tz))
(apply 'values `(0 0 ,@(reverse (second test-case)))))))))

(deftest test/timezone/adjust-across-dst-by-days ()
(let* ((old (parse-timestring "2014-03-09T01:00:00.000000-05:00"))
(new (timestamp+ old 1 :day eastern-tz)))
(is (= (* 23 60 60) (timestamp-difference new old)))))

(deftest test/timezone/adjust-across-dst-by-hours ()
(let* ((old (parse-timestring "2014-03-09T01:00:00.000000-05:00"))
(new (timestamp+ old 24 :hour eastern-tz)))
(is (= (* 24 60 60) (timestamp-difference new old)))))

0 comments on commit 73aec8a

Please sign in to comment.