【问题标题】:Convert timestamp to the formatted date time using C++使用 C++ 将时间戳转换为格式化的日期时间
【发布时间】:2020-02-24 10:59:42
【问题描述】:

我只能使用 C++ 标准库 (C++14) 将时间戳转换为给定的日期时间格式。我是 C++ 新手,我知道 C++ 并不能通过像 Java 这样的库为我们提供很多支持。 在给定的日期和时间 2011-03-10 11:23:56 在中欧时区 (CET),以下 将生成标准格式输出:“2011-03-10T11:23:56.123+0100”。

std::string format = "yyyy-MM-dd'T'HH:mm:ss'.'SSSZ"; //default format
auto duration = std::chrono::system_clock::now().time_since_epoch();
auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();

我的格式字符串语法是

G : Era 
yy,yyyy : year (two digits/four digits) 
M,MM : month in number (without zero/ with zero) - (e.g.,1/01) 
MMM,MMMM : month in text (shortname/fullname)- (e.g.,jan/january) 
d,dd : day in month (without zero/with zero)- (e.g.,1/01) 
D : day in year
F : day of week of month
E, EEEE : day of week 
h,hh : hours(1-12) (without zero/with zero)- (e.g.,1/01) 
H,HH : hours(0-23) (without zero/with zero)- (e.g.,1/01) 
m,mm : minutes (without zero/with zero)- (e.g.,1/01) 
s,ss : seconds (without zero/with zero)- (e.g.,1/01) 
S,SS,SSS : milliseconds 
w,W : Week in year (without zero/with zero)- (e.g.,1/01) 
a : AM/PM 
z,zzzz : timezone name 

【问题讨论】:

  • 你试过什么?什么没用?堆栈溢出不是家庭作业写作服务。如果不是作业,只需使用 c++20 日期库 en.cppreference.com/w/cpp/chrono 也可在 howardhinnant.github.io/date/date.html 获得
  • 也许std::put_time 在这里有用?
  • @AlanBirtles “只有 C++ 标准库 (C++14)”怎么样让你觉得他可以使用一个甚至还没有完全实现的 C++ 标准?
  • 看起来您想以 ISO 8601 格式输出时间戳。如果属实,这里应该有一些解决方案:stackoverflow.com/questions/9527960/…
  • @eike 我确实读过这个问题,我试图说服他,除非他在做作业,否则他应该改变立场,让其他人使用图书馆为他做作业

标签: c++ timestamp c++14 chrono formatdatetime


【解决方案1】:
#include <ctime>
#include <iostream>
#include <iomanip>
int main()
{
    auto t = std::time(nullptr);
    auto tm = *std::localtime(&t);
    std::cout << std::put_time(&tm, "%Y-%m-%dT%H:%M:%S.%z%Z") << "\n";
}
----
2019-10-29T05:05:14.-0700PDT

