Переход на летнее и UTC-местное время с помощью WinAPI

Я пытаюсь проверить, точны ли значения летнего времени, которые преобразуют из местного в UTC время и наоборот. Например, давайте возьмем LocalFileTimeToFileTime API. В его описании говорится:

LocalFileTimeToFileTime использует текущие настройки для часового пояса
и летнее время. Поэтому, если это летнее время,
эта функция будет учитывать летнее время, даже если
Время конвертации указано в стандартном времени.

Итак, я тестирую это с помощью этого кода:

//Say, if DST change takes place on Mar-8-2015 at 2:00:00 AM
//when the clock is set 1 hr forward

//Let's check the difference between two times:
SYSTEMTIME st1_local = {2015, 3, 0, 8, 1, 30, 0, 0};    //Mar-8-2015 1:30:00 AM
SYSTEMTIME st2_local = {2015, 3, 0, 8, 3, 30, 0, 0};    //Mar-8-2015 3:30:00 AM

//Convert to file-time format
FILETIME ft1_local, ft2_local;
VERIFY(::SystemTimeToFileTime(&st1_local, &ft1_local));
VERIFY(::SystemTimeToFileTime(&st2_local, &ft2_local));

//Then convert from local to UTC time
FILETIME ft1_utc, ft2_utc;
VERIFY(::LocalFileTimeToFileTime(&ft1_local, &ft1_utc));
VERIFY(::LocalFileTimeToFileTime(&ft2_local, &ft2_utc));

//Get the difference
LONGLONG iiDiff100ns = (((LONGLONG)ft2_utc.dwHighDateTime << 32) | ft2_utc.dwLowDateTime) -
(((LONGLONG)ft1_utc.dwHighDateTime << 32) | ft1_utc.dwLowDateTime);

//Convert from 100ns to seconds
LONGLONG iiDiffSecs = iiDiff100ns / 10000000LL;

//I would expect 1 hr
ASSERT(iiDiffSecs == 3600); //But I get 7200, which is 2 hrs!

Так чего мне здесь не хватает?

4

Решение

SystemTimeToFileTime() интерпретирует свой первый аргумент как время UTC (которое не имеет понятия о летнем времени), поэтому ваш ft1_local а также ft2_local объекты всегда будут разделяться на два часа, так как вы меняете формат данных, но не фактический момент времени. LocalFileTimeToFileTime() затем примените то же смещение к тому, что вы передаете ему, так ft1_utc а также ft2_utc всегда будет в итоге два часа друг от друга, также.

Как говорится в документации:LocalFileTimeToFileTime использует текущие настройки для часового пояса и перехода на летнее время «(выделено мое), поэтому, если в текущее время вы, например, на четыре часа отстаете от UTC, он просто вычтет четыре часа из любого времени, которое вы передадите ему, независимо от того, время первоначально представляло некоторое время на другой стороне летнего времени.

РЕДАКТИРОВАТЬ: Согласно комментариям, вот как вы получите разницу в секундах между двумя местными временами в стандартном C:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void) {
struct tm start_time;
start_time.tm_year = 115;
start_time.tm_mon = 2;
start_time.tm_mday = 8;
start_time.tm_hour = 1;
start_time.tm_min = 30;
start_time.tm_sec = 0;
start_time.tm_isdst = -1;

struct tm end_time;
end_time.tm_year = 115;
end_time.tm_mon = 2;
end_time.tm_mday = 8;
end_time.tm_hour = 3;
end_time.tm_min = 30;
end_time.tm_sec = 0;
end_time.tm_isdst = -1;

time_t start_tm = mktime(&start_time);
time_t end_tm = mktime(&end_time);

if ( start_tm == -1 || end_tm == -1 ) {
fputs("Couldn't get local time.", stderr);
exit(EXIT_FAILURE);
}

double seconds_diff = difftime(end_tm, start_tm);
printf("There are %.1f seconds difference.\n", seconds_diff);

return EXIT_SUCCESS;
}

какие выводы:

paul@thoth:~/src$ ./difftime
There are 3600.0 seconds difference.
paul@thoth:~/src$

как вы ожидаете.

