我很难从一年中的某一天和自午夜以来的秒数中获取 date/time 个组件
I'm having a hard time getting date/time components from day of year and seconds since midnight
在我的数据流中,我有自午夜以来的秒数和自 1 月 1 日以来的天数...我将手动指定年份,因此我需要能够将这三个值转换为正确的 date/time输出到另一个程序。这是我的代码:
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 被抹杀了。所以我最终得到的 ct->tm_mon 为 0,这在我的 currentday 90(4 月 1 日)运行 测试中是不正确的。
所以给定任何一年,自午夜以来的任何秒数,以及自 1 月 1 日以来的任何几天,我如何生成正确的日(1-31),小时(0-23),分钟(0-59),星期一( 1-12), 年份值?
您不能使用 mktime
以您想要的方式执行此操作。来自 these docs
The values of the members tm_wday and tm_yday of timeptr are ignored
.....
A call to this function automatically adjusts the values of the members of timeptr
if they are off-range or -in the case of tm_wday
and tm_yday
- if they have values that do not match the date described by the other members."
但是您可以利用此行为正确填充其他字段。如果您改为像这样设置 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
这是使用 <chrono>
的论据。这种论点并非没有缺点。然而,我相信在长期 运行 中,迁移到这个系统将在类型安全(正确性)、性能和可读性方面带来好处。
缺点包括需要第 3 方免费开源 header-only(一个 header)库(目前):
https://github.com/HowardHinnant/date/blob/master/date.h
这也需要 C++11 或更高版本(自然是因为它建立在 <chrono>
之上)。
我将分阶段介绍此解决方案:
第一阶段仅使用<chrono>
进行转换,在type-safety方面收益不大。这个阶段的输入输出是积分的。
当输入阶段为其自己的接口采用 <chrono>
类型时,第二阶段开始显示优势。
当输入和输出都采用 <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;
}
};
还有这样的测试 driver:
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来方便日期计算。它只是将输入 year/day/second 字段类型转换为 std::chrono::time_point
,然后将 std::chrono::time_point
转换回 year/month/day hour:minute:second 标量。
这个解决方案大致等同于当前接受的(和好的)答案。这两种解决方案都不需要用户进行日历运算。此解决方案的 driver 输出:
2015-04-01 12:52:31
第 2 阶段
假设 convert
的输入代码决定转换为 <chrono>
。这具有显着的类型安全优势。编译器现在可以帮助您正确转换单位,并防止您将 minutes
与其他与时间单位无关的整数类型混淆。这将有效地将潜在的 run-time 错误转化为 compile-time 错误(在 compile-time 处捕获错误总是更好)。
现在指定 convert
函数采用计时类型:
CCelC
convert(date::days currentDay, std::chrono::duration<float> s, date::year y)
date::days
只是 24 std::chrono::hours
的 type-alias。 date::year
是一种新类型,但有助于消除 2015
与某个任意整数的歧义。现在 2015_y
的类型为 year
,编译器会为您传播该信息。
我们的 driver 现在可以变得更加可读(假设时间持续时间文字为 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>
库的 type-safe 单位。 convert
的大部分工作仍在采用 CCelC
的标量格式需求。
阶段 3
但是如果CCelC
采用了<chrono>
呢?从逻辑上讲,如果这样做,它应该存储 std::chrono::time_point
而不是所有这些字段。它更 space 高效,并且在必要时很容易(使用 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 要求刚刚 急剧 下降。而涉及秒、分、时、日的算术性能仅为sky-rocketed.
convert
函数也刚刚获得了性能和简化。它的输入根本没有改变,所以 driver 仍然是一样的。但是现在 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)};
}
现在的代码已经大大简化了,逻辑错误的几率大大降低了。此简化包括 type-safety,以便编译器帮助您捕获逻辑错误。代码中不再公开单位转换,消除了另一个 class 错误。如果你在这段代码周围包装定时器,你会发现它 运行s fast:
Cppcon 2015 video presentation of date.h
也许 <chrono>
不是您今天可以完全采用的东西。但是在小的阶段,在你的代码的小部分采用它是有好处的。 date.h 可以提供帮助。 2 或 3 年后,<chrono>
就是您想要的目标。最终这就是 C++ 社区将普遍采用的方式。 <ctime>/<time.h>
死了。 type-safety和<chrono>
的性能优势太大了。这个答案描述了如何逐步使用 <chrono>
,一次一小段代码。
在我的数据流中,我有自午夜以来的秒数和自 1 月 1 日以来的天数...我将手动指定年份,因此我需要能够将这三个值转换为正确的 date/time输出到另一个程序。这是我的代码:
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 被抹杀了。所以我最终得到的 ct->tm_mon 为 0,这在我的 currentday 90(4 月 1 日)运行 测试中是不正确的。
所以给定任何一年,自午夜以来的任何秒数,以及自 1 月 1 日以来的任何几天,我如何生成正确的日(1-31),小时(0-23),分钟(0-59),星期一( 1-12), 年份值?
您不能使用 mktime
以您想要的方式执行此操作。来自 these docs
The values of the members tm_wday and tm_yday of timeptr are ignored
.....
A call to this function automatically adjusts the values of the members of
timeptr
if they are off-range or -in the case oftm_wday
andtm_yday
- if they have values that do not match the date described by the other members."
但是您可以利用此行为正确填充其他字段。如果您改为像这样设置 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
这是使用 <chrono>
的论据。这种论点并非没有缺点。然而,我相信在长期 运行 中,迁移到这个系统将在类型安全(正确性)、性能和可读性方面带来好处。
缺点包括需要第 3 方免费开源 header-only(一个 header)库(目前):
https://github.com/HowardHinnant/date/blob/master/date.h
这也需要 C++11 或更高版本(自然是因为它建立在 <chrono>
之上)。
我将分阶段介绍此解决方案:
第一阶段仅使用
<chrono>
进行转换,在type-safety方面收益不大。这个阶段的输入输出是积分的。当输入阶段为其自己的接口采用
<chrono>
类型时,第二阶段开始显示优势。当输入和输出都采用
<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;
}
};
还有这样的测试 driver:
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来方便日期计算。它只是将输入 year/day/second 字段类型转换为 std::chrono::time_point
,然后将 std::chrono::time_point
转换回 year/month/day hour:minute:second 标量。
这个解决方案大致等同于当前接受的(和好的)答案。这两种解决方案都不需要用户进行日历运算。此解决方案的 driver 输出:
2015-04-01 12:52:31
第 2 阶段
假设 convert
的输入代码决定转换为 <chrono>
。这具有显着的类型安全优势。编译器现在可以帮助您正确转换单位,并防止您将 minutes
与其他与时间单位无关的整数类型混淆。这将有效地将潜在的 run-time 错误转化为 compile-time 错误(在 compile-time 处捕获错误总是更好)。
现在指定 convert
函数采用计时类型:
CCelC
convert(date::days currentDay, std::chrono::duration<float> s, date::year y)
date::days
只是 24 std::chrono::hours
的 type-alias。 date::year
是一种新类型,但有助于消除 2015
与某个任意整数的歧义。现在 2015_y
的类型为 year
,编译器会为您传播该信息。
我们的 driver 现在可以变得更加可读(假设时间持续时间文字为 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>
库的 type-safe 单位。 convert
的大部分工作仍在采用 CCelC
的标量格式需求。
阶段 3
但是如果CCelC
采用了<chrono>
呢?从逻辑上讲,如果这样做,它应该存储 std::chrono::time_point
而不是所有这些字段。它更 space 高效,并且在必要时很容易(使用 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 要求刚刚 急剧 下降。而涉及秒、分、时、日的算术性能仅为sky-rocketed.
convert
函数也刚刚获得了性能和简化。它的输入根本没有改变,所以 driver 仍然是一样的。但是现在 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)};
}
现在的代码已经大大简化了,逻辑错误的几率大大降低了。此简化包括 type-safety,以便编译器帮助您捕获逻辑错误。代码中不再公开单位转换,消除了另一个 class 错误。如果你在这段代码周围包装定时器,你会发现它 运行s fast:
Cppcon 2015 video presentation of date.h
也许 <chrono>
不是您今天可以完全采用的东西。但是在小的阶段,在你的代码的小部分采用它是有好处的。 date.h 可以提供帮助。 2 或 3 年后,<chrono>
就是您想要的目标。最终这就是 C++ 社区将普遍采用的方式。 <ctime>/<time.h>
死了。 type-safety和<chrono>
的性能优势太大了。这个答案描述了如何逐步使用 <chrono>
,一次一小段代码。