--- title: カレンダー関係の手続きを作ってみました author: kazu634 date: 2009-03-15 url: /2009/03/15/_1211/ wordtwit_post_info: - 'O:8:"stdClass":13:{s:6:"manual";b:0;s:11:"tweet_times";i:1;s:5:"delay";i:0;s:7:"enabled";i:1;s:10:"separation";s:2:"60";s:7:"version";s:3:"3.7";s:14:"tweet_template";b:0;s:6:"status";i:2;s:6:"result";a:0:{}s:13:"tweet_counter";i:2;s:13:"tweet_log_ids";a:1:{i:0;i:4527;}s:9:"hash_tags";a:0:{}s:8:"accounts";a:1:{i:0;s:7:"kazu634";}}' categories: - gauche - Lisp ---

車輪の再発名であることはわかっていますが、

をgaucheで作成しました。背景としては、Perlで作ったスクリプトをgaucheに移植しよう考えています。PerlだとDate::Simpleというモジュールがあって、それがすべてバックグラウンドで処理してくれていたのですが、今回は一から作ってみることにしました。

うるう年の判定

Wikipediaにはこんな風に書かれています:

次の規則に従って400年に97回の閏年が設けられる。1暦年は平均365.2425日(365日と5時間49分12秒)で、約3320年に1日の割合で暦と季節がずれる。

  1. 西暦年が4で割り切れる年は閏年
  2. ただし、西暦年が100で割り切れる年は平年
  3. ただし、西暦年が400で割り切れる年は閏年

閏年 – Wikipedia

というわけで、これを単純に置き換えてみました:

(define (isleap? year)
(cond
[(= (remainder year 400) ) "Leap"]
[(= (remainder year 100) ) "Not Leap"]
[(= (remainder year 4)  ) "Leap"]
[else "Not Leap"]))

これは簡単でした。

曜日の判定

曜日の判定は「ツェラーの公式」というのがあるそうです:

ツェラーの公式(Zeller’s congruence)は、西暦の年、月、日からその日が何曜日であるかを算出する公式である。

まず、求めたい日の年の下2桁を削ったもの(年/100の小数点以下切り捨て)をJ、年の下2桁(年 mod 100)をK、月をm、日をq、曜日をhとする。ただし求めたい日の月が1月、2月の場合はそれぞれ前年の13月、14月とする(例えば、2007年1月1日なら2006年13月1日と考える)。

http://upload.wikimedia.org/math/2/7/8/278e8aed83117a55800e1ed99c6dbe0a.png

ツェラーの公式 – Wikipedia

これをgaucheで置き換えてみました:

(define (day_of_week? year month day)
;; 求めたい日の月が1月、2月の場合はそれぞれ前年の13月、14月とする
(when (= month 1)
(set! month (+ month 12))
(set! year (- year 1)))
(when (= month 2)
(set! month (+ month 12))
(set! year (- year 1)))
;; ツェラーの公式
(let*
((j (quotient year 100))	;作業用の変数
(k (modulo year 100))		;作業用の変数
(result			;ツェラーの公式で計算している部分
(modulo (- (+ day (quotient (* (+ month 1) 26) 10) k (quotient k 4) (quotient j 4)) (* j 2)) 7)))
;; PerlのDate::Simpleのdays_of_weekメソッドと同じ返り値に変更
;; ツェラーの公式は「0なら土曜日、1なら日曜日、2なら月曜日、……、6なら金曜日」
;; これを「0なら日曜日、1なら月曜日、2なら火曜日、……、6なら土曜日」に変更
(if (= result ) 6
(- result 1))
))

詰まった点は2点ありました。一つ目はgauche(lisp)では変数は非破壊的に扱われるいうことです。具体的に言うと、

(when (= month 1)
(set! month (+ month 12))
(set! year (- year 1)))

この部分をこんな風に書いていました:

(when (= month 1)
(+ month 12)
(- year 1))

これだとmonth, yearの値が変わらない。。。破壊的な変数の操作を行うset!を使うべきところでした。

二つ目はletで先に宣言した変数を次の変数で使うためにはlet*を使わなければいけないということです。具体的には、

(let*
((j (quotient year 100))	;作業用の変数
(k (modulo year 100))		;作業用の変数
(result			;ツェラーの公式で計算している部分
(modulo (- (+ day (quotient (* (+ month 1) 26) 10) k (quotient k 4) (quotient j 4)) (* j 2)) 7)))
;; PerlのDate::Simpleのdays_of_weekメソッドと同じ返り値に変更
;; ツェラーの公式は「0なら土曜日、1なら日曜日、2なら月曜日、……、6なら金曜日」
;; これを「0なら日曜日、1なら月曜日、2なら火曜日、……、6なら土曜日」に変更
(if (= result ) 6
(- result 1))
)

この部分をletにしてしまうと、resultでj,kが使えないです(正確に言うと、letの中で評価される順番は不定だから、resultが一番最初に評価されてj,kがundefになる可能性がある…ということらしい)。letの中で上から順番に評価したい場合は、let*を使えば良いとのこと。