3

I get a date/time as double value from C# (DateTime.ToOADate(), which is a OLE Date Time). It contains the passed days from 1899/12/31, with the fraction being the passed part of the day. Multiplied with 86400 I get the seconds and from that finally the day's time.

Getting the date however is harder. I still don't have an solution except for the dates that the UNIX time covers (1970/01/01 to 2038/01/19). During this time, mktime() can be used to convert the passed days to a datetime.

double OleDateTimeValue = 28170.654351851852; // 14.02.1977 15:42:16
struct tm * timeinfo;
int passedDays = (int)OLEDateTimeValue;

// between 1.1.1970 and 18.1.2038
if((passedDays >= 25569) && (passedDays <= 50423)) 
{
    timeinfo->tm_year = 70; //1970
    timeinfo->tm_mon = 0;
    timeinfo->tm_mday = 1 + (passedDays - 25569);
    mktime(timeinfo);
}    
else // date outside the UNIX date/time
{
}

Now, mktime() formats the tm struct so that it represents the requested date, or returns -1 if the value is outside the given dates.

Is there a generic way to do the calculation? Unfortunately I can't use MFC and have to use Visual C++ 6.0.

Thanks, Markus

Markus Erlacher
  • 97
  • 1
  • 3
  • 6
  • 1
    Visual C++ 6.0's compiler is not even C++. It violates the standard in horrendous ways. Be aware of this as you program, and try to upgrade if you can. Also, not being able to use MFC is in fact _fortunate_, not unfortunate. :) Use standard C++ + Boost wherever you can. – Lightness Races in Orbit Jan 03 '11 at 20:50

3 Answers3

4

I believe you can use the VariantTimeToSystemTime function to convert into a SYSTEMTIME.

villintehaspam
  • 8,540
  • 6
  • 45
  • 76
3

I love date conversions - they make such a nice puzzle!

Here's some code that works for the dates from 1900-03-01 to 2100-02-28. The reason it doesn't work past those boundaries is that 1900 and 2100 are not leap years. This has been tested against Microsoft's COleDateTime and matches every day within the range.

int leapDays = (int)((OleDateTimeValue + 1400) / 1461);
timeinfo->tm_year = (int)((OleDateTimeValue - leapDays - 1) / 365);
int janFirst = timeinfo->tm_year * 365 + (int)((timeinfo->tm_year + 7) / 4);
int wholeDays = (int)OleDateTimeValue - janFirst;
if (timeinfo->tm_year % 4 != 0 && wholeDays > 58)
    ++wholeDays;
static int firstOfMonth[12] = { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 };
timeinfo->tm_mon = std::upper_bound(&firstOfMonth[0], &firstOfMonth[12], wholeDays) - &firstOfMonth[0];
timeinfo->tm_mday = wholeDays - firstOfMonth[timeinfo->tm_mon - 1] + 1;

Conversion of the time portion is trivial, and I leave it as an exercise to the reader.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • Is there a standard way of doing this or is the expectation that the developer uses the formula above? – Jason Thompson Aug 17 '18 at 17:49
  • @JasonThompson there might be a standard way, but I'm not aware of it or I wouldn't have provided the answer that I did. Given that nobody else has provided an answer it might not be possible. To be honest I expected `mktime` to work with dates outside the given bounds, using a negative number for `tm_year` for example. – Mark Ransom Aug 17 '18 at 20:30
0

Converting to time_t should be easy (multiply by 86400 and add a constant offset), then you can use the localtime function. But you'll still be limited to the range of UNIX time. If you really need beyond that range then villintehaspam's answer, along with copying all the individual fields, looks like the way to go.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720