[Info] VBScript / ADO RecordSet : 時刻を文字列として扱う際 “0:00:00” の場合は文字列が Drop されてしまう

本日は VBScript で時刻を扱う処理を実施する際、システム時刻の 0 時 0 分 0 秒に実行すると、日付は取れるものの時刻が返却されない現象が発生するという事象についてご報告申し上げます。

問題の概要
VBScript (WScript/CScript) の時刻を扱う処理を実施したタイミングがシステム時刻の 0 時 0 分 0 秒である場合、日付は取れるものの時刻は取り扱われません。
この現象は製品の設計上の動作であり、障害ではありません。またこれは時刻取扱を行う関数動作に起因するものではなく、Scripting Runtime 内部で時刻の戻り値を文字列に変換する部分が原因です。

再現コードおよび再現例

Wscript.Echo Now

■00:00:00の場合(現象発生時)
2010/9/6

■00:00:01の場合(正常時)
2010/9/6 00:00:01

原因
例えばコンソール に出力する、ファイルに出力するなどの動作を行う場合などは、Scripting Runtime 内部で時刻データを文字列に変換してから値を引き渡す動作になります。この際 Scripting Runtime 側での内部では結果的に VarBstrFromDate() API を使用して "時間:分:秒(hh:mm:ss)" に変換いたします。この動作の際、内部実装では、明示的に時間:分:秒がすべて 0 の場合は、これらを文字列として変換しない処理となります。

日付型のデータは内部的には浮動小数点型 (日付が整数部で時刻が小数部) で扱われますが、時刻が 0:00:00 のときは xxx.0 = xxx というように小数部を省略した形式で扱えることもあり、String 型への変換時には 0:00:00 を省略する形になります。これが結果として文字列で変換されない現象につながります。
なお、本現象は WScript のみではなく、VarBstrFromDate() API を使用するすべてのプログラムが対象となります。

以下参考例です。

///////////// コード例ここから //////////////
UDATE udate;
VARIANT vDate;
BSTR bstr;

HRESULT hr = E_FAIL;

::GetLocalTime(&udate.st);

udate.st.wHour = 0;
udate.st.wMinute = 0;
udate.st.wSecond = 0;

vDate.vt = VT_DATE;
hr = ::VarDateFromUdate(&udate,0,&vDate.date);
hr = ::VarBstrFromDate(vDate.date, 0x400, 0, &bstr);

::MessageBoxW(NULL, bstr,L"VarBstrFromDate TEST", MB_OK);
///////////// コード例ここまで //////////////

VariantChangeType Function
https://msdn.microsoft.com/en-us/library/ms221258.aspx

VarBstrFromDate
https://msdn.microsoft.com/en-us/library/ee488273.aspx

回避策
回避策としては FormatDateTime() などを用いて明示的に日付形式を設定することとなります。

Visual Basic Scripting Edition
FormatDateTime 関数
https://msdn.microsoft.com/library/ja/default.asp?url=/library/ja/script56/html/vsfctFormatDateTime.asp

その他参考情報 : ADO の RecordSet オブジェクトでも 0:00:00 が扱えない
スクリプトの例ではありませんが、ADO の Recordset オブジェクトでも同様の事例が確認されておりましたので併せてご案内いたします。
原因はスクリプトと同じく、「時刻 ⇒ 文字列変換」の際の小数点の取り扱いに起因します。

現象 :
"2010/09/06 0:00:00" のような時刻が "0:00:00" の Date 型のデータを格納すると GetRows メソッドでデータを取り出した際、時刻部分が欠落し "2010/01/14" のみの Variant 型データが取得される。Recordset オブジェクトが保持している Date 型のデータでは "0:00:00" の情報は保持されていが、GetRows メソッドで取り出し Variant 型に変換されたときに時刻部分の "0:00:00" が欠落してしまい、文字列として出力すると日付のみ表示される状況になる。

原因 :
この現象も上記のスクリプトの場合と同じです。
GetRows メソッド自体の動作によるものではなく、Date 型から String 型へのデータ型変換に起因する現象です。
例えば、Data 型変数に 時刻部分が 0:00:00 という日時を代入し、これを String 型の変数に変換する場合にも同様の動作が確認できます。

GetRows メソッドの場合、Recordset の値を二次元配列に渡す際、配列は Variant 型で定義されます。Variant 型配列内の時点では内部的には Date 型としてデータは保持されていますが。この配列値を、例えば CSV 書き出しや Response.Write で値を参照するときなど 表示のために扱おうとした際には、文字列への内部変換が発生します。このタイミングで Date 型から String 型への変換が実施され、文字列として表示する際には 0:00:00 という時刻は省略した形式に変換されます。

回避策 :
対処法としては、Format 関数の実装がございます。日付データを文字列として表示する際にどう表現するかという問題であるため、その対処としては Format 関数が適切ということになります。たとえば、文字列変換の処理が起こりそうな処理の部分で、VarType 関数でデータ型をチェックし日付型であれば Format 関数を実行するといった方法などが有効です。

VarType
https://office.microsoft.com/ja-jp/access/HA012289321041.aspx

GetRows
https://msdn.microsoft.com/ja-jp/library/cc364163.aspx

対象オペレーティング システム
この実装は本日現在 Windows XP / Windows Server 2003 / Windows Vista (Windows Server 2008) / Windows 7 (Windows Server 2008 R2) 共通となりますため、現行のオペレーティング システム上ですべて同一の動作となります。

(Script Programming Support Team - Sep 06, 2010)