我很难从一年中的某天和午夜后的秒数中获取日期/时间组件



在我的数据流中,我从午夜开始有几秒钟,从 1 月 1 日开始有几天......我将手动指定年份,因此我需要能够将这三个值转换为正确的日期/时间以输出到另一个程序。 这是我的代码:

int currentDay = XPLMGetDatai(gLocalDate); // days since Jan 1st
float currentTime = XPLMGetDataf(gZuluTime); // seconds since midnight
int currentYear = 2015;
struct tm t;
struct tm * ct;
time_t t_of_day;
t.tm_year = currentYear - 1900;
t.tm_yday = currentDay;
t.tm_hour = (int)(currentTime / 3600);
t.tm_min = (int)(currentTime - (t.tm_hour * 3600)) / 60;
t.tm_sec = (int)currentTime - ((t.tm_hour * 3600) + (t.tm_min * 60));
t_of_day = mktime(&t); // should convert t into a valid time_t
ct = gmtime(&t_of_day); // should give me a valid UTC from t
// Send data to Celestial Control
CCelC.SetDay(ct->tm_mday);
CCelC.SetHour(ct->tm_hour);
CCelC.SetMinute(ct->tm_min);
CCelC.SetMonth(ct->tm_mon);
CCelC.SetYear(currentYear);

我似乎遇到的问题是,当调用 mktime(&t) 时,当前入tm_yday会被抹去。所以我最终得到的 ct->tm_mon 为 0,这在我当前 90 天(4 月 1 日)的测试运行中是不正确的。

因此,给定任何年份、午夜以来的任何秒数以及自 1 月 1 日以来的任何日子,我如何生成正确的日(1-31)、小时(0-23)、分钟(0-59)、周一(1-12)、年值?

这是使用 <chrono> 的参数。 这种说法并非没有缺点。 但是,我相信从长远来看,迁移到该系统将在类型安全性(正确性),性能和可读性方面带来好处。

缺点包括需要第三方开源免费标头(单个标头)库(目前):

https://github.com/HowardHinnant/date/blob/master/date.h

这也将需要 C++11 或向前(自然是因为它建立在 <chrono> 上)。

我将分阶段介绍此解决方案:

  1. 第一阶段仅将<chrono>用于转换,在类型安全性方面几乎没有收获。 此阶段的输入和输出是积分的。

  2. 当输入级为其自己的接口采用<chrono>类型时,第二阶段开始显示出优势。

  3. 当输入和输出都采用<chrono>时,第三阶段显示出显着的好处。

基础设施

假设struct CCelC如下所示:

#include <iomanip>
#include <iostream>
struct CCelC
{
    int year_;
    unsigned month_;
    unsigned day_;
    int hour_;
    int min_;
    int sec_;
    friend
    std::ostream&
    operator<<(std::ostream& os, const CCelC& x)
    {
        using namespace std;
        auto f = os.fill();
        os.fill('0');
        os << setw(4) << x.year_ << '-'
           << setw(2) << x.month_ << '-'
           << setw(2) << x.day_  << ' '
           << setw(2) << x.hour_ << ':'
           << setw(2) << x.min_ << ':'
           << setw(2) << x.sec_;
        os.fill(f);
        return os;
    }
};

还有这样的测试驱动程序:

int
main()
{
    auto t = convert(90, 12*3600 + 52*60 + 31, 2015);
    std::cout << t << 'n';
}

第 1 阶段

第一阶段构建一个CCelC convert(int currentDay, float s, int y)转换函数,该函数接受标量输入并输出一个CCelC,该该本身接受标量输入。 这里<chrono>的唯一用途是输入标量、进行日期计算和输出标量:

#include "date.h"
#include <chrono>
CCelC
convert(int currentDay, float s, int y)
{
    using namespace date;
    using namespace std::chrono;
    auto tp = sys_days{year{y}/jan/1} + days{currentDay} + seconds{static_cast<int>(s)};
    auto dp = floor<days>(tp);
    auto time = make_time(tp - dp);
    auto ymd = year_month_day{dp};
    return {int{ymd.year()}, unsigned{ymd.month()}, unsigned{ymd.day()},
            int(time.hours().count()), int(time.minutes().count()),
            int(time.seconds().count())};
}

需要这个辅助(免费的、开源的单头)库来方便日期计算。 它只是将输入的年/日/秒字段类型转换为std::chrono::time_point,然后将std::chrono::time_point转换回年/月/日小时:分钟:秒标量。

这个解决方案大致相当于当前接受的(和好的)答案。 这两种解决方案都不需要用户进行历法运算。 此解决方案的此驱动程序输出:

2015-04-01 12:52:31

第 2 阶段

