"YYYY-MM-DD" to Time in Common Lisp

muro - Mar 4 - - Dev Community

Disclaimer:
The purpose of this post is to track my journey into Common Lisp. Though there might exist more optimal ways, this represents my personal path.


While querying an API, I encountered dates formatted as strings in the "YYYY-MM-DD" format (for instance, "2023-01-21"), prompting me to seek a method to transform these into a format conducive to date comparisons.

Let's break it down step by step to get to the final solution, shall we?

First up, we're going to tackle that date string by splitting it into its parts: the year (yup, the 'YYYY' bit), the month (the 'MM' bit), and the day (you guessed it, the 'DD' part).

So, we've got our example date string: '2023-01-21'. Now, let's snag the 'YYYY' part. According to the Common Lisp Hyperspec (CLHS), there's a handy function that lets us pull out a chunk of a sequence when we tell it where to start and stop. In our scenario, that sequence is our date string.

From the CLHS:
subseq creates a sequence that is a copy of the subsequence of sequence bounded by start and end.

So, we're going to apply subseq to our date string. Since the year part stretches from positions 0 to 4, we just feed those numbers into our function like this:

(subseq "2023-01-21" 0 4) ;;; returns "2023"
Enter fullscreen mode Exit fullscreen mode

Great, we're on the right track!

Now, it's key to note that our result, "2023", comes back as a string. To transform it into an integer, we can use the parse-integer function. This step will convert the string "2023" into an actual numeric value, making it ready for any date comparisons or calculations we might want to perform later on.
Let's now enclose our snippet with parse-integer to perform the conversion:

(parse-integer (subseq "2023-01-21" 0 4)) ;;; returns 2023 (now it's an integer)
Enter fullscreen mode Exit fullscreen mode

With our strategy in place, we're ready to define a function named parse-date. This function will extract and return three pieces of information from our date string: the year, the month, and the day:

(defun parse-date (date-string)
  "Extracts year, month, and day from a date string formatted as YYYY-MM-DD."
  (let ((year (parse-integer (subseq date-string 0 4)))
        (month (parse-integer (subseq date-string 5 7)))
        (day (parse-integer (subseq date-string 8 10))))
    (values year month day)))
Enter fullscreen mode Exit fullscreen mode

Now, let's transform the day, month, and year values into a universal time format, enabling us to perform comparisons between different dates.

Luckily, Common Lisp offers the encode-universal-time function, capable of transforming our values into universal time. As outlined in the CLHS, to use this function effectively, we should supply the seconds, minutes, hour, day, month, and year as arguments, precisely in that sequence.
Given that we lack the specifics for seconds, minutes, and hours, we'll substitute zeroes for these values instead.

;;; Let's convert 2023-01-21:
(encode-universal-time 0 0 0 21 1 23) ;; returns 3883233600
;;; Let's convert 2023-01-22:
(encode-universal-time 0 0 0 22 1 23) ;; returns 3883320000
Enter fullscreen mode Exit fullscreen mode

Let's create a function that turns a date string straight into universal time. We're going to use our parse-date function here. Remember, parse-date gives us three things: the year, the month, and the day. We'll grab these three using something called multiple-value-bind, and then use them to get everything set up for the encode-universal-time function:

(defun date-string-to-universal-time (date-string)
  "Converts a date string in the format 'YYYY-MM-DD' to universal time."
  (multiple-value-bind (year month day) (parse-date date-string)
    (encode-universal-time 0 ; second
                           0 ; minute
                           0 ; hour
                           day
                           month
                           year))) 
Enter fullscreen mode Exit fullscreen mode

Now, we're equipped to perform date comparisons right from the date string, let's write an example:

(> (date-string-to-universal-time "2023-01-21")
     (date-string-to-universal-time "2023-01-22"))
;; returns NIL
Enter fullscreen mode Exit fullscreen mode
(< (date-string-to-universal-time "2023-01-21")
     (date-string-to-universal-time "2023-01-22"))
;; returns T
Enter fullscreen mode Exit fullscreen mode

There you have it. See you next time!

. .