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"
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)
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)))
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
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)))
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
(< (date-string-to-universal-time "2023-01-21")
(date-string-to-universal-time "2023-01-22"))
;; returns T
There you have it. See you next time!
Top comments (4)
The function
encode-universal-time
doesn't give me the same results as you. Your results (shown in the comments):My results (with SBCL 2.4.0):
And, I get yet a third result when I use an online lisp compiler:
I'm not sure why, but changing the year from 23 to 2023 produces the same results:
There's nothing in the hyperspec about that. How does
encode-universal-time
know that 23 means 2023 and not 1923? Edit: The hyperspec says:Edit: I think the different results have to do with timezones. According to the hyperspec for
encode-universal-time
:That implies that there is some adjustment if you don't supply a timezone? I tried suppling a timezone of 0, which I think is GMT:
With a timezone of 0, I got the same numbers using the online lisp interpreter.
Oh, on reddit we have the idea:
Thanks @vindarel !, should that simplify my
parse-date
function? something like:Well done. To add a handy library in: