在 windows 将时间字符串转换为纪元时间

Converting time string into epoch time on windows

我有一个获取文件的界面,文件名是内部数据有效时的时间戳。我正在按顺序处理文件并将它们提供给另一个 API,它自 1970 年 1 月 1 日起以毫秒为单位。

所以,我以 YYYYMMDD_HHmmSS.sss 格式的字符串形式获取文件名,并且必须将其转换为时间。我假设处理和收集发生在同一时区等,只需将字符转换为数字即可。

uint64_t foo::convertFileNameToTimestamp(const std::string& filename) const
{
    const uint64_t yyyy = atoi(filename.substr(0,4).c_str());
    const uint64_t MM = atoi(filename.substr(4,2).c_str());
    const uint64_t DD = atoi(filename.substr(6,2).c_str());
    const uint64_t HH = atoi(filename.substr(9,2).c_str());
    const uint64_t MI = atoi(filename.substr(11,2).c_str());
    const uint64_t SS = atoi(filename.substr(13,2).c_str());
    const uint64_t sss = atoi(filename.substr(16,3).c_str());

    // number of milliseconds in a day
    const uint64_t DAY = 24 * 60 * 60 * 1000;
    int MD = 0;
    if (MM == 2) MD = 31; // currently feb, so add all of january's days
    else if (MM == 3) MD = 31+28; // ...
    else if (MM == 4) MD = 31+28+31;
    else if (MM == 5) MD = 31+28+31+30;
    else if (MM == 6) MD = 31+28+31+30+31;
    else if (MM == 7) MD = 31+28+31+30+31+30;
    else if (MM == 8) MD = 31+28+31+30+31+30+31;
    else if (MM == 9) MD = 31+28+31+30+31+30+31+31;
    else if (MM == 10) MD = 31+28+31+30+31+30+31+31+30;
    else if (MM == 11) MD = 31+28+31+30+31+30+31+31+30+31;
    else if (MM == 12) MD = 31+28+31+30+31+30+31+31+30+31+30;

    // year 2000 wasn't a leap year
    uint64_t YLD = ((yyyy-1970) / 4);
    if (yyyy > 2000) --YLD;

    uint64_t temp = sss;
    temp += SS * 1000;
    temp += MI * 60 * 1000;
    temp += HH * 60 * 60 * 1000;
    temp += (DD-1) * DAY;
    temp += (MD) * DAY;
    temp += (yyyy-1970) * 365 * DAY + YLD*DAY;

    return temp;
}

显然,在这里重新发明轮子。似乎应该有某种功能。另外..我如何计算闰秒?处理闰日已经够烦人的了。时间戳都是从 2015 年及以后开始的,并且永远都是,但我不认为我可以盲目地添加 26 秒。最终我们将有 27 个或备份到 25 个。在之前的函数中,我已经验证了字符串格式正确、文件存在以及所有爵士乐。我正在 运行 windows 8.1 使用 VS 2010 编译 64 位。

我看过 Convert Epoch Time string to Time,它建议 ctime(),但它似乎不处理构造函数中的毫秒数,甚至不处理任何 get方法,它不接受通用格式的字符串输入。我假设我必须调用一些时间 类 CTOR,它将接受文件名字符串,然后在其上调用一些访问器以获取自 1970 年以来的毫秒时间,包括闰秒等。

我没有使用 boost,也没有 access/permission 来使用它。

您可以使用这段代码(您不必担心闰年和所有相关问题)。 @Edit1:修改代码以考虑闰秒;还将其重组为 class:

Foo.h:

#ifndef __FOO__H__
#define __FOO__H__

#include <string>
#include <Windows.h>
#include <stdint.h>

class CFoo {
private:
    const static int kLeapSecsDim = 26;
    static uint64_t msecsBetweenEpochs;
    static SYSTEMTIME leapSecs[kLeapSecsDim];

    ULARGE_INTEGER leapSecsUi[kLeapSecsDim];

    int CFoo::getLeapSeconds(ULARGE_INTEGER ui) const;
public:
    CFoo();
    ~CFoo() {};

    uint64_t toEpoch(const std::string& filename) const;
};

#endif  //__FOO__H__

Foo.cpp:

#include "Foo.h"

