【问题标题】:Converting time string into epoch time on windows在Windows上将时间字符串转换为纪元时间
【发布时间】:2015-11-19 13:45:37
【问题描述】:

我有一个获取文件的界面,文件名是内部数据有效时的时间戳。我正在按顺序处理文件并将它们提供给另一个 API,该 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 个。在之前的函数中,我已经验证了字符串的格式是否正确,文件是否存在以及所有这些爵士乐。我正在使用 VS 2010 在 Windows 8.1 编译 64 位上运行。

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

我没有使用 boost,也没有使用它的访问权限。

【问题讨论】:

  • 那个else if 块太残暴了。将日期放入数据结构中,并使用MM 作为开始索引循环返回它们
  • // year 2000 wasn't a leap year 其实是这样。
  • 您可能会发现有用的日期算法:howardhinnant.github.io/date_algorithms.html
  • @HowardHinnant 非常正确。当我检查下面的答案时发现了这一点。

标签: c++ windows time


【解决方案1】:

您可以使用这段代码(您不必担心闰年和所有相关的东西)。 @Edit1:修改了代码以考虑闰秒;也将其重组为一个类:

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;
}

注意事项

  • 对于无效的日期/时间,SystemTimeToFileTime 将失败

  • 常量CFoo::msecsBetweenEpochs我觉得可以在谷歌上找到;我是从 Python(2.7.10)posixmodule.c 中获取的(实际上是秒数;我只需将它乘以 1000)

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

  • 根据SystemTimeToFileTime,时间戳为UTC

【讨论】:

  • 我会尝试一下,看看我得到了什么。为什么要除以 10000? ui.QuadPart 是几十纳秒吗?不用担心包装,我会让它适合我的架构。
  • 是的(几乎是:)),这里是FILETIME MSDN page
  • 所以,问题依然存在。这个答案产生了与我的解决方案相同的答案(一旦我处理了有闰日的 2k 年),也就是说,它不包括闰秒。它假定时间字符串中没有闰秒并且不添加任何自身。正如这个答案所示,来自 Windows 的文件时间/系统时间不处理闰秒。 stackoverflow.com/questions/130573/…
  • 是的,你是对的!我不知道为什么我认为它会处理闰秒(也许我是这么认为的,因为它处理的是闰年)。我可以通过添加闰秒来纠正解决方案,以表格的形式(数据来自timeanddate.com/time/leap-seconds-future.html),缺点是每次添加新的闰秒时,都需要调整代码。我应该继续调整解决方案还是应该删除它?
  • 谢谢。然而,这是我想避免的事情。如果我的代码维护查找表,那么我需要更新我的代码并在每次添加闰秒时重新发布。我希望 windows 的某些内置函数中包含闰秒数,并且当添加新的闰秒时,它将包含在每周 windows 更新包之一中。
【解决方案2】:

这是一个适用于任何支持 C++11 或 C++14 的平台的答案。它建立在 C++11 中引入的 std::chrono 库的基础上。它还使用免费、开源、跨平台的库来简化算术(通常认为对律师友好的 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};
}

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

如果您想考虑闰秒,您需要this higher-level library,它是IANA timezone database 的完整解析器。您还需要保留为my timezone/leap-second library 下载的IANA timezone database 的更新副本以进行解析。但是一旦设置好,您的转换器的源代码与上面的非常相似,而且几乎一样简单:

#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" 标头现在包含一个 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));
}

【讨论】:

  • 抱歉问,这是问题的真正答案吗? , 这会在 Windows 上自动更新吗? (每次出现闰秒时,stl 库都会更新...)?
  • @CristiFati:很抱歉造成沟通不畅。除了&lt;chrono&gt; 库,它 方便地做日历,上面的库是not 标准的。它们是我写的东西,并以免费和开源的形式提供。在闰秒问题上,更高级别的 tz 库依赖于下载 IANA 时区数据库的最新副本。这一切都在答案中链接的文档中明确说明。目前对于这个问题没有好的 std::lib 解决方案(恕我直言)。
猜你喜欢
  • 2013-01-25
  • 2011-06-21
  • 1970-01-01
  • 1970-01-01
  • 2012-01-30
  • 2018-01-27
  • 2016-07-23
  • 2012-04-03
  • 2018-09-07
相关资源
最近更新 更多