[local-time-devel] ENCODE-TIMESTAMP with a timezone

Thomas Munro munro at ip9.org
Sun May 10 17:51:08 UTC 2009


Hi

I was trying out local-time 1.0.1 and I encountered a problem: the
function ENCODE-TIMESTAMP takes an offset, rather than a timezone
(unlike the interface described in the Naggum paper, I think).
The offset defaults to your offset for now, but that is not
necessarily the offset that was in effect at the local time you
are encoding, which might not be what some users want, and I
couldn't figure out how to get the appropriate offset for that time
with the functions available (although I may have missed something).

For example, my computer and I live in the timezone Europe/London,
which maps to subzone BST (GMT+01) today, and during the winter it
maps to subzone GMT.  The following REPL session shows the problem
as I see it:

CL-USER> (local-time:encode-timestamp 0 0 0 12 25 12 2008)
@2008-12-25T11:00:00.000000Z
;; The local time was interpreted as BST, even though at the time
;; mentioned the subtimezone here was GMT; working as designed
;; but is it what most users would want?

CL-USER> (local-time:encode-timestamp 0 0 0 12 09 05 2009)
@2009-05-09T12:00:00.000000+01:00
;; For yesterday's date it so happens that the subtimezone matches
;; today's, so the result is good.

I need to be able to get from "2008-12-25 12:00:00 in tzinfo zone
Europe/London" to a TIMESTAMP.  I can see of course that there
is a problem of ambiguity with such an interface: in countries with
DST there is one hour per year when the local time repeats, but as
far as I can see other libraries support this (for example Java's
calendar system and POSIX mktime) and I guess they must just pick
an arbitrary interpretation of ambiguous dates.

Here is my attempt at modifying ENCODE-TIMESTAMP to support either
OFFSET or TIMEZONE arguments.  If you supply neither it uses
*DEFAULT-TIMEZONE* rather than the current offset (so this is a change
in behaviour).  Here is the code:

(defun encode-timestamp (nsec sec minute hour day month year
                         &key (timezone *default-timezone*) offset into)
  "Return a new TIMESTAMP instance corresponding to the specified time
elements."
  ;; If the user provided an explicit offset, we use that.  Otherwise,
  ;; we try converting the local time to a timestamp using each available
  ;; subtimezone, until we find one where the offset matches the offset that
  ;; applies at that time (according to the transition table).
  ;;
  ;; Consequence for ambiguous cases:
  ;; Whichever subtimezone is listed first in the tzinfo database will be
  ;; the one that we pick to resolve ambiguous local time representations.
  (declare (type integer nsec sec minute hour day month year))
  (if offset
      ;; a specific offset was requested
      (multiple-value-bind (nsec sec day)
          (encode-timestamp-into-values nsec sec minute hour day month year
                                        :offset offset)
        (if into
            (progn
              (setf (nsec-of into) nsec)
              (setf (sec-of into) sec)
              (setf (day-of into) day)
              into)
            (make-timestamp
             :nsec nsec
             :sec sec
             :day day)))
      ;; find the first potential offset that is valid at the represented time
      (let ((timestamp (or into (make-timestamp))))
        (loop
           for subtimezone across (timezone-subzones timezone) do
             (encode-timestamp nsec sec minute hour day month year
                               :offset (subzone-offset subtimezone)
                               :into timestamp)
             (if (= (timestamp-subtimezone timestamp timezone)
                    (subzone-offset subtimezone))
                 (return timestamp))
           finally
             (error "The requested local time is not valid")))))

Here's are those examples again in the REPL with this definition of
ENCODE-TIMESTAMP:

CL-USER> (local-time:encode-timestamp 0 0 0 12 25 12 2008)
@2008-12-25T12:00:00.000000Z

CL-USER> (local-time:encode-timestamp 0 0 0 12 9 5 2009)
@2009-05-09T12:00:00.000000+01:00

What do you think, is this useful?  Is the algorithm correct and
is there a better one?  Would it be better to have two separate
functions, one with the OFFSET and the other with the TIMEZONE?
Does my LOOP syntax suck?

Thanks
Thomas Munro




More information about the local-time-devel mailing list