uint64_t CFoo::msecsBetweenEpochs = 11644473600000; /* Milliseconds between 1.1.1601 and 1.1.1970 */
SYSTEMTIME CFoo::leapSecs[CFoo::kLeapSecsDim] = 
                            {{1972, 06, 0, 30, 23, 59, 59, 999},
                             {1972, 12, 0, 31, 23, 59, 59, 999},
                             {1973, 12, 0, 31, 23, 59, 59, 999},
                             {1974, 12, 0, 31, 23, 59, 59, 999},
                             {1975, 12, 0, 31, 23, 59, 59, 999},
                             {1976, 12, 0, 31, 23, 59, 59, 999},
                             {1977, 12, 0, 31, 23, 59, 59, 999},
                             {1978, 12, 0, 31, 23, 59, 59, 999},
                             {1979, 12, 0, 31, 23, 59, 59, 999},
                             {1981, 06, 0, 30, 23, 59, 59, 999},
                             {1982, 06, 0, 30, 23, 59, 59, 999},
                             {1983, 06, 0, 30, 23, 59, 59, 999},
                             {1985, 06, 0, 30, 23, 59, 59, 999},
                             {1987, 12, 0, 31, 23, 59, 59, 999},
                             {1989, 12, 0, 31, 23, 59, 59, 999},
                             {1990, 12, 0, 31, 23, 59, 59, 999},
                             {1992, 06, 0, 30, 23, 59, 59, 999},
                             {1993, 06, 0, 30, 23, 59, 59, 999},
                             {1994, 06, 0, 30, 23, 59, 59, 999},
                             {1995, 12, 0, 31, 23, 59, 59, 999},
                             {1997, 06, 0, 30, 23, 59, 59, 999},
                             {1998, 12, 0, 31, 23, 59, 59, 999},
                             {2005, 12, 0, 31, 23, 59, 59, 999},
                             {2008, 12, 0, 31, 23, 59, 59, 999},
                             {2012, 06, 0, 30, 23, 59, 59, 999},
                             {2015, 06, 0, 30, 23, 59, 59, 999},
                            };


int CFoo::getLeapSeconds(ULARGE_INTEGER ui) const {
    int ret = 0;
    for (int i = 0; i < kLeapSecsDim; i++) {
        if (ui.QuadPart <= this->leapSecsUi[i].QuadPart)
            break;
        ret++;
    }
    return ret;
}

CFoo::CFoo() {
    FILETIME ft;
    BOOL res;
    for (int i = 0; i < this->kLeapSecsDim; i++) {
        res = SystemTimeToFileTime(&(this->leapSecs[i]), &ft);
        if (res == FALSE)
            throw std::exception("SystemTimeToFileTime error", GetLastError());
        this->leapSecsUi[i].LowPart = ft.dwLowDateTime;
        this->leapSecsUi[i].HighPart = ft.dwHighDateTime;
    }
}

uint64_t CFoo::toEpoch(const std::string& filename) const {
    SYSTEMTIME st;
    FILETIME ft;
    ULARGE_INTEGER ui;
    st.wYear = atoi(filename.substr(0, 4).c_str());
    st.wMonth = atoi(filename.substr(4, 2).c_str());
    st.wDay = atoi(filename.substr(6, 2).c_str());
    st.wHour = atoi(filename.substr(9, 2).c_str());
    st.wMinute = atoi(filename.substr(11, 2).c_str());
    st.wSecond = atoi(filename.substr(13, 2).c_str());
    st.wMilliseconds = atoi(filename.substr(16, 3).c_str());
    BOOL result = SystemTimeToFileTime(&st, &ft);
    if (result == FALSE)
        throw std::exception("SystemTimeToFileTime error", GetLastError());
    ui.HighPart = ft.dwHighDateTime;
    ui.LowPart = ft.dwLowDateTime;
    //printf("%016I64X - %I64u\n", ui.QuadPart, ui.QuadPart);
    //printf("%016I64X - %I64u\n", ui.QuadPart/10000, ui.QuadPart/10000);
    return (ui.QuadPart / 10000) - this->msecsBetweenEpochs + this->getLeapSeconds(ui) * 1000;
}

