目的
Linux 0.01 のカーネルには、CMOS などから取得した日付・時刻(1900 年始まり)を UNIX 時間(1970 年1月1日0時0分0秒からの経過秒数)に変換するコードがあります。
この記事では、その変換の仕組みを理解することを目的としています。
仕組み
Linux 0.01 の kernel/mktime.c にある kernel_mktime() は、CMOS などから取得した日付・時刻を UNIX 時間(1970年1月1日0時0分0秒からの経過秒数) に変換する関数です。
1. 年の計算
tm->tm_yearは1900 年からの年数を表すので、UNIX時間の基準である1970年からの差を計算します:
year = tm->tm_year - 70;
2. 閏年の補正
1 年は通常365日ですが、4年ごとに閏年(366 日)があります。
res = YEAR * year + DAY * ((year + 1) / 4);
ここで(year + 1)/4は閏年の日数を加算しています。
3. 月の加算
月ごとの累積日数をmonth[]配列で計算して加算します。
2月以降かつ閏年でない場合は1日分を引いて調整します:
if (tm->tm_mon > 1 && ((year + 2) % 4))
res -= DAY;
4. 日・時・分・秒の加算
残りの tm_mday, tm_hour, tm_min, tm_sec を秒に換算して足し込みます:
res += DAY * (tm->tm_mday - 1);
res += HOUR * tm->tm_hour;
res += MINUTE * tm->tm_min;
res += tm->tm_sec;
結果として、与えられた日付・時刻を UNIX 時間に変換して返します。
動作
test.c
#include <stdio.h>
#include <time.h>
#define MINUTE 60
#define HOUR (60 * MINUTE)
#define DAY (24 * HOUR)
#define YEAR (365 * DAY)
/*
その月が始まるまでに経過した秒数の累積値
一旦毎年閏年と仮定する
*/
static int month[12] = {
0, // 1月
DAY * (31), // 2月
DAY * (31 + 29), // 3月
DAY * (31 + 29 + 31), // 4月
DAY * (31 + 29 + 31 + 30), // 5月
DAY * (31 + 29 + 31 + 30 + 31), // 6月
DAY * (31 + 29 + 31 + 30 + 31 + 30), // 7月
DAY * (31 + 29 + 31 + 30 + 31 + 30 + 31), // 8月
DAY * (31 + 29 + 31 + 30 + 31 + 30 + 31 + 31), // 9月
DAY * (31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30), // 10月
DAY * (31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31), // 11月
DAY * (31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30) // 12月
};
long kernel_mktime(struct tm *tm)
{
long res;
int year;
year = tm->tm_year - 70;
res = YEAR * year + DAY * ((year + 1) / 4);
res += month[tm->tm_mon];
if (tm->tm_mon > 1 && ((year + 2) % 4))
res -= DAY;
// 残りの tm_mday, tm_hour, tm_min, tm_sec を秒に換算して足し込みます
res += DAY * (tm->tm_mday - 1);
res += HOUR * tm->tm_hour;
res += MINUTE * tm->tm_min;
res += tm->tm_sec;
return res;
}
int main(void)
{
struct tm t;
/* :2024-01-20 12:34:56 */
t.tm_year = 2024 - 1900; // struct tm は1900から始まる
t.tm_mon = 0; // 0=1月,11=12月
t.tm_mday = 20;
t.tm_hour = 12;
t.tm_min = 34;
t.tm_sec = 56;
long sec = kernel_mktime(&t);
printf("Unix time = %ld\n", sec);
return 0;
}
test@test-fujitsu:~/kaihatsu$ gcc test.c -o test
test@test-fujitsu:~/kaihatsu$ ./test
Unix time = 1705754096
test@test-fujitsu:~/kaihatsu$ date +%s -d "2024-01-20 12:34:56"
1705725296