西暦(グレゴリオ暦)と通日(連続した日数)の変換。
400年周期
1年を 365.2425 日として 400年周期で計算します。
周期 | 日数 | 週(7日) + 日 | 閏日 |
---|---|---|---|
1 | 365 | 52週 + 1日 | - - |
4 | 1461 | 208週 + 5日 | あり |
100 | 36524 | 5217週 + 5日 | なし |
400 | 146097 | 20871週 + 0日 | あり |
400年間(146097日)は7日の倍数です。
3月1日を年の始めとして年内の通日を考える
各月の日数は
月 : 上 / 下 | 1 / 7 | 2 / 8 | 3 / 9 | 4 / 10 | 5 / 11 | 6 / 12 |
---|---|---|---|---|---|---|
1月 〜 6月 | 31 | 28(29) | 31 | 30 | 31 | 30 |
7月 〜 12月 | 31 | 31 | 30 | 31 | 30 | 31 |
ですが、3月を先頭にすると
月 : 上 / 下 | 3 / 9 | 4 / 10 | 5 / 11 | 6 / 12 | 7 / 1 | 8 / 2 |
---|---|---|---|---|---|---|
3月 〜 8月 | 31 | 30 | 31 | 30 | 31 | 31 |
9月 〜 2月 | 30 | 31 | 30 | 31 | 31 | 28(29) |
閏日は年末になります。
(春分頃を年始とする暦が先で、某君が自分の誕生月を年始にしたとかしてないとか...)
年始からの通日
年始を3月として、月番号 $M$ を $0$ から割り当て直すと、月始め(1日)の通日 $D$ は
$M$ : 上 / 下 | 0 / 6 | 1 / 7 | 2 / 8 | 3 / 9 | 4 / 10 | 5 / 11 |
---|---|---|---|---|---|---|
3月 〜 8月 | 0 | 31 | 61 | 92 | 122 | 153 |
9月 〜 2月 | 184 | 214 | 245 | 275 | 306 | 337 |
$D = \lfloor M \times 30.6 + 0.4 \rfloor$
で求められます。しかし、浮動小数点演算は使いたくないので、少し工夫します。
$D$ を $30$ で割ったときの余りは
$M$ : 上 / 下 | 0 / 6 | 1 / 7 | 2 / 8 | 3 / 9 | 4 / 10 | 5 / 11 |
---|---|---|---|---|---|---|
3月 〜 8月 | 0 | 1 | 1 | 2 | 2 | 3 |
9月 〜 2月 | 4 | 4 | 5 | 5 | 6 | 7 |
となるので、(3+2)ビットの小数部を継ぎ足した固定小数演算を使い
int D = (M * 979 + 15) >> 5; /* = M * 30.6 + 0.4 */
とすることができます。逆変換は、年始からの通日 $Dy$ より
$M = \lfloor (Dy + 0.4) \div 30.6 \rfloor$
$M = \lfloor \left( Dy \times 5 + 2 \right) \div 153 \rfloor$
になります。
単純な通日と日付の変換処理
- 通日を $T$、日付の「年/月/日」を「$Y/M/D$」とします
- $T,Y,M,D$ は非負の整数とします
- $Y/M/D = 0/3/1$ のとき $T=0$ とします
日付 $Y/M/D$ から通日 $T$ への変換
適用出来ない日付の調査はしません。例えば、4月31日 は 5月1日 と同じ結果になります。
unsigned int ymd2t(unsigned int Y, unsigned int M, unsigned int D)
{
unsigned int Y000 = Y - ((M < 3) ? 1 : 0); /* 年 Y を3月で補正 */
unsigned int Y004 = Y000 >> 2; /* 4年周期の閏年: Y000 / 4 */
unsigned int Y100 = (Y004 * 2621 + 2631) >> 16; /* 100年周期の閏年: Y004 / 25 */
unsigned int Y400 = Y100 >> 2; /* 400年周期の閏年: Y100 / 4 */
unsigned int Ty = Y000 * 365 + Y004 - Y100 + Y400; /* 年単位の通日 */
unsigned int M3 = (M < 3) ? (M + 12 - 3) : (M - 3); /* 月 M を3月で補正 */
unsigned int Tm = (M3 * 979 + 15) >> 5; /* 月単位の通日: M3 * 30.6 + 0.4 */
unsigned int T = Ty + Tm + (D - 1); /* 通日 */
return T;
}
通日 $T$ から日付 $Y/M/D$ への変換
void t2ymd(unsigned int *Y, unsigned int *M, unsigned int *D, unsigned int T)
{
unsigned int Y400 = T / 146097; /* 400年単位 */
unsigned int R400 = T % 146097; /* 400年内の通日 */
unsigned int Y100 = R400 / 36524; /* 100年単位 */
unsigned int R100 = R400 % 36524; /* 100年内の通日 */
unsigned int Y004 = R100 / 1461; /* 4年単位 */
unsigned int R004 = R100 % 1461; /* 4年内の通日 */
unsigned int Y001 = R004 / 365; /* 1年単位 */
unsigned int R001 = R004 % 365; /* 1年内の通日(閏日なし) */
unsigned int Leap = (Y001 | Y100) >> 2; /* 閏日 */
unsigned int Dy = Leap ? 365 : R001; /* 年始からの通日(閏日あり) */
unsigned int M3 = (Dy * 5 + 2) / 153; /* 月単位(3月=0) */
unsigned int Tm = (M3 * 979 + 15) >> 5; /* 月単位の通日: M3 * 30.6 + 0.4 */
unsigned int rY = (Y400 * 400) + (Y100 * 100) + (Y004 * 4) + (Y001 - Leap);
unsigned int rM = M3 + 3;
unsigned int rD = Dy - Tm + 1;
*Y = rY;
*M = rM;
*D = rD;
if (rM > 12)
{
*Y = rY + 1;
*M = rM - 12;
}
}
休日
ほとんどの休日は日付から判断できますが、「春分の日」と「秋分の日」は例外で、国立天文台から発表されます。精度の高い、地球の公転周期と、過去の春分点・秋分点の時刻を使うと、目安は求められます。将来は分かりませんが、近年は計算通りになっているようです。
カレンダーで「春分の日」と「秋分の日」を計算で求めようとする(メモ:春分・秋分の日を大雑把に求める)と、通日が欲しくなります。