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

176 lines
14 KiB
Markdown
Raw Normal View History

2019-03-31 11:00:21 +00:00
---
title: カレンダー関係の手続きを作ってみました
author: kazu634
date: 2009-03-15
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>