Даты и время
Коммон Лисп предоставляет два способа узнать время: мировое(universal) время, и локальное время, используемое компьютером.
Они могут не совпадать, мы хотим иметь возможность получить каждое из них по отдельности.
Мировое время
Мировое время 1) определяется как количество секунд, прошедших с первого января 1900 года, с 00:00 по Гринвичу (GMT). Функция get-universal-time возвращает текущее мировое время:
(get-universal-time)
3460967607
Очевидно, что таким представлением удобно пользоваться далеко не всегда, и Коммон Лисп представляет возможность для его расшифровки. Функция decode-universal-time возвращает девять значений
(decode-universal-time 3460967607)
27 ; секунда
53 ; минута
11 ; час
3 ; день
9 ; месяц
2009 ; год
3 ; день недели (пн = 0, вскр = 6)
nil ; летнее время (daylight saving time flag)
0 ; временная зона
День недели закодирован цифрой от 0 до 6, где 0 - понедельник. Временная зона представлена количеством часов, которое следует добавить, чтобы получить GMT.
Для удобства можно писать
(get-decoded-time)
вместо
(decode-universal-time (get-universal-time))
Следующий пример показывает возможные способ вывода даты и времени в удобочитаемом виде:
(defconstat *day-names*
'("понедельник" "вторник" "среда"
"четверг" "пятница" "суббота" "воскресенье"))
*DAY-NAMES*
(multiple-value-bind
(second minute hour date month year day-of-week dst-p tz)
(get-decoded-time)
(format t "Сейчас ~2,'0d:~2,'od:~2,'od, ~a, ~d/~2,'0d/~d (GMT~@d)"
hour
minute
second
(nth day-of-week *day-names*)
date
month
year
(- tz)))
Сейчас 12:12:12, понедельник, 7/09/2009 (GMT-3)
3) Функция decode-universal-time тоже может выполнять полезную работу, например, для расшифровки любого (а не только текущего) времени. Обратное преобразование выполняет функция encode-universal-time, которая принимает шесть обязательных аргументов (секунда, минута, час, день, месяц, год), а также один опционный - временную зону. Если временная зона не указана явно, флаг летнего времени устанавливается автоматически.
(encode-universal-time 27 53 11 3 9 2009)
3460967607
Так как universal time в лиспе - это обычное число, то работать с ним удобнее и предпочтительнее, чем с календарной датой. Заодно решается проблема вычисления вычисления времени относительно какого-то события:
(setq *moon* (encode-universal-time 0 17 16 20 7 1969 4))
2194805820
(setq *takeoff* (encode-universal-time 0 38 11 28 1 1986 5))
271630308
(- *takeoff* *moon*)
521497260
Внутреннее время
Лисп измеряет внутреннее время, используя системные часы. В отличие от мирового, пользователь сам назначает точку его отсчета. Это может быть момент старта компьютера, лисп-системы или любой другой момент в прошлом. Из этого следует, внутреннее время полезно не столько само по себе, сколько для измерения относительных интервалов.
Еще одно отличие внутреннего времени в том, что оно измеряется не в секундах, а в других единицах (обычно меньших секунды). Количество этих промежутков в секунде определяется переменной internal-time-units-per-second:
internal-time-units-per-second
1000
Это значит, что этот условный интервал равен одной миллисекунде.
Всё-таки в Лисп-среде под "внутренним временем" подразумевают две вещи - с одной стороны это то же, что и "реальное время" из предыдущего раздела, но выраженное в более точных единицах, с другой - CPU время, то есть время, в течении которого процессор занимался вычислением задач, связанных с текущим процессом Лисп-среды. так как в современных компьютерах используются системы разделения времени, время процессора всегда будет меньше реального времени, потому что часть его будет отдана под другие задачи (работа других программ, обработка прерываний с клавиатуры). Внутреннее время в первом смысле можно получить с помощью функции get-internal-real-time, а время с точки зрения процессора с get-internal-run-time. Небольшой пример, чтобы прояснить разницу:
(let ((real1 (get-internal-real-time))
(run1 (get-internal-run-time)))
;; Рабочий код тут
(let ((run2 (get-internal-run-time))
(real2 (get-internal-real-time)))
(format t "Вычисления заняли:~%")
(format t " ~f секунд реального времени~%"
(/ (- real2 real1) internal-time-units-per-second))
(format t " ~f секунд процессорного времени~%"
(/ (- run2 run1) internal-time-units-per-second))))
Хороший способ увидеть разницу между реальным временем и временем выполнения - проверить код выше с помощью функции sleep, которая прерывает выполнение на указанное количество секунд:
(let ((real1 (get-internal-real-time))
(run1 (get-internal-run-time)))
(sleep 10)
(let ((run2 (get-internal-run-time))
(real2 (get-internal-real-time)))
(format t "Вычисления заняли:~%")
(format t " ~f секунд реального времени~%"
(/ (- real2 real1) internal-time-units-per-second))
(format t " ~f секунд процессорного времени~%"
(/ (- run2 run1) internal-time-units-per-second))))
В результате время выполнения должно быть близко к нулю (процесс "спал"), а реальное время - к 10 секундам.
Обобщим этот метод в макрос timing, который принимает формы для выполнения и выводит статистику времени:
(defmacro timing (&body forms)
(let ((real1 (gensym))
(real2 (gensym))
(run1 (gensym))
(run2 (gensym))
(result (gensym)))
`(let* ((,real1 (get-internal-real-time))
(,run1 (get-internal-run-time))
(,result (progn ,@forms))
(,run2 (get-internal-run-time))
(,real2 (get-internal-real-time)))
(format *debug-io* ";;; Вычисления заняли:~%")
(format *debug-io* ";;; ~f секунд реального времени, ~%"
(/ (- ,real2 ,real1) internal-time-units-per-second))
(format t ";;; ~f секунд процессорного времени.~%"
(/ (- ,run2 ,run1) internal-time-units-per-second))
,result)))
; проверяем:
(timing (sleep 1))
;;; Вычисления заняли: 0.994 секунд реального времени, 0.0 секунд процессорного времени.
NIL
Встроенный макрос time делает примерно то же, но он также выводит статистику по использованию памяти, времени, потраченного на сборку мусора, и т.д - вывод может отличатся в разных реализациях. Например, статистика о ресурсах, потраченных на сортировку миллиона действительных чисел на Clisp:
(let ((numbers (loop for i from 1 to 1000000 collect (random 1.0)))) (time (sort numbers #'<)) nil)
Real time: 9.84375 seс
Run time: 9.84375 sec
Space: 4000000 Bytes
И на SBCL:
(let ((numbers (loop for i from 1 to 1000000 collect (random 1.0))))
(time (sort numbers #'<)) nil)
Evaluation took:
6.704 seconds of real time
6.703125 seconds of total run time (6.546875 user, 0.156250 system)
99.99% CPU
17,005,975,479 processor cycles
0 bytes consed
NIL
Пример : вычисление дня недели
В разделе о мировом времени было показано, как вычислить день недели. Однако встроенные функции работают только с датами, позже чем 1 января 1900 года. Чтобы устранить этот недостаток, можно использовать такую функцию:
(defun day-of-week (day month year)
"Возвращает день недели 0 - воскресенье, 1 -понедельник, и т.д.
Работает для дат > 1752 года."
(let ((offset '(0 3 2 5 0 3 5 1 4 6 2 4)))
(when (< month 3)
(decf year 1))
(mod
(truncate (+ year
(/ year 4)
(/ (- year)
100)
(/ year 400)
(nth (1- month) offset)
day
-1))
7)))
(оригинальная версия - Gerald Doussot на comp.lang.c FAQ).
Библиотеки
Для работы с временем также могу оказаться полезными следующие библиотеки: