从午夜开始,我很难从一年中获得日期/时间组件

时间:2016-01-15 16:54:29

标签: c++ date time epoch

在我的数据流中,我从午夜开始有几秒钟和自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的currentDay正在被删除。所以我最终得到了一个0的tt-> tm_mon,这在我当前90(4月1日)的测试运行中是不正确的。

所以给定任何一年,从午夜以来的任何秒数,以及自1月1日以来的任何日子,我怎样才能生成正确的一天(1-31),小时(0-23),分钟(0-59),星期一(1- 12),年份值?

2 个答案:

答案 0 :(得分:1)

以下是使用<chrono>的论据。这种说法并非没有缺点。但是我相信从长远来看,迁移到这个系统将有利于类型安全(正确性),性能和可读性。

缺点包括需要第三方开源免费标题(单个标题)库(暂时):

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

这也需要C ++ 11或转发(当然因为它建立在<chrono>)。

我将分阶段提出这个解决方案:

  1. 第一阶段仅使用<chrono>进行转换,并且在类型安全方面收益甚微。此阶段的输入和输出是不可或缺的。

  2. 当输入阶段为其自己的界面采用<chrono>类型时,第二阶段开始显示好处。

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

  4. <强>基础设施

    假设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())};
    }
    

    需要此辅助(free, open-source one-header) library才能使日期计算变得方便。它只是将输入的年/日/秒字段类型转换为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的标量格式需求。

    第3阶段

    但如果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。并且sizeof要求只是显着掉线。而涉及秒,分,小时和天的算术性能刚刚飙升。

    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 video presentation of date.h

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

答案 1 :(得分:0)

您无法使用mktime以您希望的方式执行此操作。来自these docs

  

忽略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