Common Lisp 有两种不同的查看时间的方法:国际标准时间,即日常使用的时间;运行时间,即 CPU 处理的时间。

国际标准时间

国际标准时间是指距离格林威治时间1900年1月1日0:00过了多少秒。函数 GET-UNIVERSAL-TIME 会返回当前的时间标准时间:

  1. CL-USER> (get-universal-time)
  2. 3794283195

这个数字不是那么好理解,可以使用函数 DECODE-UNIVERSAL-TIME 将其转换为正常的日历上的时间:

  1. CL-USER> (decode-universal-time (get-universal-time))
  2. 31
  3. 35
  4. 15
  5. 27
  6. 3
  7. 2020
  8. 4
  9. NIL
  10. -8

输出的结果的值分别代表:秒、分、时、日、月、年、周几、夏令时和时区。其中周几是从 0 开始计数的,也就是说如果结果是 4 的话,那么就表示是周五。时区的话即为当前时间加上时区就是格林威治时间。所以目前的话是 2020年3月27号 15:35:31,周五,东八区,无夏令时。

  1. CL-USER> (get-decoded-time)

等效于

  1. CL-USER> (decode-universal-time (get-universal-time))

以下是个使用这些函数的程序:

  1. CL-USER> (defconstant *day-names*
  2. '("Monday" "Tuesday" "Wednesday"
  3. "Thursday" "Friday" "Saturday"
  4. "Sunday"))
  5. *DAY-NAMES*
  6. CL-USER> (multiple-value-bind
  7. (second minute hour day month year day-of-week dst-p tz)
  8. (get-decoded-time)
  9. (format t "It is now ~2,'0d:~2,'0d:~2,'0d of ~a, ~d/~2,'0d/~d (GMT~@d)"
  10. hour
  11. minute
  12. second
  13. (nth day-of-week *day-names*)
  14. month
  15. day
  16. year
  17. (- tz)))
  18. It is now 15:48:38 of Friday, 3/27/2020 (GMT+8)

既然有 DECODE-UNIVERSAL-TIME,那么也就有 ENCODE-UNIVERSAL-TIME

  1. CL-USER> (encode-universal-time 48 47 15 27 3 2020)
  2. 3794284068

有了这些后,就可以计算一些临时的时间距离了,如地月时间(地球到月球的时间):

  1. CL-USER> (setq *moon* (encode-universal-time 0 17 16 20 7 1969 4))
  2. 2194805820
  3. CL-USER> (setq *takeoff* (encode-universal-time 0 38 11 28 1 1986 5))
  4. 2716303080
  5. CL-USER> (- *takeoff* *moon*)
  6. 521497260

内部时间

内部时间是使用计算机时钟来计算Lisp环境中的时间。与世界标准时间是不同的。首先,内部时间可以计算你启动 Lisp 实例的时间,计算机启动的时间。同时内部时间的单位使用的是微秒,而不是秒。可以使用 INTERNAL-TIME-UNITS-PER-SECOND 来查看:

  1. CL-USER> internal-time-units-per-second
  2. 1000

