如何在C++中将iso 8601日期(可选毫秒)解析为struct tm?

mar*_*964 20 c++ parsing posix datetime-format

我有一个字符串,应该用ISO 8601格式指定一个日期和时间,其中可能有或没有毫秒,我想struct tm从它获得一个以及可能已指定的任何毫秒值(可以是如果不存在于字符串中则假定为零).

检测字符串格式是否正确,以及将用户指定的字符串转换为struct tm毫秒和毫秒值会涉及什么?

如果它不是毫秒问题,我可能只是使用C函数strptime(),但我不知道当秒包含小数点时该函数的定义行为应该是什么.

最后一点需要注意的是,如果可能的话,我会更喜欢一种解决方案,它不依赖于仅在Boost中找到的函数(但我很乐意接受C++ 11作为先决条件).

输入看起来像:

2014-11-12T19:12:14.505Z
Run Code Online (Sandbox Code Playgroud)

要么

2014-11-12T12:12:14.505-5:00
Run Code Online (Sandbox Code Playgroud)

Z在这种情况下,表示UTC,但可以使用任何时区,并将表示为与GMT的+或 - 小时/分钟偏移.秒字段的小数部分是可选的,但它可能存在的事实是我不能简单地使用strptime()或者std::get_time(),如果在字符串的秒部分中找到这样的字符,则不会描述任何特定的定义行为.

How*_*ant 20

旧问题的新答案.理由:更新的工具.

使用这个免费的开源库,可以解析为a std::chrono::time_point<system_clock, milliseconds>,它具有超过tm能够保持毫秒精度的优势.如果你真的需要,你可以继续使用C API system_clock::to_time_t(在此过程中失去毫秒).

#include "date.h"
#include <iostream>
#include <sstream>

date::sys_time<std::chrono::milliseconds>
parse8601(std::istream&& is)
{
    std::string save;
    is >> save;
    std::istringstream in{save};
    date::sys_time<std::chrono::milliseconds> tp;
    in >> date::parse("%FT%TZ", tp);
    if (in.fail())
    {
        in.clear();
        in.exceptions(std::ios::failbit);
        in.str(save);
        in >> date::parse("%FT%T%Ez", tp);
    }
    return tp;
}

int
main()
{
    using namespace date;
    using namespace std;
    cout << parse8601(istringstream{"2014-11-12T19:12:14.505Z"}) << '\n';
    cout << parse8601(istringstream{"2014-11-12T12:12:14.505-5:00"}) << '\n';
}
Run Code Online (Sandbox Code Playgroud)

这输出:

2014-11-12 19:12:14.505
2014-11-12 17:12:14.505
Run Code Online (Sandbox Code Playgroud)

请注意,两个输出都是UTC.在parse使用转换的本地时间为UTC -5:00偏移.如果你真的想要本地时间,还有一种方法可以解析为一个被调用的类型date::local_time<milliseconds>,然后解析但忽略偏移量.chrono::minutes如果需要,甚至可以将偏移解析为(使用parse过载minutes&).

解析的精度由chrono::time_point传入的精度控制,而不是由格式字符串中的标志控制.并且偏移可以是风格的+/-hhmm%z,或+/-[h]h:mm%Ez.

  • 现在已经在标准里了。 (3认同)
  • 真有帮助.你的图书馆给我留下了深刻的印象.希望它能成为未来的标准! (2认同)

k06*_*06a 14

您可以使用Csscanf(http://www.cplusplus.com/reference/cstdio/sscanf/)解析它:

const char *dateStr = "2014-11-12T19:12:14.505Z";
int y,M,d,h,m;
float s;
sscanf(dateStr, "%d-%d-%dT%d:%d:%fZ", &y, &M, &d, &h, &m, &s);
Run Code Online (Sandbox Code Playgroud)

如果你有std::string它可以这样调用(http://www.cplusplus.com/reference/string/string/c_str/):

std::string dateStr = "2014-11-12T19:12:14.505Z";
sscanf(dateStr.c_str(), "%d-%d-%dT%d:%d:%fZ", &y, &M, &d, &h, &m, &s);
Run Code Online (Sandbox Code Playgroud)

如果它应该处理不同的时区你需要使用sscanf返回值 - 解析参数的数量:

int tzh = 0, tzm = 0;
if (6 < sscanf(dateStr.c_str(), "%d-%d-%dT%d:%d:%f%d:%dZ", &y, &M, &d, &h, &m, &s, &tzh, &tzm)) {
    if (tzh < 0) {
       tzm = -tzm;    // Fix the sign on minutes.
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以填写tm(http://www.cplusplus.com/reference/ctime/tm/)结构:

tm time;
time.tm_year = y - 1900; // Year since 1900
time.tm_mon = M - 1;     // 0-11
time.tm_mday = d;        // 1-31
time.tm_hour = h;        // 0-23
time.tm_min = m;         // 0-59
time.tm_sec = (int)s;    // 0-61 (0-60 in C++11)
Run Code Online (Sandbox Code Playgroud)

它也可以用std::get_time(http://en.cppreference.com/w/cpp/io/manip/get_time)来完成,因为C++11@Barry在评论中提到我如何将一个iso 8601日期(带有可选的毫秒)解析为结构tm在C++中?

  • 这假设时区将为“Z”,正如问题中所述,情况并非总是如此。 (3认同)

Ser*_*gey 5

解析 ISO 8601* 函数的现代 C++ 版本

* - 此代码仅支持 ISO 8601 的子集。唯一支持的形式是“2020-09-19T05:12:32Z”和“2020-09-19T05:12:32.123Z”。毫秒可以是 3 位数的长度或根本没有毫秒部分,除了 没有时区Z,没有其他更罕见的功能。

#include <cstdlib>
#include <ctime>
#include <string>

#ifdef _WIN32
#define timegm _mkgmtime
#endif

inline int ParseInt(const char* value)
{
    return std::strtol(value, nullptr, 10);
}

// ParseISO8601 returns milliseconds since 1970

std::time_t ParseISO8601(const std::string& input)
{
    constexpr const size_t expectedLength = sizeof("1234-12-12T12:12:12Z") - 1;
    static_assert(expectedLength == 20, "Unexpected ISO 8601 date/time length");

    if (input.length() < expectedLength)
    {
        return 0;
    }

    std::tm time = { 0 };
    time.tm_year = ParseInt(&input[0]) - 1900;
    time.tm_mon = ParseInt(&input[5]) - 1;
    time.tm_mday = ParseInt(&input[8]);
    time.tm_hour = ParseInt(&input[11]);
    time.tm_min = ParseInt(&input[14]);
    time.tm_sec = ParseInt(&input[17]);
    time.tm_isdst = 0;
    const int millis = input.length() > 20 ? ParseInt(&input[20]) : 0;
    return timegm(&time) * 1000 + millis;
}
Run Code Online (Sandbox Code Playgroud)