WMIでの日時データの扱い(SWbemDateTimeとCIM_DATETIME)
水曜日, 5月 30th, 2007WMIで日時データを扱う場合は、SWbemDateTimeを利用する方法が簡単です
Delphiだと、WbemScripting_TLB を uses して、こんな感じで使えます。
procedure Sample;
var
I:Integer;
line:string;
Locator:ISWbemLocator;
Services:ISWbemServices;
ResultSet:ISWbemObjectSet;
Enum:IEnumVariant;
RowObj:OleVariant;
Value:LongWord;
wbemDateTime:ISWbemDateTime;
begin
Locator:=CoSWbemLocator.Create as ISWbemLocator;
Services:=Locator.ConnectServer(
'.', '', '', '', '', '', 0, nil);
ResultSet:=Services.ExecQuery(
'SELECT * From Win32_NTEventLog', 'WQL',
wbemFlagReturnImmediately,nil);
Enum:=ResultSet._NewEnum as IEnumVariant;
// ISWbemDateTimeオブジェクトを生成
wbemDateTime:=CosWbemDateTime.Create as
ISWbemDateTIme;
for I:=1 to ResultSet.Count do
begin
Enum.Next(1, RowObj, Value);
if Value <= 0 then Break;
if VarIsNull(RowObj) then Continue;
// WBemDateTimeに値を代入
wbemDateTime.Value:=RowObj.TimeWritten;
// フォーマット
line:=Format('%.4d/%.2d/%.2d %.2d:%.2d:%.2d.%.3d',
[wbemDateTime.Year, wbemDateTime.Month,
wbemDateTime.Day, wbemDateTime.Hours,
wbemDateTime.Minutes, wbemDateTime.Seconds,
wbemDateTime.MicroSeconds*1000]);
ShowMessage(line);
end;
end;
ISWbemDateTimeオブジェクトを作って、ValueプロパティにWQLの検索結果の日時データを設定するだけです。逆に、TDateTime型の値をISWbemDateTimeに設定/取得する場合は、GetVarDate, SetVarDateを利用します。
しかし、SWbemDateTimeクラスはWindows XP以降にしかないので、Win 2000 などでも動かそうとすると日時データを自前で処理する必要があります。
ソフト作ってるときにWinXPで動いていたのにWin2000 に持ってくと 「クラスが登録されていません」 と手がかりなしのエラーを吐かれて、SWbemDateTimeが使えないことに気づくまでかなりはまりました。
WMIで利用される日時データは、CIM_DATETIME という形式のものです。
MSDN: CIM_DATETIME
上記、MSDNのページを見ればCIM_DATETIMEが
yyyymmddHHMMSS.mmmmmmsUUU
と定義されていることが分かります。 mmmmmm はマイクロ秒、sUUUはタイムゾーンを表す数値でUTCからの差を分で表します。日本だと +540 になります。
Delphi上では、CIM_DATETIMEはwideStringとして処理されるため、SWbemDateTimeを使わなくてもCIM_DATETIMEに変換できます。
function SystemTimeToWbemDateTime(
const localTime:TSystemTime):wideString;
var
utcTime:TSystemTime;
fileTime,localFileTime: TFileTime;
begin
SystemTimeToFileTime(localTime, localFileTime);
LocalFileTimeToFileTime(localFileTime, fileTime);
FileTimeToSystemTime(fileTime, utcTime);
Result:=Format(
'%4.4u%2.2u%2.2u%2.2u%2.2u%2.2u.%6.6u+000',
[utcTime.wYear, utcTime.wMonth, utcTime.wDay,
utcTime.wHour, utcTime.wMinute, utcTime.wSecond,
utcTime.wMilliSeconds*1000]);
end;
TDateTimeだとミリ秒以下が扱えないので、ここではTSystemTimeからCIM_DATETIMEに変換してます。タイムゾーン部分を現在のタイムゾーンのUTCからの差を算出するのが面倒だったので、TFileTimeを経由してUTC時刻に変換してあります。これだと時差が +000 ですみます。
参考: How To Convert from GMT(UTC) Time to Local Time
逆に、CIM_DATETIMEからTSystemTimeに変換するには時差の扱いを考えないといけません。手順としては、
- CIM_DATETIMEの日時(年月日時分秒マイクロ秒)情報からTFileTimeオブジェクトを生成
- CIM_DATETIMEの時差情報から現在のタイムゾーンとCIM_DATETIMEのタイムゾーンとの時差(分差)を算出
- 分差に基づいてTFileTimeの値を修正
- TFileTimeからTSystemTimeに変換
といった感じでしょうか。
今回作ったソフトは時刻を表示すればよいだけだったので、CIM_DATETIMEを単純に文字列として扱ってフォーマットの変更をしました。
function FormatWbemDateTime(
const wbemDateTime:wideString):string;
begin
Result:=wbemDateTime; // yyyymmddHHMMSS.mmmmmmsUUU
Delete(Result, 19, 3); // yyyymmddHHMMSS.mmmsUUU
Insert(':', Result,13); // yyyymmddHHMM:SS.mmmsUUU
Insert(':', Result,11); // yyyymmddHH:MM:SS.mmmsUUU
Insert(' ', Result,9); // yyyymmdd HH:MM:SS.mmmsUUU
Insert('/', Result,7); // yyyymm/dd HH:MM:SS.mmmsUUU
Insert('/', Result,5); // yyyy/mm/dd HH:MM:SS.mmmsUUU
end;
まぁ、こんな逃げ方もあるということで。