年月日と通日を自前で変換

西暦（グレゴリオ暦）と通日（連続した日数）の変換。

# 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 |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| １月 〜 　６月 | 31 | 28(29) | 31 | 30 | 31 | 30 |
| ７月 〜 １２月 | 31 | 31 | 30 | 31 | 30 | 31 |

ですが、3月を先頭にすると

| 月 : 上 / 下 | 3 / 9 | 4 / 10 | 5 / 11 | 6 / 12 | 7 / 1 | 8 / 2 |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| ３月 〜 ８月 | 31 | 30 | 31 | 30 | 31 | 31 |
| ９月 〜 ２月 | 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 |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| ３月 〜 ８月 | 0 | 31 | 61 | 92 | 122 | 153 |
| ９月 〜 ２月 | 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 |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| ３月 〜 ８月 | 0 | 1 | 1 | 2 | 2 | 3 |
| ９月 〜 ２月 | 4 | 4 | 5 | 5 | 6 | 7 |

となるので、(３＋２）ビットの小数部を継ぎ足した固定小数演算を使い

```C:C
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日 と同じ結果になります。

```C:C
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$ への変換**

```C:C
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;
    }
}
```

# 休日
ほとんどの休日は日付から判断できますが、「春分の日」と「秋分の日」は例外で、国立天文台から発表されます。精度の高い、地球の公転周期と、過去の春分点・秋分点の時刻を使うと、目安は求められます。将来は分かりませんが、近年は計算通りになっているようです。

カレンダーで「春分の日」と「秋分の日」を計算で求めようとする([メモ：春分・秋分の日を大雑把に求める](https://qiita.com/ikiuo/items/565a0f2ee455ec6d653a))と、通日が欲しくなります。