备注:

  • 无效dates/timesSystemTimeToFileTime会失败

  • 常量CFoo::msecsBetweenEpochs我觉得可以在Google上找到;我从 Python(2.7.10)posixmodule.c 拿来的(实际上有秒数;我只必须乘以 1000)

  • 您的实施产生的结果不是很准确(我使用 http://www.epochconverter.com 作为参考)。

  • 根据SystemTimeToFileTime,时间戳为UTC

这是一个适用于任何支持 C++11 或 C++14 的平台的答案。它建立在 C++11 中引入的 std::chrono 库之上。它还使用免费、开源、跨平台的库来简化算法(通常被认为是 lawyer-friendly)的 MIT 许可证。

如果你不需要考虑闰秒,你可以使用this date library,它看起来像这样:

#include <string>
#include "date.h"

using time_stamp = std::chrono::time_point<std::chrono::system_clock,
                                           std::chrono::milliseconds>;

time_stamp
convertFileNameToTimestamp(const std::string& filename)
{
    using namespace std::chrono;
    using namespace date;
    const uint64_t yyyy = atoi(filename.substr(0,4).c_str());
    const uint64_t MM = atoi(filename.substr(4,2).c_str());
    const uint64_t DD = atoi(filename.substr(6,2).c_str());
    const uint64_t HH = atoi(filename.substr(9,2).c_str());
    const uint64_t MI = atoi(filename.substr(11,2).c_str());
    const uint64_t SS = atoi(filename.substr(13,2).c_str());
    const uint64_t sss = atoi(filename.substr(16,3).c_str());
    return sys_days{year(yyyy)/MM/DD}
         + hours{HH} + minutes{MI} + seconds{SS} + milliseconds{sss};
}

从文件名中解析出数字后,创建一个 type-safe std::chrono::time_point 非常简单,它仅保存自 1970-01-01 以来的整数毫秒数(作为 int64_t).

如果要考虑闰秒,需要this higher-level library which is a complete parser of the IANA timezone database. You will also need to keep an updated copy of the IANA timezone database downloaded for my timezone/leap-second library解析。但是一旦设置好,您的转换器的源代码与上面的非常相似,而且几乎一样简单:

#include <string>
#include "tz.h"

using time_stamp_ls = std::chrono::time_point<date::utc_clock,
                                              std::chrono::milliseconds>;

time_stamp_ls
convertFileNameToTimestamp_ls(const std::string& filename)
{
    using namespace std::chrono;
    using namespace date;
    const uint64_t yyyy = atoi(filename.substr(0,4).c_str());
    const uint64_t MM = atoi(filename.substr(4,2).c_str());
    const uint64_t DD = atoi(filename.substr(6,2).c_str());
    const uint64_t HH = atoi(filename.substr(9,2).c_str());
    const uint64_t MI = atoi(filename.substr(11,2).c_str());
    const uint64_t SS = atoi(filename.substr(13,2).c_str());
    const uint64_t sss = atoi(filename.substr(16,3).c_str());
    return utc_clock::sys_to_utc(sys_days{year(yyyy)/MM/DD}
         + hours{HH} + minutes{MI} + seconds{SS} + milliseconds{sss});
}

这两个功能都可以通过以下 HelloWorld 来实现:

#include <iostream>

int
main()
{
    std::string filename = "20150830_002120.123";
    std::cout << convertFileNameToTimestamp   (filename).time_since_epoch().count() << '\n';
    std::cout << convertFileNameToTimestamp_ls(filename).time_since_epoch().count() << '\n';
}

输出:

1440894080123
1440894106123

请注意,这些时间戳恰好相隔 26,000 毫秒。

更新

"tz.h" header 现在包含一个 parse 函数,这使得编写这些函数 更容易:

date::sys_time<std::chrono::milliseconds>
convertFileNameToTimestamp(const std::string& filename)
{
    using namespace std::chrono;
    using namespace date;
    std::istringstream in{filename};
    sys_time<milliseconds> tp;
    parse(in, "%Y%m%d_%H%M%S", tp);
    return tp;
}

date::utc_time<std::chrono::milliseconds>
convertFileNameToTimestamp_ls(const std::string& filename)
{
    return date::to_utc_time(convertFileNameToTimestamp(filename));
}