Cランタイムの標準関数 localtime() について。
これは時刻をタイムゾーンに合わせて現地時刻に変換したもの取得するものだが、NULL を返すことがある。
これを忘れてエラーチェックを忘れるとランタイムエラーでプログラムが強制終了してしまう。
日時を取得する別の手段として、Windows API で処理することができる。
こちらは、localtime() 関数と違って、年数や月数の調整が不要で変数名も分かりやすいのでオススメなのだ。
void test1()
{
TCHAR szNow[ 100 ] = { 0 };
time_t long_time;
time( &long_time );
tm* t = localtime( &long_time );
if ( t != NULL ) {
// Cランタイムを使用
wsprintf( szNow, _T( "%04d/%02d/%02d %02d:%02d:%02d" )
, 1900 + t->tm_year
, 1 + t->tm_mon
, t->tm_mday
, t->tm_hour
, t->tm_min
, t->tm_sec );
}
else {
// Windows API を使用
SYSTEMTIME st;
::GetLocalTime( &st );
wsprintf( szNow, _T( "%04d/%02d/%02d %02d:%02d:%02d" )
, st.wYear
, st.wMonth
, st.wDay
, st.wHour
, st.wMinute
, st.wSecond );
}
::MessageBox( NULL, szNow, _T( "現在時刻" ), MB_OK );
}
ところで、例えば1週間前の日時を取得したいときはどうするのだろうか。
今の日時が 8 日以降なら問題ない。-7 すればいいだけなのだから。
しかしながらこの方法はそのまま採用できない。
実際には、1~7 日の場合も検討に入れねばならず、月の計算やうるう年など複雑な計算を要することもあるからである。
さて、Cランタイムを使って処理する場合、tm 構造体の中身を編集して、mktime() 関数を呼び出すと
その値を再構成してくれる。その値が 0 以下であっても適切に変換してくれるので便利だ。
ところが、これを Windows API だと、うまい方法が見当たらない。
今の所、一度 FILETIME に変換して 100 ナノ秒単位で加減して、SYSTEMTIME に再変換する
といった手順で計算しているけど、もっと良い方法はないのだろうか。
void test2()
{
TCHAR szNow[ 100 ] = { 0 };
time_t long_time;
time( &long_time );
tm* t = localtime( &long_time );
if ( t != NULL ) {
// Cランタイムを使用
t->tm_mday -= 7;
long_time = mktime( t );
t = localtime( &long_time );
if ( t != NULL ) {
wsprintf( szNow, _T( "%04d/%02d/%02d %02d:%02d:%02d" )
, 1900 + t->tm_year
, 1 + t->tm_mon
, t->tm_mday
, t->tm_hour
, t->tm_min
, t->tm_sec );
}
}
if ( t == NULL ) {
// Windows API を使用
SYSTEMTIME st;
::GetLocalTime( &st );
FILETIME ft;
ATLVERIFY( ::SystemTimeToFileTime( &st, &ft ) );
__int64 nTime = __int64( ft.dwHighDateTime ) << 32 | ft.dwLowDateTime;
nTime -= 10000000ULL /*100ns*/ * 60 /*sec*/ * 60 /*min*/ * 24 /*hour*/ * 7 /*day*/;
ft.dwHighDateTime = DWORD( nTime >> 32 );
ft.dwLowDateTime = DWORD( nTime & 0xFFFFFFFF );
ATLVERIFY( ::FileTimeToSystemTime( &ft, &st ) );
wsprintf( szNow, _T( "%04d/%02d/%02d %02d:%02d:%02d" )
, st.wYear
, st.wMonth
, st.wDay
, st.wHour
, st.wMinute
, st.wSecond );
}
::MessageBox( NULL, szNow, _T( "1週間前の時刻" ), MB_OK );
}
※なお、上記サンプルでは time() 関数で現在時刻を取得しているので、NULL になることはまずないと思われるが、
時間をファイルに読み書きしている場合に不正な時刻変換を行なおうとしてしまうことがありうる。
実はそれこそが不正終了の一因だったりして(^^;