想象一下,convert的输入代码决定转换为<chrono>。 这具有显着的类型安全优势。 编译器现在可以帮助您正确转换单位,并防止minutes与其他与时间单位无关的整数类型混淆。 这将有效地将潜在的运行时错误转换为编译时错误(在编译时捕获错误总是更好)。

convert函数现在被指定为采用计时类型:

CCelC
convert(date::days currentDay, std::chrono::duration<float> s, date::year y)

date::days只是24 std::chrono::hours的类型别名。 date::year 是一种新类型,但有助于消除2015与某个任意整数的歧义。现在2015_y具有类型 year,编译器会为您传播该信息。

我们的驱动程序现在可以变得更加可读(假设时间持续时间文字为 C++14):

int
main()
{
    using namespace date;
    using namespace std::chrono_literals;
    auto t = convert(days{90}, 12h + 52min + 31s, 2015_y);
    std::cout << t << 'n';
}

使用此新 API 实现convert略有简化:

CCelC
convert(date::days currentDay, std::chrono::duration<float> s, date::year y)
{
    using namespace date;
    using namespace std::chrono;
    auto tp = sys_days{y/jan/1} + currentDay + duration_cast<seconds>(s);
    auto dp = floor<days>(tp);
    auto time = make_time(tp - dp);
    auto ymd = year_month_day{dp};
    return {int{ymd.year()}, unsigned{ymd.month()}, unsigned{ymd.day()},
            int(time.hours().count()), int(time.minutes().count()),
            int(time.seconds().count())};
}

不再需要将标量输入转换为<chrono>库的类型安全单元。 convert的大部分工作仍然是采用CCelC的标量格式需求。

第三阶段

但是,如果CCelC收养了<chrono>呢? 从逻辑上讲,如果这样做,它应该存储一个std::chrono::time_point而不是所有这些字段。 它更节省空间,并且在必要时很容易(使用 date.h)转换为字段类型。 这可能如下所示:

#include "date.h"
#include <chrono>
#include <iomanip>
#include <iostream>
struct CCelC
{
    using time_point = std::chrono::time_point<std::chrono::system_clock,
                                               std::chrono::seconds>;
    time_point tp_;
    friend
    std::ostream&
    operator<<(std::ostream& os, const CCelC& x)
    {
        using namespace date;
        return os << x.tp_;
    }
};

这里的功能根本没有改变。 该程序的输出仍然2015-04-01 12:52:31。 而且需求的规模刚刚急剧下降。 涉及秒、分钟、小时和天的算术性能刚刚飙升。

convert功能也刚刚获得了性能和简化。 它的输入根本没有改变,所以驱动程序仍然是一样的。 但现在convert不需要转换回标量类型:

CCelC
convert(date::days currentDay, std::chrono::duration<float> s, date::year y)
{
    using namespace date;
    using namespace std::chrono;
    return {sys_days{y/jan/1} + currentDay + duration_cast<seconds>(s)};
}

现在,代码已大大简化,从而大大减少了逻辑错误的可能性。 此简化包括类型安全,以便编译器帮助您捕获逻辑错误。代码中不再公开单位转换,从而消除了另一类错误。 如果你用计时器环绕这段代码,你会发现它运行得很快

Cppcon 2015 日期的视频演示.h

也许<chrono>不是你今天可以完全采用的东西。 但是,在代码的小部分分阶段采用它也有好处。 Date.h 可以提供帮助。 在2或3年后,<chrono>是你想要的目标。 最终,这就是C++社区将普遍采用的。 <ctime>/<time.h>死了。 <chrono>的类型安全和性能优势太大了。 这个答案描述了如何逐步使用<chrono>,一次一小段代码。

你不能用mktime按照你想要的方式执行此操作。来自这些文档

忽略 timeptr 的成员tm_wday和tm_yday的值

.....

如果 timeptr 成员的值超出范围,或者(如果是 tm_wdaytm_yday,则调用此函数会自动调整其值,如果它们的值与其他成员描述的日期不匹配。

但是,您可以利用此行为正确填充其他字段。如果您改为像这样设置struct tm字段

struct tm t = {0};
t.tm_mday = currentDay + 1;
t.tm_year = currentYear - 1900;
t.tm_sec = currentTime;
mktime(&t);

此方法之所以有效,是因为自动调整了mktime的行为

作为一个完全工作的例子

#include <ctime>
#include <iostream>
int main()
{
    int currentDay = 90;
    int currentTime = (12*3600 + 52*60 + 31);
    int currentYear = 2015;
    struct tm t = {0};
    t.tm_mday = currentDay + 1;
    t.tm_year = currentYear - 1900;
    t.tm_sec = currentTime;
    // mktime will now correctly populate
    // tm_sec, tm_min, tm_hour
    // tm_day, tm_mon, tm_year
    mktime(&t);
    // Print the time to make sure!
    std::cout << asctime(&t);
}

将输出

Wed Apr  1 12:52:31 2015

最新更新