blog/content/post/2009-03-15-00001129.md

176 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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
---
<div class="section">
<p>
車輪の再発名であることはわかっていますが、
</p>
<ul>
<li>
うるう年の判定
</li>
<li>
曜日の判定
</li>
</ul>
<p>
をgaucheで作成しました。背景としては、Perlで作ったスクリプトをgaucheに移植しよう考えています。PerlだとDate::Simpleというモジュールがあって、それがすべてバックグラウンドで処理してくれていたのですが、今回は一から作ってみることにしました。
</p>
<h4>
うるう年の判定
</h4>
<p>
Wikipediaにはこんな風に書かれています:
</p>
<blockquote title="閏年 - Wikipedia" cite="http://ja.wikipedia.org/wiki/%E9%96%8F%E5%B9%B4">
<p>
次の規則に従って400年に97回の閏年が設けられる。1暦年は平均365.2425日365日と5時間49分12秒で、約3320年に1日の割合で暦と季節がずれる。
</p>
<ol>
<li>
西暦年が4で割り切れる年は閏年
</li>
<li>
ただし、西暦年が100で割り切れる年は平年
</li>
<li>
ただし、西暦年が400で割り切れる年は閏年
</li>
</ol>
<p>
<cite><a href="http://ja.wikipedia.org/wiki/%E9%96%8F%E5%B9%B4" onclick="__gaTracker('send', 'event', 'outbound-article', 'http://ja.wikipedia.org/wiki/%E9%96%8F%E5%B9%B4', '閏年 &#8211; Wikipedia');" target="_blank">閏年 &#8211; Wikipedia</a></cite>
</p>
</blockquote>
<p>
というわけで、これを単純に置き換えてみました:
</p>
<pre class="syntax-highlight">
<span class="synSpecial">(</span>define <span class="synSpecial">(</span>isleap? year<span class="synSpecial">)</span>
<span class="synSpecial">(</span><span class="synStatement">cond</span>
[<span class="synSpecial">(</span><span class="synStatement">=</span> <span class="synSpecial">(</span>remainder year <span class="synConstant">400</span><span class="synSpecial">)</span> <span class="synConstant"></span><span class="synSpecial">)</span> <span class="synConstant">&#34;Leap&#34;</span>]
[<span class="synSpecial">(</span><span class="synStatement">=</span> <span class="synSpecial">(</span>remainder year <span class="synConstant">100</span><span class="synSpecial">)</span> <span class="synConstant"></span><span class="synSpecial">)</span> <span class="synConstant">&#34;Not Leap&#34;</span>]
[<span class="synSpecial">(</span><span class="synStatement">=</span> <span class="synSpecial">(</span>remainder year <span class="synConstant">4</span><span class="synSpecial">)</span> <span class="synConstant"></span> <span class="synSpecial">)</span> <span class="synConstant">&#34;Leap&#34;</span>]
[else <span class="synConstant">&#34;Not Leap&#34;</span>]<span class="synSpecial">))</span>
</pre>
<p>
これは簡単でした。
</p>
<h4>
曜日の判定
</h4>
<p>
曜日の判定は「ツェラーの公式」というのがあるそうです:
</p>
<blockquote title="ツェラーの公式 - Wikipedia" cite="http://ja.wikipedia.org/wiki/%E3%83%84%E3%82%A7%E3%83%A9%E3%83%BC%E3%81%AE%E5%85%AC%E5%BC%8F">
<p>
ツェラーの公式Zeller&#8217;s congruenceは、西暦の年、月、日からその日が何曜日であるかを算出する公式である。
</p>
<p>
まず、求めたい日の年の下2桁を削ったもの年/100の小数点以下切り捨てをJ、年の下2桁(年 mod 100)をK、月をm、日をq、曜日をhとする。ただし求めたい日の月が1月、2月の場合はそれぞれ前年の13月、14月とする(例えば、2007年1月1日なら2006年13月1日と考える)。
</p>
<p>
<a href="http://upload.wikimedia.org/math/2/7/8/278e8aed83117a55800e1ed99c6dbe0a.png" onclick="__gaTracker('send', 'event', 'outbound-article', 'http://upload.wikimedia.org/math/2/7/8/278e8aed83117a55800e1ed99c6dbe0a.png', '');" class="http-image" target="_blank"><img src="http://upload.wikimedia.org/math/2/7/8/278e8aed83117a55800e1ed99c6dbe0a.png" class="http-image" alt="http://upload.wikimedia.org/math/2/7/8/278e8aed83117a55800e1ed99c6dbe0a.png" /></a>
</p>
<p>
<cite><a href="http://ja.wikipedia.org/wiki/%E3%83%84%E3%82%A7%E3%83%A9%E3%83%BC%E3%81%AE%E5%85%AC%E5%BC%8F" onclick="__gaTracker('send', 'event', 'outbound-article', 'http://ja.wikipedia.org/wiki/%E3%83%84%E3%82%A7%E3%83%A9%E3%83%BC%E3%81%AE%E5%85%AC%E5%BC%8F', 'ツェラーの公式 &#8211; Wikipedia');" target="_blank">ツェラーの公式 &#8211; Wikipedia</a></cite>
</p>
</blockquote>
<p>
これをgaucheで置き換えてみました:
</p>
<pre class="syntax-highlight">
<span class="synSpecial">(</span>define <span class="synSpecial">(</span>day_of_week? year month day<span class="synSpecial">)</span>
<span class="synComment">;; 求めたい日の月が1月、2月の場合はそれぞれ前年の13月、14月とする</span>
<span class="synSpecial">(</span><span class="synStatement">when</span> <span class="synSpecial">(</span><span class="synStatement">=</span> month <span class="synConstant">1</span><span class="synSpecial">)</span>
<span class="synSpecial">(</span><span class="synStatement">set</span>! month <span class="synSpecial">(</span><span class="synStatement">+</span> month <span class="synConstant">12</span><span class="synSpecial">))</span>
<span class="synSpecial">(</span><span class="synStatement">set</span>! year <span class="synSpecial">(</span><span class="synStatement">-</span> year <span class="synConstant">1</span><span class="synSpecial">)))</span>
<span class="synSpecial">(</span><span class="synStatement">when</span> <span class="synSpecial">(</span><span class="synStatement">=</span> month <span class="synConstant">2</span><span class="synSpecial">)</span>
<span class="synSpecial">(</span><span class="synStatement">set</span>! month <span class="synSpecial">(</span><span class="synStatement">+</span> month <span class="synConstant">12</span><span class="synSpecial">))</span>
<span class="synSpecial">(</span><span class="synStatement">set</span>! year <span class="synSpecial">(</span><span class="synStatement">-</span> year <span class="synConstant">1</span><span class="synSpecial">)))</span>
<span class="synComment">;; ツェラーの公式</span>
<span class="synSpecial">(</span><span class="synStatement">let*</span>
<span class="synSpecial">((</span>j <span class="synSpecial">(</span>quotient year <span class="synConstant">100</span><span class="synSpecial">))</span> <span class="synComment">;作業用の変数</span>
<span class="synSpecial">(</span>k <span class="synSpecial">(</span>modulo year <span class="synConstant">100</span><span class="synSpecial">))</span> <span class="synComment">;作業用の変数</span>
<span class="synSpecial">(</span>result <span class="synComment">;ツェラーの公式で計算している部分</span>
<span class="synSpecial">(</span>modulo <span class="synSpecial">(</span><span class="synStatement">-</span> <span class="synSpecial">(</span><span class="synStatement">+</span> day <span class="synSpecial">(</span>quotient <span class="synSpecial">(</span><span class="synStatement">*</span> <span class="synSpecial">(</span><span class="synStatement">+</span> month <span class="synConstant">1</span><span class="synSpecial">)</span> <span class="synConstant">26</span><span class="synSpecial">)</span> <span class="synConstant">10</span><span class="synSpecial">)</span> k <span class="synSpecial">(</span>quotient k <span class="synConstant">4</span><span class="synSpecial">)</span> <span class="synSpecial">(</span>quotient j <span class="synConstant">4</span><span class="synSpecial">))</span> <span class="synSpecial">(</span><span class="synStatement">*</span> j <span class="synConstant">2</span><span class="synSpecial">))</span> <span class="synConstant">7</span><span class="synSpecial">)))</span>
<span class="synComment">;; PerlのDate::Simpleのdays_of_weekメソッドと同じ返り値に変更</span>
<span class="synComment">;; ツェラーの公式は「0なら土曜日、1なら日曜日、2なら月曜日、……、6なら金曜日」</span>
<span class="synComment">;; これを「0なら日曜日、1なら月曜日、2なら火曜日、……、6なら土曜日」に変更</span>
<span class="synSpecial">(</span><span class="synStatement">if</span> <span class="synSpecial">(</span><span class="synStatement">=</span> result <span class="synConstant"></span><span class="synSpecial">)</span> <span class="synConstant">6</span>
<span class="synSpecial">(</span><span class="synStatement">-</span> result <span class="synConstant">1</span><span class="synSpecial">))</span>
<span class="synSpecial">))</span>
</pre>
<p>
詰まった点は2点ありました。一つ目はgauche(lisp)では変数は非破壊的に扱われるいうことです。具体的に言うと、
</p>
<pre class="syntax-highlight">
<span class="synSpecial">(</span><span class="synStatement">when</span> <span class="synSpecial">(</span><span class="synStatement">=</span> month <span class="synConstant">1</span><span class="synSpecial">)</span>
<span class="synSpecial">(</span><span class="synStatement">set</span>! month <span class="synSpecial">(</span><span class="synStatement">+</span> month <span class="synConstant">12</span><span class="synSpecial">))</span>
<span class="synSpecial">(</span><span class="synStatement">set</span>! year <span class="synSpecial">(</span><span class="synStatement">-</span> year <span class="synConstant">1</span><span class="synSpecial">)))</span>
</pre>
<p>
この部分をこんな風に書いていました:
</p>
<pre class="syntax-highlight">
<span class="synSpecial">(</span><span class="synStatement">when</span> <span class="synSpecial">(</span><span class="synStatement">=</span> month <span class="synConstant">1</span><span class="synSpecial">)</span>
<span class="synSpecial">(</span><span class="synStatement">+</span> month <span class="synConstant">12</span><span class="synSpecial">)</span>
<span class="synSpecial">(</span><span class="synStatement">-</span> year <span class="synConstant">1</span><span class="synSpecial">))</span>
</pre>
<p>
これだとmonth, yearの値が変わらない。。。破壊的な変数の操作を行うset!を使うべきところでした。
</p>
<p>
二つ目はletで先に宣言した変数を次の変数で使うためにはlet*を使わなければいけないということです。具体的には、
</p>
<pre class="syntax-highlight">
<span class="synSpecial">(</span><span class="synStatement">let*</span>
<span class="synSpecial">((</span>j <span class="synSpecial">(</span>quotient year <span class="synConstant">100</span><span class="synSpecial">))</span> <span class="synComment">;作業用の変数</span>
<span class="synSpecial">(</span>k <span class="synSpecial">(</span>modulo year <span class="synConstant">100</span><span class="synSpecial">))</span> <span class="synComment">;作業用の変数</span>
<span class="synSpecial">(</span>result <span class="synComment">;ツェラーの公式で計算している部分</span>
<span class="synSpecial">(</span>modulo <span class="synSpecial">(</span><span class="synStatement">-</span> <span class="synSpecial">(</span><span class="synStatement">+</span> day <span class="synSpecial">(</span>quotient <span class="synSpecial">(</span><span class="synStatement">*</span> <span class="synSpecial">(</span><span class="synStatement">+</span> month <span class="synConstant">1</span><span class="synSpecial">)</span> <span class="synConstant">26</span><span class="synSpecial">)</span> <span class="synConstant">10</span><span class="synSpecial">)</span> k <span class="synSpecial">(</span>quotient k <span class="synConstant">4</span><span class="synSpecial">)</span> <span class="synSpecial">(</span>quotient j <span class="synConstant">4</span><span class="synSpecial">))</span> <span class="synSpecial">(</span><span class="synStatement">*</span> j <span class="synConstant">2</span><span class="synSpecial">))</span> <span class="synConstant">7</span><span class="synSpecial">)))</span>
<span class="synComment">;; PerlのDate::Simpleのdays_of_weekメソッドと同じ返り値に変更</span>
<span class="synComment">;; ツェラーの公式は「0なら土曜日、1なら日曜日、2なら月曜日、……、6なら金曜日」</span>
<span class="synComment">;; これを「0なら日曜日、1なら月曜日、2なら火曜日、……、6なら土曜日」に変更</span>
<span class="synSpecial">(</span><span class="synStatement">if</span> <span class="synSpecial">(</span><span class="synStatement">=</span> result <span class="synConstant"></span><span class="synSpecial">)</span> <span class="synConstant">6</span>
<span class="synSpecial">(</span><span class="synStatement">-</span> result <span class="synConstant">1</span><span class="synSpecial">))</span>
<span class="synSpecial">)</span>
</pre>
<p>
この部分をletにしてしまうと、resultでj,kが使えないです正確に言うと、letの中で評価される順番は不定だから、resultが一番最初に評価されてj,kがundefになる可能性がある…ということらしい。letの中で上から順番に評価したい場合は、let*を使えば良いとのこと。
</p>
</div>