Обратите внимание, что с struct tm:

  • tm_year выражается в годах с 1900 года, поэтому для получения 2015 года мы пишем 115

  • tm_mon находится в диапазоне от 0 до 11, поэтому март 2, а не 3.

  • В остальное время участники, как и следовало ожидать

  • когда tm_isdst установлен в -1, mktime() попытается выяснить, действовал ли DST в указанное нами местное время, что мы и хотим сделать здесь.

6

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

Несмотря на всю красоту Пол Гриффитс«Решение, я не могу использовать его из-за очевидного ограничения локали. (Очевидно, C показывает свой возраст.) Поэтому мне пришлось использовать чистый подход WinAPI. Следующее, что я придумал. Поправьте меня, если я ошибаюсь (особенно люди, имеющие доступ к часовым поясам, отличным от американского, который Microsoft mktime вроде бы в пользу)

SYSTEMTIME st1 = {2015, 3, 0, 8, 1, 30, 0, 0};    //Mar-8-2015 1:30:00 AM
SYSTEMTIME st2 = {2015, 3, 0, 8, 3, 30, 0, 0};    //Mar-8-2015 3:30:00 AM

LONGLONG iiDiffNs;
if(GetLocalDateTimeDifference(&st1, &st2, &iiDiffNs))
{
_tprintf(L"Difference is %.02f sec\n", (double)iiDiffNs / 1000.0);
}
else
{
_tprintf(L"ERROR (%d) calculating the difference.\n", ::GetLastError());
}

Тогда это фактическая реализация. Здесь важно отметить один важный аспект: метод, описанный ниже, может не работать надежно в Windows XP из-за отсутствия API для получения информации о часовом поясе за определенный год.

Сначала несколько объявлений:

enum DST_STATUS{
DST_ERROR = TIME_ZONE_ID_INVALID,           //Error
DST_NONE = TIME_ZONE_ID_UNKNOWN,            //Daylight Saving Time is NOT observed
DST_OFF = TIME_ZONE_ID_STANDARD,            //Daylight Saving Time is observed, but the system is currently not on it
DST_ON = TIME_ZONE_ID_DAYLIGHT,             //Daylight Saving Time is observed, and the system is currently on it
};

#define FILETIME_TO_100NS(f) (((LONGLONG)f.dwHighDateTime << 32) | f.dwLowDateTime)

BOOL GetLocalDateTimeDifference(SYSTEMTIME* pStBegin_Local, SYSTEMTIME* pStEnd_Local, LONGLONG* pOutDiffMs = NULL);
BOOL ConvertLocalTimeToUTCTime(SYSTEMTIME* pSt_Local, SYSTEMTIME* pOutSt_UTC = NULL);
DST_STATUS GetDSTInfoForYear(USHORT uYear, TIME_ZONE_INFORMATION* pTZI = NULL);

И реализация:

BOOL GetLocalDateTimeDifference(SYSTEMTIME* pStBegin_Local, SYSTEMTIME* pStEnd_Local, LONGLONG* pOutDiffMs)
{
//Calculate difference between two local dates considering DST adjustments between them
//INFO: May not work correctly on Windows XP for a year other than the current year!
//'pStBegin_Local' = local date/time to start from
//'pStEnd_Local' = local date/time to end with
//'pOutDiffMs' = if not NULL, receives the difference in milliseconds (if success)
//RETURN:
//      = TRUE if success
//      = FALSE if error (check GetLastError() for info)
BOOL bRes = FALSE;
LONGLONG iiDiffMs = 0;
int nOSError = NO_ERROR;

if(pStBegin_Local &&
pStEnd_Local)
{
//Convert both dates to UTC
SYSTEMTIME stBeginUTC;
if(ConvertLocalTimeToUTCTime(pStBegin_Local, &stBeginUTC))
{
SYSTEMTIME stEndUTC;
if(ConvertLocalTimeToUTCTime(pStEnd_Local, &stEndUTC))
{
//Then convert into a more manageable format: FILETIME
//It will represent number of 100-nanosecond intervals since January 1, 1601 for each date
FILETIME ftBeginUTC;
if(::SystemTimeToFileTime(&stBeginUTC, &ftBeginUTC))
{
FILETIME ftEndUTC;
if(::SystemTimeToFileTime(&stEndUTC, &ftEndUTC))
{
//Now get the difference in ms
//Convert from 100-ns intervals = 10^7, where ms = 10^3
iiDiffMs = (FILETIME_TO_100NS(ftEndUTC) - FILETIME_TO_100NS(ftBeginUTC)) / 10000LL;

//Done
bRes = TRUE;
}
else
nOSError = ::GetLastError();
}
else
nOSError = ::GetLastError();
}
else
nOSError = ::GetLastError();
}
else
nOSError = ::GetLastError();
}
else
nOSError = ERROR_INVALID_PARAMETER;

if(pOutDiffMs)
*pOutDiffMs = iiDiffMs;

::SetLastError(nOSError);
return bRes;
}

