年月日と2000年1月1日からの経過日数を相互変換



Pythonのコードですが、日付け計算ロジック検証用のプログラムです。
ロジック自体はフリースタンディング環境なC言語での実装に使用します。
たぶんPythonにはまともな日時ライブラリがあるはずなのでこんなものは不要です。

MCUの内蔵、あるいは単体デバイスのリアルタイムクロック(RTC)は、大抵内部表現が2000年1月1日を基準としたBCD形式なので、そのままの表現では日にちの差分などの計算が厄介です。

RTCから読み取った年月日を2000年1月1日基準の経過日数に換算してから計算を行えば簡単になります。
RTCの現在時刻や、アラームなどの設定はBCD形式なので年月日に戻す変換も実装しました。

備考

* 2099/12/31は36524日目なので、2000/1/1-2099/12/31は int16_t で表せる。
* コメント欄で指摘していただいたように2100年以降は正しく動きません。これは一般的なRTCが2000年1月1日から2099年12月31日までしかサポートしていないため私の用途では必要ないためです。
* ロジック検証用なのでエラー処理等は省略。
* OS環境でタイムゾーンやらネットワークでNTPとかを処理するとか考えだすとUNIX時間ベースにするべきなのだろうがハードルは高いしオーバースペックなので私はやらない。
* 本気で Python を使っている人にはごめんなさい。

```python:calendar.py
__author__ = 'forth83@gmail.com'
__version__ = '0.1'

# 以下のURLの記事を参考にした。
# Going My C Way: 2000年1月1日からの経過日数を求める
# https://cprogramming.g.hatena.ne.jp/lnznt/20110721/1311244811

# 年のうるう年判定関数
def is_leapyear(year):
    return ((year % 4) == 0) and ((year % 100) != 0) or ((year % 400) == 0)

# 年/月の最終日を計算する。
def last_day_of_month(year, month):
    if ((month == 2) and is_leapyear(year)):
        return 29
    return (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)[month]

# うるう年1月1日からの月ごとの経過日数
days_tbl = (
    # 1月, 2月, 3月, ... 12月
    0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, # うるう年
    366, 397, 425, 456, 486, 517, 547, 578, 609, 639, 670, 700, # うるう年+1
    731, 762, 790, 821, 851, 882, 912, 943, 974, 1004, 1035, 1065, # うるう年+2
    1096, 1127, 1155, 1186, 1216, 1247, 1277, 1308, 1339, 1369, 1400, 1430, # うるう年+3
    1461)

# 2000年1月1日からの経過日数から(yyyy, mm, dd)を計算する。
def to_date(elapsed_days):
    y4 = int(elapsed_days / (1 + (365 * 4)))
    d0 = elapsed_days % (1 + (365 * 4))
    m0 = int(d0 / 30)

    m1 = m0 - (1 if (d0 < days_tbl[m0]) else 0)

    year = 2000 + (y4 * 4) + int(m1 / 12)
    month = (m1 % 12) + 1
    day = (d0 - days_tbl[m1]) + 1
    return (year, month, day)

# (yyyy, mm, dd)から経過日数を計算する。
def calc_elapsed_days(date):
    y = int((date[0] - 2000) / 4)
    m = ((date[0] - 2000) % 4) * 12 + date[1] - 1
    d = date[2] - 1

    return ((y * (1 + 365 * 4)) + days_tbl[m] + d)


def check_days(date, days):
    cdays = calc_elapsed_days(date)
    result = 'OK' if (cdays == days) else 'NG'
    print("{0} is {1}. calc={2}, correct={3}".format(date, result, cdays, days));


def check_ymd(days, date):
    calc_date = to_date(days)
    result = 'OK' if (calc_date[0] == date[0] and calc_date[1] == date[1] and calc_date[2] == date[2]) else'NG'
    print("{0} is {1}. calc={2}, correct={3}".format(days, result, calc_date, date));


def main():
    print('(yyyy,mm,dd) to elapsed days')
    check_days((2000, 1, 1), 0)
    check_days((2000, 12, 31), 365)
    check_days((2001, 1, 1), 366)
    check_days((2001, 12, 31), 730)
    check_days((2002, 1, 1), 731)
    check_days((2050, 12, 31), 18627)
    check_days((2098, 12, 31), 36159)
    check_days((2099, 1, 1), 36160)
    check_days((2099, 11, 30), 36493)
    check_days((2099, 12, 1), 36494)
    check_days((2099, 12, 31), 36524)

    print('elapsed days to (yyyy,mm,dd)')
    check_ymd(0, (2000, 1, 1))
    check_ymd(365, (2000, 12, 31))
    check_ymd(366, (2001, 1, 1))
    check_ymd(730, (2001, 12, 31))
    check_ymd(731, (2002, 1, 1))
    check_ymd(18627, (2050, 12, 31))
    check_ymd(36159, (2098, 12, 31))
    check_ymd(36160, (2099, 1, 1))
    check_ymd(36493, (2099, 11, 30))
    check_ymd(36494, (2099, 12, 1))
    check_ymd(36524, (2099, 12, 31))


if __name__ == '__main__':
    main()
```