日時と時間計測

日時の取得

現在の日時はGetLocalTime関数で取得することができます。
GetLocalTime関数は日時情報をSYSTEMTIME構造体に格納します。

void GetLocalTime(
 LPSYSTEMTIME lpSystemTime
);
現在の日時を取得しlpSystemTimeに格納する。
typedef struct _SYSTEMTIME {
 WORD wYear;
 WORD wMonth;
 WORD wDayOfWeek;
 WORD wDay;
 WORD wHour;
 WORD wMinute;
 WORD wSecond;
 WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME, *LPSYSTEMTIME;
日時の情報を格納する構造体。

これらの使い方はC言語のlocaltime関数とtm構造体に似ています。

wYearメンバは西暦です。
wMonthメンバは月です。
C言語のlocaltime関数では「0~11」でしたが、このGetLocalTime関数は「1~12」を取得します。
wDayOfWeekメンバは曜日です。
日曜日は0、月曜日は1、火曜日は2…と続きます。
wDayメンバは日付です。
wHourメンバは時間です。
wMinuteメンバは分です。
wSecondメンバは秒です。
wMillisecondsメンバはミリ秒(1/1000秒)です。


#include <windows.h>
#include <strsafe.h>

//ウィンドウの生成等は省略

#define BUFFERSIZE 32

//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	HDC hdc;
	PAINTSTRUCT ps;
	RECT rt;
	SYSTEMTIME st;

	static const WCHAR* format =
		L"%04d年 %02d月 %02d日 %s曜日\n"
		L"%02d時 %02d分 %02d秒";
	static const WCHAR *dayOfWeek[] =
	{ L"日", L"月", L"火", L"水", L"木", L"金", L"土" };
	static WCHAR buf[BUFFERSIZE];

	switch (message)
	{
	case WM_CREATE: //ウィンドウ作成
		GetLocalTime(&st);
		StringCchPrintf(buf, BUFFERSIZE, format,
			st.wYear, st.wMonth, st.wDay, dayOfWeek[st.wDayOfWeek],
			st.wHour, st.wMinute, st.wSecond);
		break;

	case WM_PAINT: //ウィンドウ描画
		GetClientRect(hWnd, &rt);
		hdc = BeginPaint(hWnd, &ps);

		DrawText(hdc, buf, -1, &rt, DT_WORDBREAK);

		EndPaint(hWnd, &ps);
		break;

	case WM_DESTROY: //ウィンドウの破棄
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

このコードはアプリ起動時の日時を表示します。
日時の取得

ちなみに現在の日時を設定するにはSetLocalTime関数を使用します。
使い方は日時をセットしたSYSTEMTIME構造体変数を引数にポインタで渡すだけです。
(wDayOfWeekメンバの値は無視されます)
当然ですがシステム(Windows)の日時の設定が変更されるので不用意に使用するべきではありません。
また、管理者権限で実行する必要があります。

ローカル日時(日本時間)ではなくUTC時間(協定世界時)を取得/設定する場合はGetSystemTime関数、SetSystemTime関数を使用します。
日本と協定世界時との時差は+9時間ですから、日本時間に-9時間した時間が取得/設定されます。

時間の計測

GetTickCount関数

時間の計測にはGetTickCount関数を使用します。

DWORD GetTickCount();
システムが起動してからの経過時間を取得する。
(ミリ秒)

これもC言語でのclock関数とほぼ同じです。
計測開始時の値を変数に保存しておき、計測終了時の時間からマイナスすることで経過時間を計算します。


#include <windows.h>
#include <strsafe.h>

//ウィンドウの生成等は省略

#define BUFFERSIZE 64

//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static const WCHAR* txt[] = {
		L"ストップウォッチアプリ\n画面クリックで計測開始。",
		L"計測中...\n画面クリックで計測終了。",
		L"%dミリ秒でした。\n画面クリックで再度計測開始。"
	};

	static WCHAR buf[BUFFERSIZE];
	static RECT rt;
	static DWORD startTime;
	static int state;

	HDC hdc;
	PAINTSTRUCT ps;

	switch (message)
	{
	case WM_CREATE:
		StringCchPrintf(buf, BUFFERSIZE, txt[0]);
		break;

	case WM_LBUTTONDOWN:
		switch (state)
		{
		case 0:
			state = 1;
			startTime = GetTickCount();
			StringCchPrintf(buf, BUFFERSIZE, txt[state]);
			break;
		case 1:
			state = 2;
			StringCchPrintf(buf, BUFFERSIZE, txt[state],
				GetTickCount() - startTime);
			break;
		case 2:
			state = 1;
			startTime = GetTickCount();
			StringCchPrintf(buf, BUFFERSIZE, txt[state]);
			break;
		}
		InvalidateRect(hWnd, NULL, TRUE);
		break;

	case WM_SIZE: //ウィンドウサイズ変更
		GetClientRect(hWnd, &rt);
		break;

	case WM_PAINT: //ウィンドウ描画
		hdc = BeginPaint(hWnd, &ps);

		DrawText(hdc, buf, -1, &rt, DT_WORDBREAK);

		EndPaint(hWnd, &ps);
		break;

	case WM_DESTROY: //ウィンドウの破棄
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

実行結果です。
画面をクリック(正確にはマウスダウン)することで計測を開始/停止します。
時間の計測

ウィンドウクラススタイルでダブルクリックメッセージを通知するようにしていると、素早くクリックしたときに上手く動作しないのでCS_DBLCLKSフラグを外してください。


//ウィンドウクラスの登録
ATOM MyRegisterClass(HINSTANCE hInstance)
{
	WNDCLASSEX wcex;
	wcex.cbSize = sizeof(WNDCLASSEX);
	wcex.style = CS_HREDRAW | CS_VREDRAW;// | CS_DBLCLKS;
	//↑これ

//以降省略

GetTickCount64関数

GetTickCount関数で取得できる値は、システムの起動から約49.7日が経過すると0に戻ります。
これは時間を32ビットで管理していて、約49.7日で扱えるデータ範囲の限界値を超えるためです。

この問題を回避するためにGetTickCount64関数があります。
これは名前の通り値を64ビットで管理します。
戻り値はULONGLONG型です。
(64ビット符号なし整数。計算上は数億年の時間を保存できる)

使い方はGetTickCount関数と同じです。
ただしこの関数を使用できるのはWindows Vista以降です。

timeGetTime関数

GetTickCount関数やC言語のclock関数は手軽に使用できますが、あまり精度がよくありません。
単位はミリ秒ですが、実際には10ミリ秒程度の精度だそうです。

より精度の高い計測が必要な場合はtimeGetTime関数を使用することができます。
これはシステム起動からの経過時間を1ミリ秒の精度で取得できます。

DWORD timeGetTime();
システムが起動してからの経過時間を取得する。
(ミリ秒)

この関数はwinmm.libというライブラリに定義されていて、標準ではリンクされていません。
コードの先頭に「#pragma comment(lib, "winmm.lib")」と記述してリンクするか、リンカーオプションでリンクする必要があります。


#pragma comment(lib, "winmm.lib")
#include <windows.h>

使い方はGetTickCount関数と同じです。

QueryPerformanceCounter関数

QueryPerformanceCounter関数は、より高精度な時間を取得します。
この関数は1ミリ秒よりもさらに細かい時間を扱います。

BOOL QueryPerformanceCounter(
 LARGE_INTEGER *lpPerformanceCount
);
高分解能パフォーマンスカウンターの現在の値をlpPerformanceCountに格納する。
成功した場合は0以外を、失敗した場合は0を返す。

LARGE_INTEGERというのは共用体で、64ビット符号付き整数値を扱います。
この共用体のQuadPartというメンバが実際の値を保存しています。

この関数が取得する値の精度はコンピューターの処理能力により変わります。
これを時間に変換するためにQueryPerformanceFrequency関数を使用します。

BOOL QueryPerformanceFrequency(
 LARGE_INTEGER *lpFrequency
);
高分解能パフォーマンスカウンターの周波数をlpFrequencyに格納する。

QueryPerformanceCounterで得られる値をQueryPerformanceFrequencyで得られる値で割ることで、秒単位に変換することができます。

ちなみにこれらの関数は失敗した時に0を返しますが、Windows XP以降であれば常に成功するそうです。


#include <windows.h>
#include <strsafe.h>

#define BUFFERSIZE 64

//ウィンドウの生成等は省略

//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static const WCHAR* txt[] = {
		L"ストップウォッチアプリ\n画面クリックで計測開始。",
		L"計測中...\n画面クリックで計測終了。",
		L"%f秒でした。\n画面クリックで再度計測開始。"
	};

	static WCHAR buf[BUFFERSIZE];
	static RECT rt;
	static LARGE_INTEGER qpf, startTime;
	static int state;

	HDC hdc;
	PAINTSTRUCT ps;
	LARGE_INTEGER endTime;

	switch (message)
	{
	case WM_CREATE:
		//高分解能パフォーマンスカウンターの周波数を
		//最初に取得しておく
		QueryPerformanceFrequency(&qpf);

		StringCchPrintf(buf, BUFFERSIZE, txt[0]);
		break;

	case WM_LBUTTONDOWN:
		switch (state)
		{
		case 0:
			state = 1;
			QueryPerformanceCounter(&startTime);
			StringCchPrintf(buf, BUFFERSIZE, txt[state]);
			break;
		case 1:
			state = 2;
			QueryPerformanceCounter(&endTime);
			StringCchPrintf(buf, BUFFERSIZE, txt[state],
				(double)(endTime.QuadPart - startTime.QuadPart) / qpf.QuadPart);
			break;
		case 2:
			state = 1;
			QueryPerformanceCounter(&startTime);
			StringCchPrintf(buf, BUFFERSIZE, txt[state]);
			break;
		}
		InvalidateRect(hWnd, NULL, TRUE);
		break;

	case WM_SIZE:
		GetClientRect(hWnd, &rt);
		break;

	case WM_PAINT: //ウィンドウ描画
		hdc = BeginPaint(hWnd, &ps);

		DrawText(hdc, buf, -1, &rt, DT_WORDBREAK);

		EndPaint(hWnd, &ps);
		break;

	case WM_DESTROY: //ウィンドウの破棄
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

このコードの実行結果です。
高分解能パフォーマンスカウンターの例

小数を表示するため、書式指定文字列が%dから%fに代わっていることに注意してください。
ミリ秒が欲しい場合は単純に1000を掛ければOKです。