Регистрация | Войти
Lisp — программируемый язык программирования

Даты и время

Коммон Лисп предоставляет два способа узнать время: мировое(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        ; временная зона

2)

День недели закодирован цифрой от 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).

Библиотеки

Для работы с временем также могу оказаться полезными следующие библиотеки:

1)Первоночальный термин "вселенское время" кажется довольно сомнительным :)
2)Пояснение про нумерацию сделано в англоязычной версии, так как у них принято вести отсчет с воскресенья, а не понедельника. Счастливые...
3)если не против - руссифицировал немного
@2009-2013 lisper.ru