Prime dates

I have been nerdsniped by the gang at A Problem Squared from their episode 105, "Next Prime Dates and Ancient Gag Rates." The problem is thus (paraprased from the pod because they don't have transcripts -- yall, do transcripts!):

Three common date-writing formats are the "American" way, MM/DD/YY, the "European" way, DD/MM/YY, and the ISO8601 (also known as "correct") way, YYYY-MM-DD. For example, today's date in the three formats is as follows:

If you smush those numbers together, some of them might be prime. None of today's are, but you can see how that could be so. What we want to find is dates where *all three* are prime. Some examples from recent history include

That's all of them from 2000 to now. Matt and Bec did a few more from the near future---I believe up to 2100---but I wanted to get *all* of the triple prime dates. From 0 to 9999. (I think 9999 is a good stopping point, because let's face it, years do keep coming and they don't stop coming). The list will be below, but I want to share the code I used to generate it.

Matt is renowned for his terrible Python code, but my flavor of terrible code is Common Lisp. I was able to whip up this code in oh, an hour (when I should've been working, but like I said, I was nerdsniped). At first, I was generating lists of years and months and days, then generating lists of each format, but then I realized I just want to find out when a date is triple-prime, so I don't have to generate anything before hand.

(defpackage #:prime-dates
  (:use #:cl))

(in-package #:prime-dates)

(defun leap-year? (year)
  (and (zerop (mod year 4))
       (if (zerop (mod year 100))
           (zerop (mod year 400))
           t)))

(defun valid-date? (year month day)
  (and (<= 1 month 12)
       (cond
         ((member month '(9 4 6 11))
          (<= 1 day 30))
         ((and (eq month 2)
               (leap-year? year))
          (<= 1 day 29))
         ((eq month 2)
          (<= 1 day 28))
         (t
          (<= 1 day 31)))))

(defun composite? (n)
  (loop :for divisor :from 2 :upto (sqrt n)
        :thereis (zerop (mod n divisor))))

(defun prime? (n)
  (not (composite? n)))

(defun triple-prime-date? (year month day)
  (let ((yyyymmdd (+ (* year 10000)
                     (* month 100)
                     day))
        (mmddyy (+ (* month 10000)
                   (* day 100)
                   (mod year 100)))
        (ddmmyy (+ (* day 10000)
                   (* month 100)
                   (mod year 100))))
    (and (valid-date? year month day)
         (prime? yyyymmdd)
         (prime? mmddyy)
         (prime? ddmmyy))))

(defun find-prime-dates (from-year &optional (to-year from-year))
  (loop :for year :from from-year :to to-year
        :append
        (loop :for month :from 1 :to 12
              :append
              (loop :for day :from 1 :to 31
                    :when (triple-prime-date? year month day)
                      :collect (format nil "~4,'0D-~2,'0D-~2,'0D"
                                       year month day)))))

After running it and double-checking the dates from the podcast were in my list, I ran

(find-prime-dates 0 9999)

and here is the result, in all of its exhaustive glory:

Big thanks to A Problem Squared for making my morning a little more interesting ^_^

PS. Of course, as I'm writing this I realize that some folks write the date of, say, today as 25/5/25 (or they might?) ... and as they mention on the pod some might also write 5/25/2025. Solving for those date formats as well would be pretty simple, and is left as an exercise to the reader. (Hint: you might want to use the CL functions `parse-integer` and `format`.)