Конвертировать OLE Automation Date (OADate) double в struct tm без использования VariantTimeToSystemTime

Я пишу Windows DLL в основном STD C ++ (VS2010), который не использует MFC / ATL.

Родительский модуль использует MFC и передает COleDateTime.m_dt к моей DLL, которая поступает в виде double, Я считаю, что это дата автоматизации OLE, также известная как OADate.

Я хочу преобразовать это в любой тип стандартной структуры (tm …), в которой есть дни, часы и т. Д., Не включая MFC, OLE и т. Д. В мою DLL.

Об этом уже спрашивали (Конвертировать дату / время (как двойные) в struct * tm в C ++) однако, ответ всегда использует VariantTimeToSystemTime(), который упускает смысл этого вопроса — не используя MFC / OLE и т. д.

Требования VariantTimeToSystemTime:

Заголовок — OleAuto.h
Библиотека — OleAut32.lib
DLL — OleAut32.dll

В настоящее время моя DLL практически не имеет зависимостей, поэтому я бы предпочел не использовать OleAut32.dll для этого преобразования.

Лучшее, что я нашел до сих пор, это Моно код C #, который я могу преобразовать в C ++.

1

Решение

У меня есть 2 решения, первое работает с функцией, которая реализует gmtime_r так что это решение не будет использовать никаких стандартных функций. Второе решение использует стандартную функцию gmtime_r,

1. Первое решение: собственная реализация gmtime_r (01-Jan-1601 в 31-Dec-9999):

Это будет работать на даты между 01-Jan-1601 а также 31-Dec-9999, Я реализовал fromOADate функция, которая использует SecondsSinceEpochToDateTime функция из этого ответ на ТАК который конвертирует секунды до или после 01-Jan-1970 к tm структура, но работает только от 01-Jan-1601 на.

Я изменил функцию из этого ответа, чтобы работать также с 32-разрядным, добавив один ULL суффикс. Это требует, чтобы long long типы имеют ширину 64 бита, в противном случае это решение не будет работать.

Если вам нужны даты до 1601 года, вы можете изменить SecondsSinceEpochToDateTime как это хорошо задокументировано.
Чтобы проверить разные значения этого онлайн конвертация очень хорошо, который также поддерживает метку времени Unix и тип OADate.

Полный рабочий код и пример на идеоне:

#include <iostream>
#include <ctime>
#include <cstring>