BOOL ConvertLocalTimeToUTCTime(SYSTEMTIME* pSt_Local, SYSTEMTIME* pOutSt_UTC)
{
//Convert local date/time from 'pSt_Local'
//'pOutSt_UTC' = if not NULL, receives converted UTC time
//RETURN:
//      = TRUE if success
//      = FALSE if error (check GetLastError() for info)
BOOL bRes = FALSE;
SYSTEMTIME stUTC = {0};
int nOSError = NO_ERROR;

if(pSt_Local)
{
//First get time zone info
TIME_ZONE_INFORMATION tzi;
if(GetDSTInfoForYear(pSt_Local->wYear, &tzi) != DST_ERROR)
{
if(::TzSpecificLocalTimeToSystemTime(&tzi, pSt_Local, &stUTC))
{
//Done
bRes = TRUE;
}
else
nOSError = ::GetLastError();
}
else
nOSError = ::GetLastError();
}
else
nOSError = ERROR_INVALID_PARAMETER;

if(pOutSt_UTC)
*pOutSt_UTC = stUTC;

::SetLastError(nOSError);
return bRes;
}

DST_STATUS GetDSTInfoForYear(USHORT uYear, TIME_ZONE_INFORMATION* pTZI)
{
//Get DST info for specific 'uYear'
//INFO: Year is not used on the OS prior to Vista SP1
//'pTZI' = if not NULL, will receive the DST data currently set for the time zone for the year
//RETURN:
//      = Current DST status, or an error
//        If error (check GetLastError() for info)
DST_STATUS tzStat = DST_ERROR;
int nOSError = NO_ERROR;

//Define newer APIs
DWORD (WINAPI *pfnGetDynamicTimeZoneInformation)(PDYNAMIC_TIME_ZONE_INFORMATION);
BOOL (WINAPI *pfnGetTimeZoneInformationForYear)(USHORT, PDYNAMIC_TIME_ZONE_INFORMATION, LPTIME_ZONE_INFORMATION);

//Load APIs dynamically (in case of Windows XP)
HMODULE hKernel32 = ::GetModuleHandle(L"Kernel32.dll");
ASSERT(hKernel32);
(FARPROC&)pfnGetDynamicTimeZoneInformation = ::GetProcAddress(hKernel32, "GetDynamicTimeZoneInformation");
(FARPROC&)pfnGetTimeZoneInformationForYear = ::GetProcAddress(hKernel32, "GetTimeZoneInformationForYear");

TIME_ZONE_INFORMATION tzi = {0};

//Use newer API if possible
if(pfnGetDynamicTimeZoneInformation &&
pfnGetTimeZoneInformationForYear)
{
//Use new API for dynamic time zone
DYNAMIC_TIME_ZONE_INFORMATION dtzi = {0};
tzStat = (DST_STATUS)pfnGetDynamicTimeZoneInformation(&dtzi);
if(tzStat == DST_ERROR)
{
//Failed -- try old method
goto lbl_fallback_method;
}

//Get TZ info for a year
if(!pfnGetTimeZoneInformationForYear(uYear, &dtzi, &tzi))
{
//Failed -- try old method
goto lbl_fallback_method;
}
}
else
{
lbl_fallback_method:
//Older API (also used as a fall-back method)
tzStat = (DST_STATUS)GetTimeZoneInformation(&tzi);
if(tzStat == DST_ERROR)
nOSError = ::GetLastError();
else
nOSError = ERROR_NOT_SUPPORTED;
}

if(pTZI)
{
*pTZI = tzi;
}

::SetLastError(nOSError);
return tzStat;
}
2

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