Skip to content

Lisp - Date and Time

Handling dates and times is a common requirement in software development. Common Lisp provides a low-level, portable standard for time called Universal Time. For more sophisticated and developer-friendly operations, the modern ecosystem relies on external libraries. This tutorial covers both approaches.

Common Lisp’s built-in time functions revolve around Universal Time, an integer representing the number of seconds that have elapsed since midnight, January 1, 1900, GMT.

  • get-universal-time: Returns the current Universal Time as an integer. This is the primary way to get a timestamp.
  • decode-universal-time: Converts a Universal Time integer into nine values for easier inspection: second, minute, hour, day, month, year, day of the week (Monday=0), a daylight saving time flag, and the timezone offset from GMT.
  • encode-universal-time: Performs the reverse operation, converting the nine decoded values back into a Universal Time integer.
  • get-decoded-time: A convenient shorthand for (decode-universal-time (get-universal-time)), returning the nine decoded values for the current time.

While powerful, using these functions directly can be verbose. Here’s how to get and display the current time.

(multiple-value-bind (second minute hour day month year day-of-week)
(get-decoded-time)
(format t "It is now ~2,'0d:~2,'0d:~2,'0d on ~d/~d/~d. Day of week: ~d."
hour minute second month day year day-of-week))
It is now 14:45:02 on 10/26/2023. Day of week: 3.

The Modern Approach: The local-time Library

Section titled “The Modern Approach: The local-time Library”

For nearly all practical applications, the local-time library is the de facto standard. It provides a rich, intuitive API for date-time arithmetic, timezone management, and formatting. It abstracts away the complexities of Universal Time.

Quicklisp is the package manager for Common Lisp. To use local-time, you first need to load it into your Lisp image:

;; This command downloads and loads the library.
;; You only need to run it once per session.
(ql:quickload :local-time)

Compare the clarity of this example with the previous one. We can get the current time, add 3 days and 10 hours to it, and format it according to a specific template.

(use-package :local-time)
;; Get the current time as a timestamp object
(let* ((now (now))
(future (timestamp+ now 3 :day))
(future (timestamp+ future 10 :hour)))
(format t "Current time: ~a~%" now)
;; format-timestring can produce almost any format you need.
;; The 'nil' argument uses the current timezone.
(format t "Formatted time: ~a~%"
(format-timestring nil now :format '(:long-weekday ", " :long-month " " :day ", " :year " at " :hour12 ":" :min ":" :sec " " :ampm)))
(format t "In 3 days and 10 hours, it will be: ~a~%" future))
Current time: @2023-10-26T14:45:02.123456-04:00
Formatted time: Thursday, October 26, 2023 at 02:45:02 PM
In 3 days and 10 hours, it will be: @2023-10-30T00:45:02.123456-04:00
  • Prefer local-time: For any application-level code, local-time is more robust, readable, and feature-rich than the built-in functions.
  • Understand Universal Time: It’s still valuable to understand Universal Time, as it’s the portable foundation that libraries are built upon and is often used in file metadata and other low-level systems.
  • Explore Other Libraries: While local-time is dominant, other libraries like cl-dates exist for specific needs, such as business-day calculations.
  • Timezones are Complex: local-time has powerful timezone support. Always be explicit about timezones in applications that are not purely local.