Emptiness in COleDateTime Format VAR_TIMEVALUEONLY

Funny how you can have trouble using a function, and search the web looking for an answer to no avail, and finally figure out how to do it only to discover that if you had read between the lines in the documentation you would have gotten it in the first place. Well, it is half true here. There turned out to be two big underlying issues, one of which was hinted at in the documentation and the other goes back to a problem I've talked about before. What's amazing about this one is that I thought I wouldn't have much to write about but I kept peeling back layers to find worse stuff.

The MFC COleDateTime class has a Format method which returns a string with the formatted date/time value. As explained in the MSDN documentation this method is overloaded so you can pass a strftime style format string or you can use:

CString Format(
   DWORD dwFlags = 0,
   LCID lcid = LANG_USER_DEFAULT 
) const;

dwFlags
Indicates one of the following locale flags:
LOCALE_NOUSEROVERRIDE Use the system default locale settings, rather than custom user settings.
VAR_TIMEVALUEONLY Ignore the date portion during parsing.
VAR_DATEVALUEONLY Ignore the time portion during parsing.

The documentation for the VAR_XXXXVALUEONLY flags is bad because there is no parsing going on! It is just due to lazy documenting because these descriptions were copied directly from the ParseDateTime method (ref). What it should say is that it will return just the relevant portion of the locale date/time format. But there is more to my rant...

It was brought to my attention that some of my code was displaying a blank time at midnight and I tracked it down to a call to Format(VAR_TIMEVALUEONLY). Sure enough, it was returning an empty string at 00:00:00 (12:00:00 AM). I found the following exchange on the newsgroups:

March 2003 I'm having some trouble with the COleDateTime Format() returning a string with no length when it is 12:00:00 AM. Here's a code snippet that reproduces the problem:

COleDateTime time; 
CString strTime;
time.m_dt = 37688.999996701386;
strTime = time.Format(VAR_TIMEVALUEONLY);

Stepping into the Format(), I see that VarBstrFromDate doesn't fail (hr = S_OK), it just doesn't set the bstr to anything.

Someone apparently from Microsoft responded: I did repro it on my side. I will report it to our dev team. For now, I think you need to use time.Format() to get the setting when you get the empty string.

I am not sure what he meant at the end about getting a "setting". But at least now I knew that someone else experienced the same problem. After a number of detours I finally found out I could just use "%X" instead of VAR_TIMEVALUEONLY and it worked perfectly.

Or did it? Before patting myself on the back I should have tested it in a different locale, and when I finally did I discovered that these two different means of getting the locale-specific date time format are another example of The secret family split in Windows code page functions. In order to use the "%X" form you need to call setlocale(LC_ALL,"") somewhere in the beginning of your program. When I switch to Japanese dates and formats (via Control Panel Regional Settings), the code below demonstrates different results from these two forms. Since times are generally formatted the same in the locales I know of, the example uses the VAR_DATEVALUEONLY and "%x" format to show the difference (results are shown in the remarks).

COleDateTime odt = COleDateTime::GetCurrentTime();
CString str1 = odt.Format( VAR_DATEVALUEONLY ); // 2006/01/09
CString str2 = odt.Format( "%x" ); // 01/09/06
setlocale( LC_ALL, "" );
CString str3 = odt.Format( "%x" ); // 2006/01/09

Now I was trying to remember why I ever used VAR_TIMEVALUEONLY instead of "%X", and I realized this was probably the reason. I wasn't just doing the time, I had been doing the date too and saw that with "%x" the date was not showing up properly in the other locales.

Now getting back to the issue of the empty time string at midnight, let's revisit the documentation. It is an overloaded function so if you use the form of it that takes the flags the following applies:

Format( dwFlags, lcid )
This form formats the value using the national language specifications (locale IDs) for date and time. Using the default parameters, this form will print the date and the time, unless the time portion is 0 (midnight), in which case it will print just the date, or the date portion is 0 (30 December 1899), in which case it will print just the time. If the date/time value is 0 (30 December 1899, midnight), this form with the default parameters will print midnight.

By "default parameters" it means 0 and LANG_USER_DEFAULT as shown in the function prototype at the top. The real confusing part of this is where it says it "will print midnight." I have no idea what it means to print midnight, surely not the string "midnight" (in the locale's language)?

But anyway, in here is a clue to how the thing works. When nFlags is 0, the result is a combination of both VAR_DATEVALUEONLY and VAR_TIMEVALUEONLY. A time value of 0 (midnight) just displays the date so therefore the "time value only" portion is blank and therefore VAR_TIMEVALUEONLY will also return blank. Twisted I know, but it is there!

Unfortunately it is a poor design since midnight is a valid time of day. There might be cases where it is nice to hide the time if and when only the date portion of the object is important and visa versa, but this was not the right way to do it. Using "%X" is a good work-around, however the ability to specify the locale identifier is not available with a string format like "%X" so I guess the nFlags/lcid form of this function is almost useless for most purposes.

The MSDN documentation is truly hopelessly inadequate on this COleDateTime Format function. Well if you found this article via a web search and it helped you out, let me know in the comments! Thanks.