逆引きxyzzy lisp(日付と時刻) - circleratio/xyzzy GitHub Wiki

目次

日付と時刻

現在の時刻・日付を取得する

ユニバーサルタイム形式(1900年1月1日0時0分0秒からの秒数)での取得なら下記。

(get-universal-time)
=> 3498307254

月、日、時、分、秒などを分けて取得するなら get-decoded-time を使う。 この関数は多値を返すので、multiple-value-list マクロでリストにすると扱いやすい。

(multiple-value-list (get-decoded-time))
=> (45 21 21 5 2 2011 5 nil -9)

値は下記の順序: second, minute, hour, date, month, year, day of week (0 = Monday), T (daylight savings times) or NIL (standard time), and timezone.

時刻を任意のフォーマットで扱う

(format-date-string "%Y/%m/%d")

optional引数として時間を指定可能. 下記は24時間前の日付を表示する例.

(format-date-string "%Y/%m/%d" (- (get-universal-time) (* 60 60 24))

フォーマットとして指定できるのは下記。 :%a: 短い形式の曜日 :%A: 長い形式の曜日 :%b: 短い形式の月 :%B: 長い形式の月 :%d: 日(00~31) :%e: 和暦の年(01~) :%E: 和暦の年(元, 02~) :%g: 元号(明治,大正,昭和,平成) :%G: 元号(M, T, S, H) :%H: 時(00~23) :%I: 12時間の時(01~12) :%i: Internet Time(000~999) :%m: 月(01~12) :%M: 分(00~59) :%p: 午前/午後 :%P: AM/PM :%S: 秒(00~59) :%v: 曜日(日本語) :%y: 年(2桁) :%Y: 年(4桁) :%z: タイムゾーン名(JST-9) :%Z: タイムゾーン(+0900)

時刻に任意の時間を加減する

ユニバーサルタイム形式にしてから任意の秒数を加減すればよい。

(format-date-string
 "%Y/%m/%d"
 (+ (encode-universal-time 30 59 23 2 5 2011) ; 2011年5月2日 23時59分30秒
    (* 24 60 60)))
=> "2011/05/03"

2つの時刻の差を求める

ユニバーサルタイム形式に変換してから差を取ればよい。 次の例は元旦から5月2日までの日数を求めたもの。

(truncate (/ (- (encode-universal-time 0 0 12 2 5 2011) ; 2011年5月2日 12時0分0秒
		(encode-universal-time 0 0 0 1 1 2011)) ; 2011年1月1日 0時0分0秒
	     (* 24 60 60.0)))
=> 121
   0.5

時刻中の曜日を日本語に変換する

xyzzy の日付フォーマット指定は強力で、日本語の曜日が指示できる。

(format-date-string "%v")
=> "土"

他のLisp実装系で、これができない環境では下記のようにする。

(nth (multiple-value-bind
	 (second minute hour day month year day-of-week daylight-p tz) ; day-of-week は 0(月曜)から始まる
	 (get-decoded-time)
       day-of-week)
     '("月" "火" "水" "木" "金" "土" "日"))
=> "土"

指定の日付が存在するかどうか調べる

encode-universal-time ではチェックできない。 次の例では日付の範囲がおかしいが、それでもエラーにならず、ユニバーサルタイムの値が返ってくる。

(encode-universal-time 0 0 0 33 1 2001) ; 2001年1月33日
=> 3190028400

しかたがないので真面目にチェックする(ただし紀元前は対象外)。 うるう年には注意。

(defun valid-date (year month day)
  (and (>= year 0)
       (>= month 1)
       (<= month 12)
       (>= day 1)
       (let ((end-of-month
	      (+ (nth (1- month) '(31 28 31 30 31 60 31 31 30 31 30 31))
		 (if (and (eql month 2) (leap-year-p year))
		     1
		   0))))
	 (<= day end-of-month))))

(defun leap-year-p (year)
  (or (and (eql (mod year 4) 0)
	   (not (eql (mod year 100) 0)))
      (eql (mod year 400) 0)))

(valid-date 2100 2 29) ; 平年
=> nil

(valid-date 2000 2 29) ; うるう年
=> t

(valid-date 2011 1 32) ; 日付の範囲がおかしい
=> nil

何日後、何日前の日付を求める

ユニバーサルタイムで計算する。

(defun date-diff-day (year month day diff-day) ; 年月日および差となる日数(負の値もOK)
  (multiple-value-bind
      (s m h d m y dow daylight tz)
      (decode-universal-time
       (+ (encode-universal-time 0 0 0 day month year)
	  (* diff-day 24 60 60)))
    (values y m d)))

(date-diff-day 2011 2 5 -5)
=> 2011
   1
   31

何ヶ月後、何ヶ月前の日付を求める

剰余を使って計算。 ただし、月ごとに末日が異なる点に注意が必要(1月31日の1ヵ月後を「2月31日」とするわけにはいかない)。 以下の処理では、日数オーバーの場合は単純に切り詰めるようにした(2月31日→2月28日/29日(うるう年の場合))。

(defun date-diff-month (year month day diff-month)
  (let* ((remainder (mod (+ diff-month month) 12))
	 (quotient (/ (- (+ diff-month month) remainder) 12))
	 (result-year (+ year quotient))
	 (result-month (if (eql remainder 0) 12 remainder))
	 (result-day (adjust-end-of-month result-year result-month day)))
    (values result-year result-month result-day)))

(defun adjust-end-of-month (year month day)
  (let ((end-of-month
	 (+ (nth (1- month) '(31 28 31 30 31 60 31 31 30 31 30 31))
	    (if (and (eql month 2) (leap-year-p year))
		1
	      0))))
    (if (<= day end-of-month)
	day
      end-of-month)))

(defun leap-year-p (year)
  (or (and (eql (mod year 4) 0)
	   (not (eql (mod year 100) 0)))
      (eql (mod year 400) 0)))

(date-diff-month 2010 1 31 1)
=> 2010
   2
   28

(date-diff-month 2010 1 31 -14)
=> 2008
   11
   30

うるう年かどうか判定する

定義に従って判定する。

(defun leap-year-p (year)
  (or (and (eql (mod year 4) 0)
	   (not (eql (mod year 100) 0)))
      (eql (mod year 400) 0)))
leap-year-p

(leap-year-p 2000)
=> t

(leap-year-p 2010)
=> nil

(leap-year-p 2012)
=> t

(leap-year-p 2100)
=> nil

文字列の日付を日付オブジェクトに変換する

(defun string-date (str)
	(if (string-match "\\([0-9]\\{4\\}\\)/\\([0-9]\\{1,2\\}\\)/\\([0-9]\\{1,2\\}\\)" str)
			(let ((y (read-from-string (match-string 1)))
						(m (read-from-string (match-string 2)))
						(d (read-from-string (match-string 3))))
				(encode-universal-time 0 0 0 d m y))))

(string-date "2011/4/20")
=> 3512214000

(decode-universal-time 3512214000)
=> 0
   0
   0
   20
   4
   2011
   2
   nil
   -9

タイマー処理

ある時間ごとに処理を行う

5秒おきに timer-func を実行する(そして止める)には次のようにする。

(defun timer-func ()
  (message "test!"))

(start-timer 5 'timer-func)
=> t

(stop-timer 'timer-func)
=> t

1回だけ実行するなら、

(start-timer 5 'timer-func t)
=> t

とする。

一定時間待つ

キー入力があった場合に動作再開するなら sit-for, 待ったままにするなら sleep-for を使う。

(sit-for 3)
=> nil

(sleep-for 3)
=> nil

関数の実行時間を測定する

ミリ秒単位で時間を取得できるのは(たぶん)get-internal-real-time のみ。

(defun count-time (func &rest args)
  (let ((stime (get-internal-real-time)))
    (apply func args)
    (/ (- (get-internal-real-time) stime) 1000.0)))

(count-time #'sit-for 3)
=> 3.027
⚠️ **GitHub.com Fallback** ⚠️