Help us understand the problem. What is going on with this article?

人類滅亡までのカウントダウンを Emacs に表示する

More than 5 years have passed since last update.

はじめに

全国 1000 万人の emacs フリークの皆さん、こんにちは。
人類滅亡が目の前に迫った昨今いかがお過ごしでしょうか。

さて、2012年人類滅亡説 - Wikipediaによりますと、
人類は今年の 12/21 - 23 頃に滅びるそうです。
いったい残す所あと何日あるんでしょうね。見当もつきません。
elisp で計算しましょう。

そもそも日時・時刻の表現ってどうやるの

time 構造体ともいうべきもので表現します。
以下のようなリストです。

(HIGH LOW MICROSECOND)

これは何かというと、UNIX タイムです。
HIGH は秒数の上位 16bit の、LOW は下位 16bit を表します。
MICROSECOND はそのままです。システムによっては 0 固定になります。
ゆえに、MICROSECOND は省略されることもあります。

具体的な値を見るには以下の関数を実行してみるとよいです。
ちなみに、適当なバッファに elisp を書いて、
その後ろで C-u C-x で実行結果がバッファに書かれます。
便利ですよ。

(current-time) ; => (20680 38417 442000)

この他に、もう少し人が扱いやすい表現方法もあります。
以下のようなリストです。

(SEC MIN HOUR DAY MON YEAR DOW DST TZ)

これはわかりやすいですね。
基本的に書いてある通りそのまんまです。
DOW は Day of Week の略で、曜日を表す 0-6 の整数です。
DST サマータイムに t、そうでない場合は nil となります。日本だとあんま関係無いですね。
TZ はグリニッジ標準時からのオフセット秒数を表す整数です。

具体的な値を見るには以下の関数を実行してみてください。

(decode-time) ; => (46 37 23 12 12 2012 3 nil 32400)
;; ちなみに 324000 は
(/ 32400 60 60) ; => 9
;; +9 時間ってことですね

ちなみに decode-time は (HIGH LOW MICROSECOND) を
(SEC MIN HOUR DAY MON YEAR DOW DST TZ) 形式のリストに変換する関数で、
引数を省略した場合は (decode-time (current-time)) と同義になります。

懸命な読者の皆様であれば decode-time があるなら encode-time もあるだろうと察しが付くと思いますが、そのとおり。
decode-time とは逆で、SEC MIN HOUR DAY MON YEAR DOW DST TZ を引数に取って (HIGH LOW MICROSECOND) 形式に変換します。

