如何解析包含小数时间的日期时间字符串?

4

我有一个日期时间字符串:

20:48:01.469 UTC MAR 31 2016

我想使用strptime将此时间字符串转换为struct tm,但我的格式字符串不起作用。

是否有用于小数秒的格式说明符?也许是%S%s或其他什么东西?

以下是代码片段:

tm tmbuf;
const char *str = "20:48:01.469 UTC MAR 31 2016"
const char *fmt = "%H:%M:%s %Z %b %d %Y";
strptime(str,fmt,&tmbuf);

1
尝试使用 "%H:%M:%s.%f %Z %b %d %Y" - EdChum
1
一个小的更正:"%H:%M:%S.%f %Z %b %d %Y" - GMichael
但是,@Michael,"%H:%M:%S.%f %Z %b %d %Y" 也无法正确提取,它只显示了分钟和秒(整数部分),但没有其他内容。 - Dr. Debasish Jana
2
"%H:%M:%S.%Y %Z %b %d %Y" works for me。这将不需要的毫秒解析为年份,然后被真实年份覆盖。行为可能取决于运行时(例如,%f似乎并不普遍支持)。 - Karsten Koop
据我所知,没有内置的亚秒读取功能。至少我从未见过这样的解析。如果您需要这种精度,则必须手动解析整个字符串。如果您不需要它,请使用Karsten Koop的建议。@Dr. Debasish Jana - GMichael
显示剩余4条评论
2个回答

3
使用这个免费、开源的C++11/14库,在此处还有另一种处理解析分数秒的方法:
#include "tz.h"
#include <iostream>
#include <sstream>

int main()
{
    using namespace date;
    using namespace std::chrono;
    std::istringstream str("20:48:01.469 UTC MAR 31 2016");
    sys_time<milliseconds> tp;
    parse(str, "%T %Z %b %d %Y", tp);
    std::cout << tp << '\n';
}

输出:

2016-03-31 20:48:01.469

即,使用此工具%S%T就可以轻松实现。精度不是通过标志控制的,而是由std::chrono::time_point的精度控制。

如果您想找出解析的时区缩写,也是可能的:

std::istringstream str("20:48:01.469 UTC MAR 31 2016");
sys_time<milliseconds> tp;
std::string abbrev;
parse(str, "%T %Z %b %d %Y", tp, abbrev);
std::cout << tp << ' ' << abbrev << '\n';

输出:

2016-03-31 20:48:01.469 UTC

话虽如此,这个库是基于std::get_time构建的,因此存在与Jonathan的出色答案所提到的相同的可移植性问题:目前只有libc++以不区分大小写的方式解析月份名称。希望在不久的将来会有所改变。

libstdc++错误报告。

VSO#232129错误报告。

如果您需要处理除UTC之外的时区,一般来说没有确切的方法可以做到这一点,因为在任何时候,多个时区可能使用相同的缩写。 因此,UTC偏移量可能是模糊的。 但是,这里有一篇简短的文章介绍了如何使用此库将缩写缩小到候选时区列表,从中您可以有一些特定逻辑来选择唯一的时区。


我得说,我本来期望你在我完成所有编辑之前就能回答这个问题。你的库什么时候会成为标准? - Jonathan Mee
1
@JonathanMee:你抓住我瞌睡了。;-) 标准化是一个充满失败的缓慢过程。我几周前提交了http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0355r0.html。它将在下周的奥卢被考虑。我不知道结果会是什么。但我可以向你保证,不会有一致的意见。`<chrono>`本身就非常有争议,对于C++11的标准化也很困难。 - Howard Hinnant
1
哦,糟糕!祝你好运先生。我发现自己总是不由自主地被这些时间问题所吸引,因为我想我已经花了很多时间在它们上面了,但我一直在想,如果我有@HowardHinnant的库,我就可以更容易地回答这个问题了。 - Jonathan Mee
2
@JonathanMee:请随意使用我的库来回答这些问题!:-)已移植到Mac、Linux和Windows。Windows很快就会升级到自动下载模式。 - Howard Hinnant
你知道吗,学习和解释标准中即将推出的内容真的让我感到非常兴奋。这个标准确实需要这个库,所以我相信你至少会从奥卢带回一个问题编号。当你拿到它时,请告诉我,这样我就可以开始链接它了! - Jonathan Mee

1
请注意tm的成员变量 tm_sec 表示最小的时间增量,它是一个int类型,仅在以下范围内定义:

自 C++11 起,每分钟之后的秒数为[0,60]

因此,您将无法在tm中存储秒的一部分,只需舍弃小数点后面的数字即可。

如Karsten Koop所建议的那样,您可以两次读取年份,第二个%Y将覆盖第一个。

auto fmt = "%H:%M:%S.%Y %Z %b %d %Y";

实时示例


那么,我建议不要使用strptime,因为它是一个POSIX函数,使用像get_time这样的标准函数更可取。这有一个小缺陷:get_time没有时区的知识,但tm也是如此,除了tm_isdst外,它是:

夏令时标志。如果DST生效,则值为正,如果不生效,则为零;如果没有信息,则为负

所以,如果您追求这样的东西,您可能需要单独分配tm_isdst

tm tmbuf;
stringstream str("20:48:01.469 UTC MAR 31 2016");

str >> get_time(&tmbuf, "%H:%M:%S.%Y UTC %b %d %Y");

实时示例


我的get_time答案有点虚伪,因为我虽然谈到了标准化的重要性,但只能在libc++上运行它。因此,我想发布一个更通用的解决方案,它也会丢弃时区,所以你需要独立设置tm_isdst

tm tmbuf{};
stringstream str("20:48:01.469 UTC MAR 31 2016");
string tm_mon;

str >> get_time(&tmbuf, "%T");

str.ignore(std::numeric_limits<std::streamsize>::max(), 'C');

str >> tm_mon >> get_time(&tmbuf, "%d %Y");

for (const auto& i : { "JAN"s, "FEB"s, "MAR"s, "APR"s, "MAY"s, "JUN"s, "JUL"s, "AUG"s, "SEP"s, "OCT"s, "NOV"s, "DEC"s }) {
    if (equal(cbegin(tm_mon), cend(tm_mon), cbegin(i), cend(i), [](const unsigned char a, const unsigned char b) { return toupper(a) == b; })) break;
    ++tmbuf.tm_mon;
}

实时示例

这有两个关键依赖:

  1. 时区总是以字符'C'结尾(必须大写)
  2. 输入的月份缩写必须与我的initializer_list中的一个匹配

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接