CalcDate.setPastDays()

------------------------------------

CalcDateクラスの根幹をなすもう一つのメソッドsetPastDays()です。「西暦1年1月1日からの日数」をセットすると同時に、それが何年何月何日に相当するのか計算します。

シグニチャは

void setPastDays(logn days)

になります。以下では729005を設定する場合を考えながら説明していきます。年月日では1996年12月12日になります。

一番最初に

pastDays = days;

で「西暦1年1月1日からの日数」をセットします。ここで考えている例ではpastDays=729005になります。

------------------------------------

次の

year = (int)(days / 365);
long tmpDays = year * 365 + (year / 4) - (year / 100) + (year / 400);

while (days <= tmpDays) {
    year--;
    tmpDays = year * 365 + (year / 4) - (year / 100) + (year / 400);
}

days -= tmpDays;
year++;

で「年」をセットしています。何をしているか順番に見てきましょう。

最初の

year = (int)(days / 365);

で「年」の近似値を計算します。この式だけでは誤った値が入る場合があります。考えている例でもyear = (int)(729005 / 365) = 1997と誤った値が求まります(729005は1996年12月12日に相当するのでyear = 1996が正解)。

誤りの原因は閏年を考慮していないからで、誤りの値は真の値より必ず大きくなります。そこで

long tmpDays = year * 365 + (year / 4) - (year / 100) + (year / 400);

while (days <= tmpDays) {
    year--;
    tmpDays = year * 365 + (year / 4) - (year / 100) + (year / 400);
}

で閏年も考慮して、正しい値を求めていきます。

tmpDaysには「西暦1年1月1日からyear年12月31日までの日数」が入り、while文の条件days <= tmpDaysが成り立つ間はyearを1ずつ引いていきます。したがってwhileループを抜けたとき、yearには「days > tmpDaysが成立する最大のyear」が入っています。

いま考えている例(days = 729005)では、ループを抜けたときにyear=1995tmpDays = 728658になっています。これは

729005は「西暦1年1月1日から1995年12月31日までの日数」よりは大きいが「西暦1年1月1日から1996年12月31日までの日数」よりは小さい。

ということを示します。ということは、729005に相当するのは1996年の何月何日かだ!ということです。

ここまで見てきて解るように、ループを抜けたあとのyearには真の値より1小さい値が入っていて、tmpDaysには前年の12月31日までの日数が入っていることになります。そこで、次の処理

days -= tmpDays;
year++;

yearを真の値にし、daysから前年の12月31日の日数を引きます。考えている例ではyear = 1996days = 347となります。

------------------------------------

上述の処理で「年」が確定したのであとは「月」と「日」を求めましょう。yearが確定してdaysには前年の12月31日までの日数が引かれて値が入っているので、考えることは「year年の1月1日からdays日目は何月何日か?」です。

まず、

for (month = 0; days > 0; days -= monthDays[month++]) {
    if (month == 1 && isLeapYear(year)) {
        days--;
    }
}

で「月」を定めます。処理の中心部は

for (month = 0; days > 0; days -= monthDays[month++]) {

に書かれています。ここでしていることは

month = 0からはじめる。days > 0が成り立つうちはdaysからmonthDays[month]を引いてmonthに1加えろ

です。真ん中の3行

if (month == 1 && isLeapYear(year)) {
    days--;
}

は、もうお馴染み閏年の処理です。

このループを抜けるとmonthには正しい「月」の値が入ります。不思議ですね〜。からくりを見てきましょう。

FORループを抜けるのは条件days > 0が成り立たなくなったとき、すなわちdays <= 0となったときです。こうなるのはdaysから「月」の最後の日までの日数を引きさったときです。

ここで考えている例ではdays = 347から「1月の日数を引き、2月の日数を引き、・・・11月の日数を引き」としていって、12月の日数を引きさったときにdays <= 0となります。つまり、

347は「1996年1月1日から1996年11月末日までの日数」よりは大きいが、「1996年1月1日から1996年12月末日までの日数」よりは小さい。

ということが解ります。すなわち、求める「月」は12です。

このときの変数monthの動きをみてみましょう。monthは0から始まっているので、12月の日数を引きさるときはmonth = 11となっています。そして

days -= monthDays[month++]

という処理で引きさるのですが、ここでは

という処理がこの順番で行なわれています。したがって12月の日数を引きさったときにはmonth = 12となり正しい値が入っているわけです。

------------------------------------

「年」「月」が求まったのであとは「日」を求めるだけです。これは簡単で

date = (int)days + monthDays[month - 1];
if (month == 2 && isLeapYear(year)) {
    date++;
}

と「月」の処理のときに引きすぎた日数を足してやるだけで良いです。足す月が閏年の2月のときは忘れずに1加えます。

------------------------------------

前の項目へ次の項目へ

「メイキング・オブ・バイオリズム」のページへ