夏令时和使用WinAPI进行的UTC到本地时间转换

我试图看看是否从本地转换到UTC时间,反之亦然是准确的夏令时。 例如,让我们拿LocalFileTimeToFileTime API。 其描述中指出:

LocalFileTimeToFileTime使用时区和夏令时的当前设置。 因此,如果是夏令时,即使您正在转换的时间处于标准时间,此function也会考虑夏令时。

所以我用这个代码testing它:

//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! 

那么我在这里错过了什么?

Solutions Collecting From Web of "夏令时和使用WinAPI进行的UTC到本地时间转换"

SystemTimeToFileTime()将其第一个参数解释为UTC时间(它没有DST的概念),所以您的ft1_localft2_local对象将始终相隔两个小时,因为您正在更改数据格式,但不是实际的时间点。 然后, LocalFileTimeToFileTime()会将相同的偏移量应用于传递给它的任何偏移量,所以ft1_utcft2_utc总是会相隔两个小时。

正如文档所述,“ LocalFileTimeToFileTime 使用时区和夏令时的当前设置 ”(重点是我的),所以如果在当前时间比UTC UTC时间少了四个小时,那么它只会从无论何时何地传递给它,无论这个时间是否原本代表DST的另一端。

编辑:根据评论,这里是如何得到标准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之间,所以3月是2,而不是3。

  • 其他时间的成员是你所期望的

  • tm_isdst被设置为-1mktime()会试图找出DST在我们提供的本地时间是否有效,这就是我们想要的。

尽管保罗·格里菲斯 ( Paul Griffiths )解决方案的所有美丽,但由于明显的语言环境限制,我无法使用它。 (C很明显是在显示它的年龄)。所以我只好用纯粹的WinAPI方法。 接下来是我想出了。 纠正我,如果我错了(尤其是人们可以访问除美国以外的时区,微软的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 hcoreel32 = ::GetmoduleeHandle(L"coreel32.dll"); ASSERT(hcoreel32); (FARPROC&)pfnGetDynamicTimeZoneInformation = ::GetProcAddress(hcoreel32, "GetDynamicTimeZoneInformation"); (FARPROC&)pfnGetTimeZoneInformationForYear = ::GetProcAddress(hcoreel32, "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; }