【讨论】:

    【解决方案2】:

    这是一个有点棘手的问题,因为:

    1. 没有明确说明输入是什么。但是从示例代码中我将假设std::chrono::system_clock::time_point

    2. 重要的是要认识到中欧时区 (CET) 被定义为 time zone with a fixed UTC offset of 1h。有些地理区域全年都遵循这个时区规则,有些则没有。没有人一直遵循它。无论如何,这部分问题允许我们硬编码所涉及的 UTC 偏移量:1h。无需进行夏令时调整。

    在 C++14 中,有两种方法可以在不涉及受版权保护(甚至开源)的第 3 方软件的情况下执行此操作:

    1. 使用 C API。

    2. 自己动手。

    1 的问题在于它容易出错。它不直接处理毫秒精度。它不直接处理特定时区,例如 CET。 C API 只知道 UTC 和计算机本地设置的时区。但这些问题是可以克服的。

    2 的问题在于它涉及从std::chrono::system_clock::time_point 中提取年、月和日字段的非直观算术。

    尽管 2 存在问题,但这是我更喜欢的解决方案,我将在下面介绍。我还将展示 C++20 如何让这变得更容易。

    在所有解决方案中,我将通过实现这种形式的函数来形式化输入和输出:

    std::string format_CET(std::chrono::system_clock::time_point tp);
    

    滚动你自己的 (C++14)

    有六个独立的步骤。它将需要这些标头而不需要其他标头:

    #include <chrono>
    #include <string>
    #include <iomanip>
    #include <iostream>
    #include <limits>
    #include <sstream>
    

    A.将输入移动 +1 小时 UTC 偏移量。

    // shift time_point to CET
    tp += 1h;
    

    函数本地 using 指令可以方便地将 UDL h 引入范围,以及此函数中 &lt;chrono&gt; 所需的所有其他内容:

    using namespace std::chrono;
    

    B.获取time_point tp 的两种变体:一种具有毫秒精度,另一种具有天精度:

    // Get time_points with both millisecond and day precision
    auto tp_ms = time_point_cast<milliseconds>(tp);
    auto tp_d = time_point_cast<days>(tp_ms);
    

    重要的是要了解这两个强制转换向零舍入,并且对于负时间点会给出不正确的结果。 system_clock 在 1970-01-01 00:00:00 UTC 的纪元之前给出负时间点。 C++17 引入了floor&lt;millliseconds&gt;(tp) 解决了这个问题。

    日精度time_point将用于提取年、月和日字段,毫秒精度time_point将用于提取小时、分钟、秒和毫秒字段。上面使用的 duration days 直到 C++20 才会添加,但你可以这样做:

    using days = std::chrono::duration<int, std::ratio<86400>>;
    

    C.要从tp_d 获取年、月和日字段,使用public domain algorithms for calendrical operations 之一很方便。这不是第 3 方库。它是用于编写您自己的日历库的算法(这就是我正在解释的内容)。我已经定制了civil_from_days 算法来完全解决format_CET 的需求:

    // Get {y, m, d} from tp_d
    auto z = tp_d.time_since_epoch().count();
    static_assert(std::numeric_limits<unsigned>::digits >= 18,
             "This algorithm has not been ported to a 16 bit unsigned integer");
    static_assert(std::numeric_limits<int>::digits >= 20,
             "This algorithm has not been ported to a 16 bit signed integer");
    z += 719468;
    const int era = (z >= 0 ? z : z - 146096) / 146097;
    const unsigned doe = static_cast<unsigned>(z - era * 146097);          // [0, 146096]
    const unsigned yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365;  // [0, 399]
    int y = static_cast<int>(yoe) + era * 400;
    const unsigned doy = doe - (365*yoe + yoe/4 - yoe/100);                // [0, 365]
    const unsigned mp = (5*doy + 2)/153;                                   // [0, 11]
    const unsigned d = doy - (153*mp+2)/5 + 1;                             // [1, 31]
    const unsigned m = mp + (mp < 10 ? 3 : -9);                            // [1, 12]
    y += (m <= 2);
    

    对于那些想知道它是如何工作的人来说,在上面链接的网站上有一个非常详细的算法推导。

    此时,整数变量{y, m, d}包含年、月、日三元组。

    D.获取自当地午夜以来的持续时间。这将用于提取一天中的当地时间:

    // Get milliseconds since the local midnight
    auto ms = tp_ms - tp_d;
    

    E.获取小时、分钟、秒和毫秒字段:

    // Get {h, M, s, ms} from milliseconds since midnight
    auto h = duration_cast<hours>(ms);
    ms -= h;
    auto M = duration_cast<minutes>(ms);
    ms -= M;
    auto s = duration_cast<seconds>(ms);
    ms -= s;
    

    此时,chrono::duration 变量 {h, M, s, ms} 保持所需的值。

    F。现在我们准备好格式化了:

    // Format {y, m, d, h, M, s, ms} as yyyy-MM-dd'T'HH:mm:ss'.'SSS+0100
    std::ostringstream os;
    os.fill('0');
    os << std::setw(4) << y << '-' << std::setw(2) << m << '-' << std::setw(2)
       << d << 'T' << std::setw(2) << h.count() << ':'
       << std::setw(2) << M.count() << ':' << std::setw(2) << s.count()
       << '.' << std::setw(3) << ms.count() << "+0100";
    return os.str();
    

    使用setw操纵器组合设置每个字段的宽度,用0填充字符,得到所需的前导零。

    C++20 解决方案

    这在 C++20 规范中要容易得多

    std::string
    format_CET(std::chrono::system_clock::time_point tp)
    {
        using namespace std::chrono;
        static auto const CET = locate_zone("Etc/GMT-1");
        return std::format("{:%FT%T%z}", zoned_time{CET, floor<milliseconds>(tp)});
    }
    

    “Etc/GMT-1”是 中欧时区 (CET) 的 IANA 等效项。这个time_zone const* 定位并存储在变量CET 中。 time_point tp 被截断到毫秒精度,并使用 zoned_timetime_zone 配对。然后使用显示的格式字符串格式化此zoned_time(精确到毫秒)。

    存在 C++20 规范的开源(MIT 许可)预览版,语法差异非常小here

    #include "date/tz.h"
    
    std::string
    format_CET(std::chrono::system_clock::time_point tp)
    {
        using namespace date;
        using namespace std::chrono;
        static auto const CET = locate_zone("Etc/GMT-1");
        return format("%FT%T%z", zoned_time<milliseconds>{CET, floor<milliseconds>(tp)});
    }
    

    Some installation is required for Windows.

    此预览版适用于 C++14。在 C++17 及更高版本中,zoned_time&lt;milliseconds&gt; 可以简化为 zoned_time

    自定义时区支持

    还有一种无需安装即可使用预览库的方法。它变成了一个 header-only 库。这是通过创建一个仅模拟 CET 的自定义时区来完成的,然后将其安装在 zoned_time 中。以下是自定义时区的样子:

    #include "date/tz.h"
    
    class CET
    {
    public:
    
        template <class Duration>
            auto
            to_local(date::sys_time<Duration> tp) const
            {
                using namespace date;
                using namespace std::chrono;
                return local_time<Duration>{(tp + 1h).time_since_epoch()};
            }
    
        template <class Duration>
            auto
            to_sys(date::local_time<Duration> tp) const
            {
                using namespace date;
                using namespace std::chrono;
                return sys_time<Duration>{(tp - 1h).time_since_epoch()};
            }
    
        template <class Duration>
            date::sys_info
            get_info(date::sys_time<Duration>) const
            {
                using namespace date;
                using namespace std::chrono;
                return {ceil<seconds>(sys_time<milliseconds>::min()),
                        floor<seconds>(sys_time<milliseconds>::max()),
                        1h, 0min, "CET"};
            }
    
        const CET* operator->() const {return this;}
    };
    

    CET 现在满足了足够的时区要求,可以在zoned_time 中使用并像以前一样格式化。在 C++14 中,语法很复杂,因为必须显式指定 zoned_time 模板参数:

    std::string
    format_CET(std::chrono::system_clock::time_point tp)
    {
        using namespace date;
        using namespace std::chrono;
        using ZT = zoned_time<milliseconds, CET>;
        return format("%FT%T%z", ZT{CET{}, floor<milliseconds>(tp)});
    }
    

    此选项也在 C++20 规范中,其优点是时区缩写(在您的问题中未使用)将正确报告“CET”而不是“+01”。

    更多关于自定义时区的文档见here

    使用这些解决方案中的任何一个,现在都可以像这样执行该功能:

    #include <iostream>
    
    int
    main()
    {
        std::cout << format_CET(std::chrono::system_clock::now()) << '\n';
    }
    

    典型的输出如下所示:

    2019-10-29T16:37:51.217+0100
    

    【讨论】:

    • 非常清晰易懂,非常感谢先生。接下来我必须做的是格式化输出流。你对我有什么建议吗?
    • 是的。请参阅我的答案的“Roll your own (C++14)”副标题下的 F. 段。
    • 你对strftime()函数的看法是在头文件中定义的。因为我的输入格式语法和Java的SimpleDateformat是一样的,所以你觉得这个函数能做到我现在正在尝试的吗?
    • 其实,我想做的就像一个Java 名称为“SimpleDateFormat”的库。输入是时间戳字符串和格式字符串。输出将是格式化的日期。我也是 C++ 的新手。我还编辑了我的帖子。
    • 您的问题很有趣。 strftime 可以处理大部分但不是全部。 strftime 的精度限制为 1 秒。它不支持时区名称,但确实支持时区缩写和时区 UTC 偏移量。标准不保证对后两者的支持。但是,由于您只需要处理“CET”,这应该不是一个重大障碍,因为您可以硬编码名称和偏移量。您还可以编写自定义代码以将毫秒字段放置在您想要的任何位置。 Java 的 SimpleDateFormat 没有直接替代品。
    猜你喜欢
    • 2016-11-26
    • 2015-08-09
    • 2019-02-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-19
    相关资源
    最近更新 更多