struct tm* SecondsSinceEpochToDateTime(struct tm* pTm, uint64_t SecondsSinceEpoch)
{
uint64_t sec;
unsigned int quadricentennials, centennials, quadrennials, annuals/*1-ennial?*/;
unsigned int year, leap;
unsigned int yday, hour, min;
unsigned int month, mday, wday;
static const unsigned int daysSinceJan1st[2][13]=
{
{0,31,59,90,120,151,181,212,243,273,304,334,365}, // 365 days, non-leap
{0,31,60,91,121,152,182,213,244,274,305,335,366}  // 366 days, leap
};
/*
400 years:

1st hundred, starting immediately after a leap year that's a multiple of 400:
n n n l  \
n n n l   } 24 times
...      /
n n n l /
n n n n

2nd hundred:
n n n l  \
n n n l   } 24 times
...      /
n n n l /
n n n n

3rd hundred:
n n n l  \
n n n l   } 24 times
...      /
n n n l /
n n n n

4th hundred:
n n n l  \
n n n l   } 24 times
...      /
n n n l /
n n n L <- 97'th leap year every 400 years
*/

// Re-bias from 1970 to 1601:
// 1970 - 1601 = 369 = 3*100 + 17*4 + 1 years (incl. 89 leap days) =
// (3*100*(365+24/100) + 17*4*(365+1/4) + 1*365)*24*3600 seconds
sec = SecondsSinceEpoch + 11644473600ULL;

wday = (uint)((sec / 86400 + 1) % 7); // day of week

// Remove multiples of 400 years (incl. 97 leap days)
quadricentennials = (uint)(sec / 12622780800ULL); // 400*365.2425*24*3600
sec %= 12622780800ULL;

// Remove multiples of 100 years (incl. 24 leap days), can't be more than 3
// (because multiples of 4*100=400 years (incl. leap days) have been removed)
centennials = (uint)(sec / 3155673600ULL); // 100*(365+24/100)*24*3600
if (centennials > 3)
{
centennials = 3;
}
sec -= centennials * 3155673600ULL;

// Remove multiples of 4 years (incl. 1 leap day), can't be more than 24
// (because multiples of 25*4=100 years (incl. leap days) have been removed)
quadrennials = (uint)(sec / 126230400); // 4*(365+1/4)*24*3600
if (quadrennials > 24)
{
quadrennials = 24;
}
sec -= quadrennials * 126230400ULL;

// Remove multiples of years (incl. 0 leap days), can't be more than 3
// (because multiples of 4 years (incl. leap days) have been removed)
annuals = (uint)(sec / 31536000); // 365*24*3600
if (annuals > 3)
{
annuals = 3;
}
sec -= annuals * 31536000ULL;

// Calculate the year and find out if it's leap
year = 1601 + quadricentennials * 400 + centennials * 100 + quadrennials * 4 + annuals;
leap = !(year % 4) && (year % 100 || !(year % 400));

// Calculate the day of the year and the time
yday = sec / 86400;
sec %= 86400;
hour = sec / 3600;
sec %= 3600;
min = sec / 60;
sec %= 60;

// Calculate the month
for (mday = month = 1; month < 13; month++)
{
if (yday < daysSinceJan1st[leap][month])
{
mday += yday - daysSinceJan1st[leap][month - 1];
break;
}
}

// Fill in C's "struct tm"memset(pTm, 0, sizeof(*pTm));
pTm->tm_sec = sec;          // [0,59]
pTm->tm_min = min;          // [0,59]
pTm->tm_hour = hour;        // [0,23]
pTm->tm_mday = mday;        // [1,31]  (day of month)
pTm->tm_mon = month - 1;    // [0,11]  (month)
pTm->tm_year = year - 1900; // 70+     (year since 1900)
pTm->tm_wday = wday;        // [0,6]   (day since Sunday AKA day of week)
pTm->tm_yday = yday;        // [0,365] (day since January 1st AKA day of year)
pTm->tm_isdst = -1;         // daylight saving time flag

return pTm;
}struct tm* fromOADate(struct tm* p_Tm, double p_OADate)
{
static const int64_t OA_UnixTimestamp = -2209161600; /* 30-Dec-1899 */

if (!(   -109205 <= p_OADate               /* 01-Jan-1601 */
&&            p_OADate <= 2958465))  /* 31-Dec-9999 */
{
throw std::string("OADate must be between 109205 and 2958465!");
}

int64_t OADatePassedDays = p_OADate;
double  OADateDayTime    = p_OADate - OADatePassedDays;
int64_t OADateSeconds    = OA_UnixTimestamp
+ OADatePassedDays * 24LL * 3600LL
+ OADateDayTime * 24.0 * 3600.0;

return SecondsSinceEpochToDateTime(p_Tm, OADateSeconds);
}int main()
{
struct tm timeVal;

std::cout << asctime(fromOADate(&timeVal, -109205));         /* 01-Jan-1601 00:00:00 */
std::cout << asctime(fromOADate(&timeVal, 0));               /* 30-Dec-1899 00:00:00 */
std::cout << asctime(fromOADate(&timeVal, 25569));           /* 01-Jan-1970 00:00:00 */
std::cout << asctime(fromOADate(&timeVal, 50424.134803241)); /* 19-Jan-2038 03:14:07 */
std::cout << asctime(fromOADate(&timeVal, 2958465));         /* 31-Dec-9999 00:00:00 */

return 0;
}

2. Второе решение: использование gmtime_r (01-Jan-1970 в 19-Jan-2038/31-Dec-9999 (32/64 бит)):

Как уже говорилось, это решение не имеет такого широкого диапазона, как вариант, описанный выше, а просто использует стандартную функцию (полный рабочий пример на Ideone):

#include <iostream>
#include <ctime>

struct tm* fromOADate(struct tm* p_Tm, double p_OADate)
{
static const int64_t OA_UnixTimestamp = -2209161600; /* 30-Dec-1899 */

if (!(   25569 <= p_OADate              /* 01-Jan-1970 00:00:00 */
&&          p_OADate <= 2958465)) /* 31-Dec-9999 00:00:00 */
{
throw std::string("OADate must be between 25569 and 2958465!");
}

time_t OADatePassedDays = p_OADate;
double OADateDayTime    = p_OADate - OADatePassedDays;
time_t OADateSeconds    = OA_UnixTimestamp
+ OADatePassedDays * 24LL * 3600LL
+ OADateDayTime * 24.0 * 3600.0;

/* date was greater than 19-Jan-2038 and build is 32 bit */
if (0 > OADateSeconds)
{
throw std::string("OADate must be between 25569 and 50424.134803241!");
}

return gmtime_r(&OADateSeconds, p_Tm);
}int main()
{
struct tm timeVal;

std::cout << asctime(fromOADate(&timeVal, 25569));           /* 01-Jan-1970 00:00:00 */
std::cout << asctime(fromOADate(&timeVal, 50424.134803241)); /* 19-Jan-2038 03:14:07 */

return 0;
}
1

Другие решения

Других решений пока нет …

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector