我想知道自纪元以来的秒数。值得注意的是,我不喜欢转换机器的位置,时区字符串应该足够了。
我有这个测试程序,pt.cc:
#include <assert.h>
#include <errno.h>
#include <iostream>
#include <stdio.h>
#include <string>
#include <string.h>
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE
#endif
#include <time.h>
using namespace std; // To be brief, don't do this in real life.
int main(int argc, char* argv[]) {
(void)argc; (void)argv; // Skip compile warning.
// I expect both of these to transform to 1440671500.
cout << "1440671500 expected" << endl;
const char utc_example[] = "2015-08-27T11:31:40+0100";
struct tm tm;
memset(&tm, 0, sizeof(struct tm));
char* end = strptime(utc_example, "%Y-%m-%dT%H:%M:%S%z", &tm);
assert(end);
assert(*end == '\0');
time_t seconds_since_epoch = mktime(&tm);
cout << "utc example: " << seconds_since_epoch << " or maybe "
<< seconds_since_epoch - tm.tm_gmtoff + (tm.tm_isdst ? 3600 : 0) << endl;
const char tz_example[] = "2015-08-27T10:31:40Z";
memset(&tm, 0, sizeof(struct tm));
end = strptime(tz_example, "%Y-%m-%dT%H:%M:%S%nZ", &tm);
assert(end);
assert(*end == '\0');
seconds_since_epoch = mktime(&tm);
cout << " tz example: " << seconds_since_epoch << " or maybe "
<< seconds_since_epoch - tm.tm_gmtoff + (tm.tm_isdst ? 3600 : 0) << endl;
return 0;
}
这是输出:
jeff@birdsong:tmp $ clang++ -ggdb3 -Wall -Wextra -std=c++14 pt.cc -o pt
jeff@birdsong:tmp $ ./pt
1440671500 expected
utc example: 1440671500 or maybe 1440667900
tz example: 1440667900 or maybe 1440664300
jeff@birdsong:tmp $ TZ=America/New_York ./pt
1440671500 expected
utc example: 1440693100 or maybe 1440711100
tz example: 1440689500 or maybe 1440707500
jeff@birdsong:tmp $ TZ=Europe/London ./pt
1440671500 expected
utc example: 1440675100 or maybe 1440675100
tz example: 1440671500 or maybe 1440671500
jeff@birdsong:tmp $
请注意mktime()
的返回值如何根据环境时区而变化。 mktime()
的手册页条目建议将解析时间解释为本地时间。所以我尝试减去GMT偏移并补偿时区,以防它忽略这些值(“或者”值)。
有关如何正确执行此操作的任何提示? (重要的是,我只需要在linux上工作。)
答案 0 :(得分:7)
此答案使用此日期/时间库:
http://howardhinnant.github.io/date/date.html
这里采用的方法是完全绕过C日期/时间API。我个人认为C方法有点混乱,麻烦,有点危险。
话虽这么说,我的日期/时间库中的解析和格式化工具是不存在的。我预计这些设施可能会成为一个独立的图书馆,将来会在我的图书馆之上分层。
与此同时,为这个特定问题推出自己的解析并不困难。方法如下:
#include "chrono_io.h"
#include "date.h"
#include <iostream>
#include <string>
#include <sstream>
using second_point = std::chrono::time_point<std::chrono::system_clock,
std::chrono::seconds>;
std::chrono::minutes
parse_offset(std::istream& in)
{
using namespace std::chrono;
char c;
in >> c;
minutes result = 10*hours{c - '0'};
in >> c;
result += hours{c - '0'};
in >> c;
result += 10*minutes{c - '0'};
in >> c;
result += minutes{c - '0'};
return result;
}
second_point
parse(const std::string& str)
{
std::istringstream in(str);
in.exceptions(std::ios::failbit | std::ios::badbit);
int yi, mi, di;
char dash;
// check dash if you're picky
in >> yi >> dash >> mi >> dash >> di;
using namespace date;
auto ymd = year{yi}/mi/di;
// check ymd.ok() if you're picky
char T;
in >> T;
// check T if you're picky
int hi, si;
char colon;
in >> hi >> colon >> mi >> colon >> si;
// check colon if you're picky
using namespace std::chrono;
auto h = hours{hi};
auto m = minutes{mi};
auto s = seconds{si};
second_point result = sys_days{ymd} + h + m + s;
char f;
in >> f;
if (f == '+')
result -= parse_offset(in);
else if (f == '-')
result += parse_offset(in);
else
;// check f == 'Z' if you're picky
return result;
}
int
main()
{
using namespace date;
std::cout << parse("2015-08-27T11:31:40+0100").time_since_epoch() << '\n';
std::cout << parse("2015-08-27T10:31:40Z").time_since_epoch() << '\n';
}
要完全提前,此解决方案主要使用std::istringstream
,std::chrono
,实际上只有一小部分是我的日期库。
我做了几个设计选择,您可以选择不选择(解析时有很多选项)。例如,如果存在任何解析错误,我选择抛出异常,并且我选择不检查-
和:
等分隔符(主要是出于简洁原因)。
代码相对不言自明。正如您在问题中所述,本地时区不是(也不应该是)解决方案的一部分。计时库用于在小时,分钟和秒之间管理算术。我的日期库用于处理将年/月/日转换为精度为chrono::time_point
的{{1}}。
通过处理所有这些算法,您可以专注于解析整数和字符。可以直接向此示例添加更多检查,并为错误执行任何操作。
此示例输出:
days
<强>更新强>
我已经将解析功能添加到"date.h"
,现在可以更简单地编写上面的1440671500s
1440671500s
函数:
parse
并返回相同的结果。
答案 1 :(得分:2)
以下是使用Google https://github.com/google/cctz
执行您想要的答案#include <chrono>
#include <iostream>
#include <string>
#include "src/cctz.h"
using namespace std;
int main(int argc, char* argv[]) {
const char kFmt[] = "%Y-%m-%dT%H:%M:%S%Ez";
// I expect both of these to transform to 1440671500.
const char utc_example[] = "2015-08-27T11:31:40+0100";
const char tz_example[] = "2015-08-27T10:31:40Z";
cout << "1440671500 expected" << endl;
// Required by cctz::Parse(). Only used if the formatted
// time does not include offset info.
const auto utc = cctz::UTCTimeZone();
std::chrono::system_clock::time_point tp;
if (!Parse(kFmt, utc_example, utc, &tp)) return -1;
cout << "utc example: " << std::chrono::system_clock::to_time_t(tp) << "\n";
if (!Parse(kFmt, tz_example, utc, &tp)) return -1;
cout << " tz example: " << std::chrono::system_clock::to_time_t(tp) << "\n";
return 0;
}
输出结果为:
1440671500 expected
utc example: 1440671500
tz example: 1440671500
请注意,其他涉及从time_t添加/减去偏移量的答案正在使用一种称为“epoch shift”的技术,但它实际上并不起作用。我在CppCon的这次演讲中于12:30解释了原因:https://youtu.be/2rnIHsqABfM?t=12m30s
答案 2 :(得分:0)
这是一个简单的函数,它将以UTC为单位返回自1970年以来的秒数:
#include <time.h>
#include <sys/time.h>
time_t GetSecondsSinceNewYearsDay1970UTC()
{
struct timeval tv;
if (gettimeofday(&tv, NULL) != 0) perror("doh!");
return tv.tv_sec;
}
如果您更喜欢反映计算机本地时区而不是全局UTC值的值,那么这是第二个返回该值的函数。
time_t GetSecondsSinceNewYearsDay1970LocalTimeZone()
{
const time_t utcTime = GetSecondsSinceNewYearsDay1970UTC();
time_t now = time(NULL);
time_t localTime = utcTime; // will be adjusted below
struct tm gmtm;
struct tm * tm = gmtime_r(&now, &gmtm);
if (tm)
{
localTime += (now-mktime(tm));
if (tm->tm_isdst>0) localTime += (60*60); // add an hour for DST
}
return localTime;
}
答案 3 :(得分:0)
在mktime()
返回的对象上调用strptime()
之前,将tm->tm_isdst
设置为负数,以使库确定DST当时是否生效。请注意,strptime()
的时区偏移是非标准扩展。 (另请注意,使用结构名称作为变量名称是C ++中的错误样式,因为它会对tm
的内容产生歧义。)