当前位置: 首页 > 知识库问答 >
问题:

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

包德业
2023-03-14

我正在尝试查看从本地时间转换为 UTC 时间(反之亦然)的 WindAPI 是否准确,夏令时是否准确。例如,让我们以 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!

所以我错过了什么?

共有2个答案

晏沈义
2023-03-14

尽管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());
}

那么这就是实际的实现。这里要注意的一个重要方面是,由于缺少用于检索特定年份时区信息的 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);

和实现:

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;
}
张岳
2023-03-14

SystemTimeToFileTime() 将其第一个参数解释为 UTC 时间(没有 DST 的概念),因此您的ft1_localft2_local对象将始终相隔两个小时,因为您要更改数据格式,而不是实际时间点。然后,LocalFileTimeToFileTime() 会将相同的偏移量应用于您传递给它的任何内容,因此 ft1_utcft2_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$ 

正如你所期望的那样。

请注意,对于structtm

>

  • tm_year是以1900年以来的年份表示的,因此为了得到2015年,我们编写了115

    < code>tm_mon的取值范围是0到11,所以三月是2,而不是3。

    其他时间成员如您所料

    tm_isdst设置为 -1 时,mktime() 将尝试自己找出 DST 是否在我们提供的本地时间有效,这就是我们希望它在这里执行的操作。

  •  类似资料:
    • 我想将datetime-local从html转换为UTC时间: html: Java: 即本地时间(GMT 8),我想将其转换为UTC字符串时间 例如:转换为- 我搜索了很多资源,但仍然不知道怎么做。

    • 我得到一个格式为yyyy-MM-dd HH: mm的字符串,它代表UTC中的日期和时间。下一步是将其放入日历(带时间区UTC)。 此外,还需要创建一个单独的日历,将UTC转换为“欧洲/伦敦”时区(GMT/BST)。 之后,我需要能够检测“欧洲/伦敦”日历是否有DST(日光节约补偿)。 下面的代码将告诉你我已经走了多远,它在一台默认系统时区为GMT的英国计算机上运行正常。但是,当我在一台时区为UTC

    • 问题内容: 我有一个UTC时区,例如: 还有一个pytz时区对象: 转换为给定时区的正确方法是什么? 问题答案: 我想我明白了: 这行代码首先将朴素的(不了解时区的) 对象转换为包含时区(UTC)的对象。然后,它使用该功能根据请求的时区调整时间。

    • 有些时区会切换到夏令时,有些不会。我知道俄罗斯不使用这个时间,而乌克兰使用夏令时。 正如我从这个链接中知道的.NET 时区信息来自奥尔森时区莫斯科使用俄罗斯标准时间,基辅(乌克兰)使用 FLE 标准时间。 我的测试是: > 冬季,俄罗斯,DateTimeKid. Utc 冬天,俄罗斯,日期时间。当地的 Summer,俄罗斯,DateTimeKind.Utc 夏天,俄罗斯,DateTimeKind。

    • 我有一个Java客户机,它正在以UTC格式在MySQL DB中插入(通过CSV和'Load DATA Infile')时间戳(type TIMESTAMP)。我的服务器的时区是“America/New_York”,我需要保留夏令时信息。11月5日凌晨2点是美国东部时间转换到东部时间的时候,时钟拨回一个小时到凌晨1点。例如,11月5日凌晨1:30,发生两次;EDT和EST各一次。 我该如何让服务器区

    • 时间从宇宙诞生之日起就存在,时间也和我们现在的生活息息相关。在软件系统中更是经常会处理和时间相关的问题。如果读者使用的是Windows操作系统,会在任务栏的右边显示当前的时间。这个时间一般就是用户所有国家或地区的本地时间。 由于地球自转的原因,产生了24个时区。也就是说,将地球按经度划分,每15度是一个时区。每相临的两个时区相差1个小时。这就会带来一个非常大的问题。由于世界不同的国家或地区分布在不