LoginSignup
1
0

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-12-12

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

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)ビットの小数部を継ぎ足した固定小数演算を使い

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
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
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;
    }
}

休日

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

カレンダーで「春分の日」と「秋分の日」を計算で求めようとする(メモ:春分・秋分の日を大雑把に求める)と、通日が欲しくなります。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0