(encode-time 46 37 23 12 12 2012 3 nil 32400) ; => (20680 38586)
;; もちろん decode すると、
(decode-time '(20680 38586)) ; => (46 37 23 12 12 2012 3 nil 32400)
;; となります

時刻を扱う関数はたいてい、このようなリストを引数にとったり、返したりします。
手で書くのはしんどいので、上に上げたような関数の他にも幾つか便利な関数が定義されています。

parse-time-string

文字列をパースして decode-time 形式にしてくれます。

(parse-time-string "2012-12-21") ; => (nil nil nil 21 12 2012 nil nil nil)
(parse-time-string "2012-12-21 23:59:59") ; => (59 59 23 21 12 2012 nil nil nil)

どんなフォーマットをパース出来るかはソース嫁状態なのですが、
とりあえず MySQL DATETIME 的なのは解釈できるようです。

date-to-time

文字列をパースして time 形式にしてくれます。

(date-to-time "2012-12-21 00:00:00") ; => (20691 10224)

これもどんなフォーマットをパース出来るかはソース嫁です。
とりあえず時刻は省略出来なさそうです。

日時・時刻を見やすく整形

計算するのに便利でもこんな内部表現は人が読むに絶えません。
見やすく整形するには以下の関数を使います。

;; time 形式を引数に取ります
(format-time-string "%Y-%m-%d %H:%M:%S" (current-time)); => "2012-12-13 00:11:48"
;; decode-time 形式は encode してから渡します
(format-time-string "%Y-%m-%d %H:%M:%S" (apply #'encode-time (decode-time))); => "2012-12-13 00:13:11"

こちらはどんな書式文字列が使えるか、ヘルプに詳しく載っています。
describe-function しましょう。

時刻を秒に換算

time 形式よりも単純に秒数になっていたほうがなにかと計算しやすいです。
time を秒に変換するには以下のようにします。

(float-time (current-time)) ; => 1355332701.567

日付・時刻計算

ある時刻から n 日後の時刻を取得

たとえば、今日から 10 日後の日付を取得したいとしましょう。
以下のようにします。

(format-time-string "%Y-%m-%d" (time-add (current-time) (days-to-time 10))) ; => "2012-12-23"

time-add と days-to-time を組み合わせると可能です。

time-add はその名の通り、time + time を計算します。
time-subtract という time - time を計算する関数もあります。

days-to-time もその名の通り、日数を time 形式(要は秒数換算)に変換して返します。

ある日付からある日付の間に何日あるかを取得

たとえば、1999年7月、空から恐怖の大王が降りてきてたらしい日から今日まで何日経過しているか数えてみましょう。
以下のようにします。

(days-between (format-time-string "%Y-%m-%d %H:%M:%S" (current-time)) "1999-07-01 00:00:00") ; => 4914

4914 日も生き延びることができていることに感謝しなければなりませんね。
でももうすぐ人類滅亡です。

ある時刻になったらある関数を実行する

ちょっと日付計算とは離れますが、タイマー実行のようなことができます。
たとえば、1 週間の間、毎日 15 時にメッセージを表示したい場合、以下のようにします。

(setq oyatsu-timer
    (run-at-time "15:00pm" 7 #'message "おやつの時間です"))

これを実行すると、午後 15 時におやつの時間をミニバッファで 7 日間知らせてくれます。

run-at-time はタイマーオブジェクトを返します。
これは cancel-timer の引数に渡すことで、タイマー実行をキャンセルすることができます。
逆に言うと、run-at-time の返り値を保存しておかないとキャンセルできません
(cancel-function-timers で、指定した関数を実行しているタイマーをすべて止める、ということは出来ます)。

run-at-time の第二引数には、以下のものが渡せます。

  1. "11:23pm" のような時刻絶対指定
  2. "2 hours 35 minutes" のような時刻相対指定
  3. 現在時刻からの秒数
  4. time 形式
  5. nil で即時実行
  6. t にすると、第三引数に与えた秒数ごとに無限に繰り返し

第三引数はタイマーの繰り返し回数を指定しますが、
第二引数に t が指定された場合、実行間隔秒数を表します。

そして、第四引数に実行したい関数、以降にはその関数に渡したい引数を指定します。

人類滅亡へのカウントダウン

それでは人類滅亡までの短い時間を有意義に過ごすため、
以下のような関数を定義して実行しましょう。
下記を適当なバッファにコピペして評価しましょう。

(defvar metsubo-countdown-timer nil)
(defun start-metsubo-countdown ()
  (interactive)
    (setq metsubo-countdown-timer
          (run-at-time
           t 1
           (lambda ()
             (let* ((parse-time-zoneinfo (cons `("jst" ,(* +9 3600)) parse-time-zoneinfo))
                    (judgement-time "2012-12-21 00:00:00 JST")
                    (time-remain
                     (float-time
                      (time-subtract (date-to-time judgement-time) (current-time))))
                    (seconds-remain (mod time-remain 60))
                    (minutes-remain (mod (/ time-remain 60) 60))
                    (hours-remain (mod (/ time-remain 60 60) 24))
                    (days-remain (/ time-remain 24 60 60)))
               (message "人類滅亡まであと %d 日 %d 時間 %d 分 %d 秒です。"
                        days-remain hours-remain minutes-remain seconds-remain))))))

(defun stop-metsubo-countdown ()
  (interactive)
  (when metsubo-countdown-timer
    (cancel-timer metsubo-countdown-timer)))

(start-metsubo-countdown)

おわりに

雑なネタで申し訳ありませんでした。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away