将时间戳字符串转换为本地时间



如何转换时间戳字符串,例如"1997-07-16T19:20:30.45+01:00";转换为UTC时间。转换的结果应该是timespec结构,就像在utimensat输入参数中一样。

// sorry, should be get_utc_time    
timespec get_local_time(const char* ts);

第页。S.我需要使用标准Linux/C/C++设施(不管这意味着什么(或Boost C++库的解决方案。

假设:您希望从"1997-07-16T19:20:30.45"中减去"+01:00"以获得UTC时间戳,然后将其转换为timespec

这里有一个C++20解决方案,它将自动处理每秒精度[+/-]hh:mm UTC偏移量:

#include <chrono>
#include <ctime>
#include <sstream>
std::timespec
get_local_time(const char* ts)
{
using namespace std;
using namespace chrono;
istringstream in{ts};
in.exceptions(ios::failbit);
sys_time<nanoseconds> tp;
in >> parse("%FT%T%Ez", tp);
auto tps = floor<seconds>(tp);
return {.tv_sec = tps.time_since_epoch().count(),
.tv_nsec = (tp - tps).count()};
}

当这样使用时:

auto r = get_local_time("1997-07-16T19:20:30.45+01:00");
std::cout << '{' << r.tv_sec << ", " << r.tv_nsec << "}n";

结果是:

{869077230, 450000000}
  • std::chrono::parse将从解析的本地值中减去+/-hh:mmUTC偏移量,以获得UTC时间戳(精度高达纳秒(。

  • 如果输入的精度为秒,此代码将处理它。如果精度精确到纳秒,则此代码将进行处理。

  • 如果输入不符合此语法,则会引发异常。如果不需要,请删除in.exceptions(ios::failbit);,然后必须检查in.fail()以查看解析是否失败。

  • 此代码还将处理1970-01-01 UTC历元之前的日期,方法是在.tv_sec中输入负值,在.tv_nsec中输入正值([0999'999'999](。请注意,处理前epoch日期通常不在timespec规范范围内,因此大多数C实用程序不会处理这样的timespec值。

  • 如果您不能使用C++20,或者您的供应商尚未实现C++20的这一部分,则存在一个仅用于头的库,它实现了C++20的此部分,并可与C++11/14/17一起使用。我没有在这里链接到它,因为它不在集合中:";标准Linux/C/C++设施(不管这意味着什么(或Boost C++库";。如果有要求,我很乐意添加一个链接。

为了进行比较,以下是在大多数标准C中如何做到这一点。这有点麻烦,因为C的日期/时间支持仍然相当分散,不像C++所拥有的更完整的支持,如Howard Hinnant的回答所示。(另外,我将要使用的两个函数没有由C标准指定,尽管它们存在于许多/大多数系统中。(

如果您有半标准的strptime函数,并且不关心子条件和显式时区,那么它将相对简单。strptimestrftime的(部分(逆,在格式说明符的控制下解析时间串,并构造struct tm。然后您可以调用mktime将该struct tm转换为time_t。然后可以使用time_t来填充struct timespec

char *inpstr = "1997-07-16T19:20:30.45+01:00";
struct tm tm;
memset(&tm, 0, sizeof(tm));
char *p = strptime(inpstr, "%Y-%m-%dT%H:%M:%S", &tm);
if(p == NULL) {
printf("strptime failedn");
exit(1);
}
tm.tm_isdst = -1;
time_t t = mktime(&tm);
if(t == -1) {
printf("mktime failedn");
exit(1);
}
struct timespec ts;
ts.tv_sec = t;
ts.tv_nsec = 0;
printf("%ld %ldn", ts.tv_sec, ts.tv_nsec);
printf("%s", ctime(&ts.tv_sec));
printf("rest = %sn", p);

在我的时区,当前为UTC+4,这将打印

869095230 0
Wed Jul 16 19:20:30 1997
rest = .45+01:00

但你确实有亚秒信息,你确实有一个明确的时区,而且在任何基本的C时间转换函数中都没有内置的对这些信息的支持,所以你必须做一些事情"手工";。这里有一种方法。我将使用sscanf来分离年、月、日、小时、分钟、秒和其他组件。我将使用这些组件来填充struct tm,然后使用半标准的timegm函数将它们直接转换为UTC时间。(也就是说,我暂时假设HH:MM:SS部分是UTC。(然后我将手动更正时区。最后,我将用一开始提取的子秒信息填充struct timesectv_nsec字段。

int y, m, d;
int H, M, S;
int ss;     /* subsec */
char zs;    /* zone sign */
int zh, zm; /* zone hours, minutes */
int r = sscanf(inpstr, "%d-%d-%dT%d:%d:%d.%2d%c%d:%d",
&y, &m, &d, &H, &M, &S, &ss, &zs, &zh, &zm);
if(r != 10 || (zs != '+' && zs != '-')) {
printf("parse failedn");
exit(1);
}
struct tm tm;
memset(&tm, 0, sizeof(tm));
tm.tm_year = y - 1900;
tm.tm_mon = m - 1;
tm.tm_mday = d;
tm.tm_hour = H;
tm.tm_min = M;
tm.tm_sec = S;
time_t t = timegm(&tm);
if(t == -1) {
printf("timegm failedn");
exit(1);
}
long int z = ((zh * 60L) + zm) * 60;
if(zs == '+')    /* East of Greenwich */
t -= z;
else t += z;
struct timespec ts;
ts.tv_sec = t;
ts.tv_nsec = ss * (1000000000 / 100);
printf("%ld %ldn", ts.tv_sec, ts.tv_nsec);
printf("%s", ctime(&ts.tv_sec));
printf(".%02ldn", ts.tv_nsec / (1000000000 / 100));

对我来说,这会打印

869077230 450000000
Wed Jul 16 14:20:30 1997
.45

时区和亚秒信息已得到尊重。

本守则对1970年以前的日期没有特别规定。我认为如果mktime/timegm起作用,它就会起作用。

如前所述,其中两个函数strptimetimegm不是由ANSI/ISO C标准指定的,因此不能保证在任何地方都可用。

最新更新