C++ 使用WinAPI进行夏令时和UTC到本地时间的转换
我想看看从本地时间转换到UTC时间的WindAPI和从UTC时间转换到本地时间的WindAPI是否精确到夏令时。例如,让我们以API为例。其说明如下: LocalFileTimeToFileTime使用时区的当前设置 还有夏时制。所以如果是夏时制,, 此功能将考虑夏令时,即使 转换的时间是标准时间 所以我用以下代码测试它:C++ 使用WinAPI进行夏令时和UTC到本地时间的转换,c++,c,windows,winapi,dst,C++,C,Windows,Winapi,Dst,我想看看从本地时间转换到UTC时间的WindAPI和从UTC时间转换到本地时间的WindAPI是否精确到夏令时。例如,让我们以API为例。其说明如下: LocalFileTimeToFileTime使用时区的当前设置 还有夏时制。所以如果是夏时制,, 此功能将考虑夏令时,即使 转换的时间是标准时间 所以我用以下代码测试它: //Say, if DST change takes place on Mar-8-2015 at 2:00:00 AM //when the clock is set 1
//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!
//如果DST变更发生在2015年3月8日凌晨2:00:00
//当时钟设置为向前1小时时
//让我们检查两次之间的差异:
SYSTEMTIME st1_local={2015,3,0,8,1,30,0,0}//2015年3月8日凌晨1:30:00
SYSTEMTIME st2_local={2015,3,0,8,3,30,0,0}//2015年3月8日凌晨3:30:00
//转换为文件时间格式
文件时ft1_本地,ft2_本地;
验证(::SystemTimeToFileTime(&st1_local,&ft1_local));
验证(::SystemTimeToFileTime(&st2_local,&ft2_local));
//然后将本地时间转换为UTC时间
文件时间ft1_utc,ft2_utc;
验证(::LocalFileTimeToFileTime(&ft1_local,&ft1_utc));
验证(::LocalFileTimeToFileTime(&ft2_local,&ft2_utc));
//明白了吗
LONGLONG iiDiff100ns=((LONGLONG)ft2_utc.dwHighDateTimeSystemTimeToFileTime()
将其第一个参数解释为utc时间(它没有DST的概念),因此您的ft1_local
和ft2_local
对象将始终相隔两小时,因为您正在更改数据格式,而不是实际的时间点。LocalFileTimeToFileTime()
然后将对传递给它的任何内容应用相同的偏移量,因此ft1_utc
和ft2_utc
也将始终相隔两小时
如文档所述,“LocalFileTimeToFileTime
使用时区和夏令时的当前设置”(emphasis mine),因此,如果当前时间比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;
}
正如你所期待的
请注意,对于struct tm
:
tm_year
表示自1900年以来的年份,因此为了得到2015年,我们写115
tm_mon
在0到11之间,所以三月是2,而不是3
- 其他时间成员如您所料
- 当
tm_isdst
设置为-1
时,mktime()
将尝试自行查明DST是否在我们提供的本地时间生效,这是我们希望它执行的操作
尽管“解决方案”非常漂亮,但由于明显的区域设置限制,我无法使用它。(C显然显示了它的年龄。)因此我不得不采用纯WinAPI方法。接下来是我想到的。如果我错了,请纠正我(尤其是那些可以访问除美国以外的时区的人,而微软的mktime
似乎更喜欢这个时区):
那么这就是实际的实现。这里需要注意的一个重要方面是,由于缺少检索特定年份时区信息的API,下面的方法在Windows XP上可能无法可靠地工作
一些声明首先:
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);
SystemTimeToFileTime不会将其第一个参数解释为UTC时间。它会按原样将其转换,或者换句话说,1:30 AM
变为1:30 AM
。您可以通过手动将FILETIME值转换为人类可读的格式来检查这一点。从1601年1月1日起,它以100纳秒的间隔表示。您如何解释这来自第一个参数的文档,然后是:“一个指向SYSTEMTIME结构的指针,该结构包含要从UTC转换为文件时间格式的系统时间。”我将如何解释它?混乱的Microsoft文档:)不过,我喜欢你的第二个论点,关于使用了当前设置。似乎LocalFileTimeToFileTime
确实采用了当前的DST设置并应用了它,而不考虑转换的日期。哇,那太糟糕了。你是如何调整我查找两个日期之间时间差的方法的呢>FILETIME结构包含从1601年1月1日UTC开始的时间间隔,因此如果1:30AM
始终变为1:30AM
,这表明SystemTimeToFileTime()< /代码>将它的第一个参数解释为UTC,它不驳斥这一点。保罗的答案是SPOT,但您也可以考虑使用.@ MattJangson的日期和时间API:是的,它是有效的。但是,除了C实现之外,“代码”> LoopFielTimeToFieltTime//Case> API本身有什么问题?它是否真的使用DST调整?就在“现在”调用它时?@ahmd0:是的,如文档所述。无需调用GetDynamicTimeZoneInformation()。只需将NULL传递给GetTimeZoneInformation ForYear()的第二个参数。
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());
}
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;
}