--- 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日の割合で暦と季節がずれる。
- 西暦年が4で割り切れる年は閏年
- ただし、西暦年が100で割り切れる年は平年
- ただし、西暦年が400で割り切れる年は閏年
というわけで、これを単純に置き換えてみました:
(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日と考える)。
これを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*を使えば良いとのこと。