Zune 30の閏年バグ

http://d.hatena.ne.jp/taizod/20090103/p2なんだけど、あんまりおかしいので再現してみた。

(defun hoge (days &optional (year 1980))
  (while (> days 365)
  (if (is-leap-year year)
    (if (> days 366)
      (progn
        (decf days 366)
        (incf year)))
    (progn
    (decf days 365)
    (incf year))))
  (values days year))

(defun is-leap-year (year)
  (or (zerop (mod year 400))
    (and (not (zerop (mod year 100)))
       (zerop (mod year 4)))))

閏年の判定関数は適当に作った。これにformatをくっつけて回すとこんな感じ。問題になるのは閏年の12月31日だけだ。

(hoge 10593)
1980-10593 うるう年
1981-10227 
1982-9862 
1983-9497 
1984-9132 うるう年
1985-8766 
... (中略)
2006-1096 
2007-731 
2008-366 うるう年
2008-366 うるう年
2008-366 うるう年
...
Quit

マイクロソフト Zune 30GBに閏年バグ、12月31日に動作停止 (追記:gigabeatも) - Engadget 日本版で書かれているとおりの症状。なんか記述がごちゃごちゃして判り辛かったけど、バグの原因はwhile条件が「365日より多い」なのに、閏年の処理条件が「366日より多い」だから。閏年で366日だとループ内でなにもしないことになる。もちろん、日数チェックを一回だけにすればこんなことは起こらない。余計な重複はバグの温床という典型的な例だね。

(defun fuga (days &optional (year 1980))
  (let ((d (if (is-leap-year year) 366 365)))
  (if (> days d)
    (fuga (- days d) (1+ year))
    (values days year))))

仕様に沿って書くとこんな感じ。それにしても何で1を基底にしてるんだろう? 0からの方が見通しが良いと思うけど。