如何将std::chrono::time_point转换为字符串

54
如何将std::chrono::time_point转换为字符串? 例如:"201601161125"

你尝试过将其插入到std::stringstream中,然后使用stringstream的成员函数.str()将其转换为字符串吗? - Ziezi
不,我没有。我会去做的。如果你的提示有效,我会告诉你的。 - Seihyung Oh
1
请参见:http://howardhinnant.github.io/date_v2.html,第“日期时间类型”部分。 - Howard Hinnant
5个回答

39

C++20 更新:

现在可以轻松地在 C++20 中完成此操作:

#include <chrono>
#include <format>
#include <iostream>
#include <string>

int
main()
{
    using namespace std::chrono_literals;
    std::chrono::time_point tp = std::chrono::sys_days{2016y/1/16} + 11h + 25min;
    std::string s = std::format("{:%Y%m%d%H%M}", tp);
    std::cout << s << '\n';
}

输出:

201601161125

演示。

原始答案:

Howard Hinnant的免费、开源、仅包含头文件、可移植的日期/时间库是一种现代化的方法,它不通过旧的C API传递数据,并且不需要丢弃所有的亚秒信息。这个库也正在提议标准化

格式化有很多灵活性。最简单的方法就是直接输出:

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

int
main()
{
    using namespace date;
    std::cout << std::chrono::system_clock::now() << '\n';
}

这只是我的输出:

2017-09-15 13:11:34.356648

为了找到system_clock::time_point的流操作符(我的库不能将其插入到namespace std::chrono中),需要使用using namespace date;。在这种格式下不会丢失任何信息:您的system_clock::time_point的完整精度将被输出(我在macOS上运行时是microseconds)。

其他格式可以使用完整的strftime类似的格式标志,并进行轻微扩展以处理诸如小数秒之类的内容。以下是另一个示例,它以毫秒精度输出:

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

int
main()
{
    using namespace date;
    using namespace std::chrono;
    std::cout << format("%D %T %Z\n", floor<milliseconds>(system_clock::now()));
}

它只是为我输出:

09/15/17 13:17:40.466 UTC

25
我认为最好在这里不要使用 using namespace whatever,这样就更清楚哪些函数和类型来自哪个命名空间。 - einpoklum
5
问题之一是名称空间随时间变化:floor最初在名称空间date中,但从C++17开始在名称空间std::chrono中。到了C++20,所有内容都将在std::chrono中。 - Howard Hinnant
@HowardHinnant 有没有一种简单的方法来丢弃由date::format("%FT%TZ", p)产生的字符串中亚秒部分末尾的零(和.,如果需要)?其中psystem_clock::time_point - C.M.
控制输出精度的最佳方法是截断输入的精度到format,例如上述示例中截断到毫秒。在运行时没有自动检测尾随零的方法。唯一的方法是对生成的字符串进行自定义后处理(反向搜索零)。 - Howard Hinnant

24

最灵活的方法是将其转换为struct tm,然后使用strftime(就像时间版的sprintf)。类似这样:

最灵活的方法是将其转换为 struct tm,然后使用 strftime(它就像时间版的 sprintf)。类似这样:

std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
std::time_t now_c = std::chrono::system_clock::to_time_t(now);
std::tm now_tm = *std::localtime(&now_c);
/// now you can format the string as you like with `strftime`

这里查找strftime的文档。

如果你有localtime_s或者localtime_r可用,你应该优先使用它们而不是localtime

还有许多其他方法来完成这个任务,但大多数情况下更容易使用的方法都会生成一些预定义的字符串表示。你可以将上述所有内容"隐藏"在一个函数中以方便使用。


为什么使用这个更好,而不是使用Hinnant先生的库? - einpoklum
3
Hinnant先生的库目前还不是C++标准的一部分,而strftime()函数已经成为C标准的一部分很长时间了。似乎在C++20中,Hinnant先生的库将成为C++标准的一部分——届时,要确定偏好会比较困难(这将取决于您的需求和经验)。 - srdjan.veljkovic
本地时间不是线程安全的! - Fabio
请纠正我,但这不仅仅是精确到最近的一秒钟吗? - Edward Falk
1
@EdwardFalk 您是正确的,struct tm 只有秒数,不包括任何更小的时间单位测量。 - srdjan.veljkovic

