0

I'm looking for a low-level WinAPI to format date-time from FILETIME to a localized string with an output of a date, time, or date and time together.

By reversing the MFC's COleDateTime I was able to find VarBstrFromDate function that "kinda" works. So I was trying to see if there's another more reliable API that can accomplish that.

There are two issues with VarBstrFromDate that I could see:

1) It doesn't reliably report the date or time parts. For instance, if I call it as such for midnight Dec. 31, 1899:

BSTR bstr = NULL;
HRESULT hr = ::VarBstrFromDate(1.0, LANG_USER_DEFAULT, VAR_FOURDIGITYEARS, &bstr);
::SysFreeString(bstr);

it returns "12/31/1899" without the time part.

Or, if I call it for, say, noon Dec. 30, 1899:

BSTR bstr = NULL;
HRESULT hr = ::VarBstrFromDate(0.5, LANG_USER_DEFAULT, VAR_FOURDIGITYEARS, &bstr);
::SysFreeString(bstr);

it returns "12:00:00 PM" without the date part.

2) Additionally, conversion from FILETIME to DATE is somewhat tricky, especially for dates prior to Dec. 30, 1899. Again, by reversing MFC I was able to find that SystemTimeToVariantTime can do it via the SYSTEMTIME struct. Kinda backwards, but OK. But then I saw this monstrosity in the atlcore.h:

inline BOOL AtlConvertSystemTimeToVariantTime(
    _In_ const SYSTEMTIME& systimeSrc,
    _Out_ double* pVarDtTm)
{
    ATLENSURE(pVarDtTm!=NULL);
    //Convert using ::SystemTimeToVariantTime and store the result in pVarDtTm then
    //convert variant time back to system time and compare to original system time.
    BOOL ok = ::SystemTimeToVariantTime(const_cast<SYSTEMTIME*>(&systimeSrc), pVarDtTm);
    SYSTEMTIME sysTime;
    ::ZeroMemory(&sysTime, sizeof(SYSTEMTIME));

    ok = ok && ::VariantTimeToSystemTime(*pVarDtTm, &sysTime);
    ok = ok && (systimeSrc.wYear == sysTime.wYear &&
            systimeSrc.wMonth == sysTime.wMonth &&
            systimeSrc.wDay == sysTime.wDay &&
            systimeSrc.wHour == sysTime.wHour &&
            systimeSrc.wMinute == sysTime.wMinute &&
            systimeSrc.wSecond == sysTime.wSecond);

    return ok;
}

Do you know why they're checking the result returned by SystemTimeToVariantTime by then passing it through VariantTimeToSystemTime in such an awkward way? Does it mean that SystemTimeToVariantTime may return an incorrect result, or what?

Anyway, I was thinking to find another API that wouldn't rely on conversion to a floating-point DATE.

c00000fd
  • 20,994
  • 29
  • 177
  • 400
  • 1
    Use standard library chrono or boost chrono if you on a legacy C++03 compiler. See: https://stackoverflow.com/questions/16692400/c11-adding-a-stream-output-operator-for-stdchronotime-point – Victor Gubin Mar 14 '18 at 19:01
  • Automation dates had to be compatible with Lotus 123 bugs. The monstrosity is just there to help you not fall in the trap. Don't use dates before March 1st 1900. – Hans Passant Mar 14 '18 at 19:27

1 Answers1

4

You can use the classic time functions in kernel32: First call FileTimeToSystemTime and then call GetTimeFormat and GetDateFormat. SHFormatDateTime is a wrapper around those functions.

The shell also has StrFromTimeInterval if you need friendly text of time duration.

IInspectable
  • 46,945
  • 8
  • 85
  • 181
Anders
  • 97,548
  • 12
  • 110
  • 164
  • Wow, good find. I somehow missed that `SHFormatDateTime` API. Although I can't seem to figure out how localization would work with it. Neither `SetThreadUILanguage` nor `SetThreadPreferredUILanguages` seem to have any effect on it? And the same with `StrFromTimeInterval` – c00000fd Mar 14 '18 at 23:13
  • Nah, the same thing. Although I just realized that I can simply do `GetDateFormat(lcid, DATE_SHORTDATE, &st, NULL, bufferDate, _countof(bufferDate));` and then `GetTimeFormat(lcid, 0, &st, NULL, bufferTime, _countof(bufferTime));` and then concatenate it as `date + space + time`, and it would do what I need. What I'm curious though (that I can't seem to find anywhere) so maybe you know -- do pretty much all cultures have `date` followed by a `space` and then `time`? – c00000fd Mar 15 '18 at 16:34
  • The implementation has moved to propsys.dll at some point and short dates are: date + " " + time. Long dates are: date + ", " + time but the comma string comes from LoadString and could possibly be something other than ", " in a different locale because MUI is involved. – Anders Mar 15 '18 at 17:04