同时运行时间和内部时间是有区别的。

  1. CL-USER> (let ((real1 (get-internal-real-time))
  2. (run1 (get-internal-run-time)))
  3. (... your call here ...)
  4. (let ((run2 (get-internal-run-time))
  5. (real2 (get-internal-real-time)))
  6. (format t "Computation took:~%")
  7. (format t " ~f seconds of real time~%"
  8. (/ (- real2 real1) internal-time-units-per-second))
  9. (format t " ~f seconds of run time~%"
  10. (/ (- run2 run1) internal-time-units-per-second))))
  11. ;; use sleep to see the difference
  12. CL-USER> (defmacro timing (&body forms)
  13. (let ((real1 (gensym))
  14. (real2 (gensym))
  15. (run1 (gensym))
  16. (run2 (gensym))
  17. (result (gensym)))
  18. `(let* ((,real1 (get-internal-real-time))
  19. (,run1 (get-internal-run-time))
  20. (,result (progn ,@forms))
  21. (,run2 (get-internal-run-time))
  22. (,real2 (get-internal-real-time)))
  23. (format *debug-io* ";;; Computation took:~%")
  24. (format *debug-io* ";;; ~f seconds of real time~%"
  25. (/ (- ,real2 ,real1) internal-time-units-per-second))
  26. (format t ";;; ~f seconds of run time~%"
  27. (/ (- ,run2 ,run1) internal-time-units-per-second))
  28. ,result)))
  29. TIMING
  30. CL-USER> (timing (sleep 1))
  31. ;;; Computation took: 0.994 seconds of real time 0.0 seconds of run
  32. ;;; time
  33. NIL

以下是 Allegro Common Lisp 6.0 的一个例子:

  1. CL-USER> (let ((numbers (loop for i from 1 to 100 collect (random 1.0))))
  2. (time (sort numbers #'<)))
  3. ; cpu time (non-gc) 0 msec user, 10 msec system
  4. ; cpu time (gc) 0 msec user, 0 msec system
  5. ; cpu time (total) 0 msec user, 10 msec system
  6. ; real time 9 msec
  7. ; space allocation:
  8. ; 3,586 cons cells, 11,704 other bytes, 0 static bytes

计算周几

上文中的计算国际标准时间不适用与1900年之前的日期

  1. CL-USER> (defun day-of-week (day month year)
  2. "Returns the day of the week as an integer. Monday is 0."
  3. (nth-value
  4. 6
  5. (decode-universal-time
  6. (encode-universal-time 0 0 0 day month year 0)
  7. 0)))
  8. DAY-OF-WEEK
  9. CL-USER> (day-of-week 23 12 1965)
  10. 3
  11. CL-USER> (day-of-week 1 1 1900)
  12. 0
  13. CL-USER> (day-of-week 31 12 1899)
  14. Type-error in KERNEL::OBJECT-NOT-TYPE-ERROR-HANDLER:
  15. 1899 is not of type (OR (MOD 100) (INTEGER 1900))

Gerald Doussot 提供了一个计算1900年之前的周几的函数:

  1. (defun day-of-week (day month year)
  2. "Returns the day of the week as an integer. Sunday is 0. Works for years after 1752."
  3. (let ((offset '(0 3 2 5 0 3 5 1 4 6 2 4)))
  4. (when (< month 3)
  5. (decf year 1))
  6. (mod
  7. (truncate (+ year
  8. (/ year 4)
  9. (/ (- year)
  10. 100)
  11. (/ year 400)
  12. (nth (1- month) offset)
  13. day
  14. -1))
  15. 7)))

local-time

local-time 库是 quicklisp 中较常用库。通常,这个库可以:

  • 打印不同格式的时间戳
  • 解析时间的字符串
  • 时间运算
  • 可以在 Unix 时间、时间戳和世界标准时间中进行转换

如,将 Unix 时间转换成可读的:

  1. (defun unix-time-to-human-string (unix-time)
  2. (local-time:format-timestring
  3. nil
  4. (local-time:unix-to-timestamp unix-time)
  5. :format local-time:+asctime-format+))

获取当前时间

now today

  1. (ql:quickload :local-time)
  2. (use-package :local-time)
  3. (local-time:now)
  4. @2020-03-27T16:16:33.195844+08:00
  5. (local-time:today)
  6. @2020-03-27T08:00:00.000000+08:00

格式化时间字符串

  1. (local-time:now)
  2. @2020-03-27T16:16:33.195844+08:00
  1. (format nil "~a" (local-time:now))
  2. "2020-03-27T16:18:13.046527+08:00"
  1. (local-time:format-timestring nil (local-time:now))
  2. "2020-03-27T16:19:02.555770+08:00"

接受 :format 参数,默认为 +iso-8601-format+

  • +rfc-1123-format+
  1. (local-time:format-timestring nil (local-time:now) :format local-time:+rfc-1123-format+)
  2. "Fri, 27 Mar 2020 16:21:13 +0800"
  • +asctime-format+
  1. (local-time:format-timestring nil (local-time:now) :format local-time:+asctime-format+)
  2. "Fri Mar 27 16:22:45 2020"
  • +iso-week-date-format+
  1. (local-time:format-timestring nil (local-time:now) :format local-time:+iso-week-date-format+)
  2. "2020-W13-5"

自定义时间格式

  1. (local-time:format-timestring nil (local-time:now) :format '(:year "-" :month "-" :day))
  2. 2020-3-27

具体参数见:https://common-lisp.net/project/local-time/manual.html#Parsing-and-Formatting

+rfc-1123-format+ 定义如下:

  1. (defparameter +rfc-1123-format+
  2. ;; Sun, 06 Nov 1994 08:49:37 GMT
  3. '(:short-weekday ", " (:day 2) #\space :short-month #\space (:year 4) #\space
  4. (:hour 2) #\: (:min 2) #\: (:sec 2) #\space :gmt-offset-hhmm)
  5. "See the RFC 1123 for the details about the possible values of the timezone field.")

解析时间字符串

parse-timestring
其说明文档为:

Parses a timestring and returns the corresponding timestamp. Parsing begins at start and stops at the end position. If there are invalid characters within timestring and fail-on-error is T, then an invalid-timestring error is signaled, otherwise NIL is returned.

If there is no timezone specified in timestring then offset is used as the default timezone offset (in seconds).

例子:

  1. (local-time:parse-timestring "2020-03-27T16:19:02.555770+08:00")
  2. ;; @2020-03-27T16:19:02.555770+08:00
  3. (local-time:parse-timestring "2020-3-17")
  4. ;; @2020-03-27T08:00:00.000000+08:00
  5. ;; using :date-separator
  6. (local-time:parse-timestring "2020/3/17" :date-separator #\/)
  7. ;; @2020-03-27T08:00:00.000000+08:00

其他的参数:

  • 开始结束位置
  • fail-on-error (默认为 t
  • (allow-missing-elements t)
  • (allow-misssing-date-part allow-missing-elements)
  • (allow-missing-time-part allow-missing-elements)
  • (allow-missing-timezone-part allow-missing-elements)
  • (offset 0)

如果 “Fri Mar 27 16:36:32 2020” 这个格式转换出错的话,可以使用 cl-date-time-parser

  1. (ql:quickload :cl-date-time-parser)
  2. (use-package :cl-date-time-parser)
  3. (cl-date-time-parser:parse-date-time "Fri Mar 27 16:36:32 2020")
  4. ;; 3794315792
  5. ;; 0

local-time

  1. (local-time:universal-to-timestamp *)
  2. @2020-03-28T00:36:32.000000+08:00