23

代码解决方案

以下函数将chrono中的time_point转换为字符串(序列化)。

<code>#include <chrono>
#include <iomanip>
#include <sstream>

using time_point = std::chrono::system_clock::time_point;
std::string serializeTimePoint( const time_point& time, const std::string& format)
{
    std::time_t tt = std::chrono::system_clock::to_time_t(time);
    std::tm tm = *std::gmtime(&tt); //GMT (UTC)
    //std::tm tm = *std::localtime(&tt); //Locale time-zone, usually UTC by default.
    std::stringstream ss;
    ss << std::put_time( &tm, format.c_str() );
    return ss.str();
}

// example
int main()
{
    time_point input = std::chrono::system_clock::now();
    std::cout << serializeTimePoint(input, "UTC: %Y-%m-%d %H:%M:%S") << std::endl;

}
</code>

时区

time_point 数据类型没有内部表示时区,因此,通过将其转换为 std::tm(使用函数 gmtimelocaltime)来聚合时区。不建议从输入中加/减时区,因为这样会导致使用 %Z 显示错误的时区,因此最好设置正确的本地时间(依赖于操作系统)并使用 localtime()

技术与用户友好的序列化

对于技术用途,硬编码的时间格式是一个好的解决方案。然而,为了向用户显示,应该使用 locale 来检索用户偏好并以该格式显示时间戳。

C++20

自 C++20 起,我们拥有了很好的序列化和解析函数,用于 time_point 和 duration。

  • std::chrono::to_stream
  • std::chrono::format

你可以从字面值创建std::string,然后从中获取c_str() => 内部存在冗余的malloc。有std::string_view可用于此类目的。 - kyb
1
如果您可以访问C++17,那么您可以使用std::string_view来优化此代码,否则,const char*也是一种可能性。 - Adrian Maire
我用auto替换了time_point。 - alboforlizo

1
很像Adrian的答案,但是加上(可选)毫秒和GMT/本地时间切换。
#include <chrono>
#include <iostream>
#include <iomanip>
#include <sstream>

using Clock = std::chrono::high_resolution_clock;
static std::string timePointToString(const Clock::time_point &tp, const std::string &format, bool withMs = true, bool utc = true)
{
    const Clock::time_point::duration tt = tp.time_since_epoch();
    const time_t durS = std::chrono::duration_cast<std::chrono::seconds>(tt).count();
    std::ostringstream ss;
    if (const std::tm *tm = (utc ? std::gmtime(&durS) : std::localtime(&durS))) {
        ss << std::put_time(tm, format.c_str());
        if (withMs) {
            const long long durMs = std::chrono::duration_cast<std::chrono::milliseconds>(tt).count();
            ss << std::setw(3) << std::setfill('0') << int(durMs - durS * 1000);
        }
    }
    // gmtime/localtime() returned null ?
    else {
        ss << "<FORMAT ERROR>";
    }
    return ss.str();
}

int main() {
    const auto tp = Clock::now();
    std::cout << timePointToString(tp, "%Z %Y-%m-%d %H:%M:%S.") << std::endl;
    return 0;
}

> GMT 2022-04-30 13:44:09.720

测试:https://www.mycompiler.io/view/43wsMbrMcmx

尽管它仅附加了毫秒,并且它们不是模板的一部分...但这将额外增加费用!


0

随着C++20的推出(正如Adrian Maire的回答中所提到的),这将变得更加容易。

#include <chrono>
#include <format>

#include <string>
#include <iostream>

int main()
{
  auto t = std::chrono::system_clock::now();
  std::string s = std::format("{0:%F %R %Z}", t);
  std::cout << s << '\n';
  return 0;
}

注意,截至目前(2023年2月),大多数编译器版本尚未实现std::format,但希望在新版本中很快能够实现。(你可以使用预处理宏__has_include(<format>)__cpp_lib_format来检查。)
关于std::format的文档: https://en.cppreference.com/w/cpp/utility/format/format 格式语法("%F %T %Z")类似于strftime()或者std::put_time()(尽管我还没有检查它们之间的区别),并且在https://en.cppreference.com/w/cpp/chrono/system_clock/formatter有记录。

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