第 8 章:Linux中使用时钟、计时器和信号

news2024/11/19 19:40:29

在本章中,我们将开始探索Linux环境中可用的各种计时器。随后,我们将深入了解时钟的重要性,并探讨UNIX时间的概念。接下来,我们将揭示在Linux中使用POSIX准确测量时间间隔的方法。之后,我们将进入std::chrono的领域,检查C++为有效的时间相关操作所提供的能力。我们的旅程接着将转向对std::chrono框架中定义的持续时间、时间点和时钟的全面检查。继续前进,我们将熟悉std::chrono内我们可以使用的各种时钟。在我们的探索路径中,我们将开始利用std::chrono提供的日历功能。在我们探索的最后阶段,我们将熟悉时区,并提升我们使用std::chrono的强大工具进行无缝时间转换的专业技能。

在本章中,我们将覆盖以下主要主题:

  • 探索Linux中的计时器
  • 处理C++中的时间
  • 使用时钟、计时器和比率
  • 使用日历和时区功能

那么,让我们开始吧!

技术要求

本章中的所有示例均在以下配置的环境中进行了测试:

  • Linux Mint 21 肉桂版。
  • GCC 13.2,编译器标志:-std=c++20
  • 稳定的互联网连接。
  • 请确保您的环境至少是这么新的。对于所有示例,您也可以替代使用 https://godbolt.org/。

在Linux中处理时间

计时是任何计算机系统的基本方面,Linux也不例外。在Linux中,有不同类型的计时器可用,每种设计用于处理特定的任务和需求。

这些计时器可用于测量程序的执行时间、安排任务、触发事件等。在本节中,我们将探索Linux中可用的不同类型的计时器以及如何有效地使用它们。

以下是Linux系统中使用的不同类型的计时器:

  • 系统计时器:Linux内核使用系统计时器来跟踪时间并安排各种任务。系统计时器用于测量系统运行时间、延迟和超时。Linux中最重要的系统计时器是 Jiffies 计时器,它在系统时钟的每个滴答中增加1。Jiffies计时器用于跟踪自系统启动以来的时间流逝,并且经常被各种内核模块和驱动程序使用。
  • 实时时钟(RTC):RTC是一个硬件时钟,即使系统关闭也会保持日期和时间的跟踪。Linux内核可以通过/dev/rtc设备文件或hwclock命令行工具读取和设置RTC。RTC用于在启动时同步系统时间,并为系统事件维护准确的时间戳。
  • 高分辨率计时器(HRTs):HRTs提供纳秒级分辨率,适用于需要精确计时的实时应用。HRTs可用于测量代码段的执行时间、高精度安排事件或驱动高速硬件。
  • POSIX计时器:POSIX计时器是POSIX标准定义的一组计时器,为Linux中的计时器管理提供统一接口。POSIX计时器可用于设置一次性或周期性计时器,这些计时器可以通过信号或线程触发。POSIX计时器是通过timer_create()timer_settime()timer_delete()系统调用实现的。
  • 计时器队列:计时器队列是Linux内核提供的一种机制,用于安排事件和超时。计时器队列作为事件的优先级队列实现,每个事件都与一个计时器相关联。计时器队列可用于安排周期性任务、实现超时或在特定间隔触发事件。计时器队列在各种内核模块和设备驱动程序中被广泛使用。

但在谈论计时器之前,我们首先需要理解计算机系统中时间的含义。让我们看一下。

Linux纪元

在计算中,纪元指的是用作特定系统或上下文中测量时间的参考点的特定时间点。它作为其他时间值计算或表示的起点。换句话说,这是计算机从中测量系统时间的时间。

纪元通常被定义为一个特定的时间点,通常表示为自特定纪元时间以来经过的秒数、毫秒数或其他比毫秒更小的时间间隔。纪元的选择取决于系统和上下文。例如,在UNIX-like系统中(Linux就是这样的系统),纪元定义为 1970年1月1日00:00:00 UTC(协调世界时间)。这个纪元时间通常被称为 UNIX纪元UNIX时间。在基于UNIX的系统中,时间值通常表示为自UNIX纪元以来经过的秒数。

现在,我们对UNIX纪元有了更好的理解,让我们看一些实际使用这些计时器的示例。

在Linux中使用计时器

既然我们已经知道Linux中可用的不同类型的计时器,让我们探索如何在我们的应用程序中使用它们。我们将看一个示例,它启动一个POSIX计时器并等待直到它被触发:

#include <iostream>
#include <csignal>
#include <unistd.h>
#include <sys/time.h>
#include <atomic>
static std::atomic_bool continue_execution{true};
int main() {
    struct sigaction sa{};
    sa.sa_handler = [](int signum) {
        // Timer triggered, stop the loop.
        std::cout << "Timer expired. Stopping the
          task...\n";
        continue_execution = false;
    };
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sigaction(SIGALRM, &sa, nullptr);
    // Configure the timer to trigger every 1 seconds
    struct itimerval timer{
        .it_interval{.tv_sec{1}, .tv_usec{0}},
        .it_value{.tv_sec{1}, .tv_usec{0}}
    };
    // Start the timer
    setitimer(ITIMER_REAL, &timer, nullptr);
    std::cout << "Timer started. Waiting for timer
      expiration...\n";
    // Keep the program running to allow the timer to
      trigger
    while (continue_execution) {
        sleep(1);
    }
    return 0;
} 

在本示例中,我们定义了一个当计时器到期时将被调用的lambda处理函数。在处理函数中,我们打印出一个消息,指示计时器已到期,并设置忙碌循环的退出条件。

我们使用sigaction函数设置信号处理程序。然后,我们使用itimerval结构的it_intervalit_value成员配置计时器。配置计时器后,我们通过调用带有ITIMER_REAL选项的setitimer POSIX函数来启动它,该选项设置一个实时计时器,在到期时发送SIGALRM信号。我们进入一个循环以使程序无限期地运行。循环内的sleep(1)调用确保程序不会立即退出,并允许计时器触发。

程序的输出如下:

Program returned: 0
Timer started. Waiting for timer expiration...
Timer expired. Stopping the task... 

软件开发中的另一个常见任务是测量代码段的执行时间。这也可以通过使用POSIX时间功能来实现。要测量代码段的执行时间,我们可以在POSIX中使用HRT(高分辨率计时器)。

要在POSIX中使用HRT,我们将使用clock_gettime()函数以及CLOCK_MONOTONIC时钟ID。以下是一个演示在POSIX中使用HRTs的示例:

#include <iostream>
#include <ctime>
static const auto LIMIT{10000};
void just_busy_wait_f() {
    for (auto i{0}; i < LIMIT; ++i) {
        for (auto j{0}; j < LIMIT; ++j);
    }
}
int main() {
    timespec start, end;
    // Start the timer
    clock_gettime(CLOCK_MONOTONIC, &start);
    // Measured code segment
    just_busy_wait_f();
    // Stop the timer
    clock_gettime(CLOCK_MONOTONIC, &end);
    // Calculate the elapsed time
    const auto elapsed{(end.tv_sec - start.tv_sec) +
      (end.tv_nsec - start.tv_nsec) / 1e9};
    std::cout << "Elapsed time: " << elapsed << "
      seconds\n";
    return 0;
} 

在这个示例中,我们声明了两个timespec结构体startend,以保存计时器的开始和结束时间戳。我们使用clock_gettime()函数获取具有高分辨率的当前时间。

我们调用clock_gettime()两次:一次在任务开始时(记录开始时间),一次在结束时(记录结束时间)。使用的是CLOCK_MONOTONIC时钟ID,它代表一个不受系统时间调整影响的单调时钟。

捕获开始和结束时间戳后,我们通过减去时间戳的秒和纳秒组成部分来计算经过的时间。然后以秒为单位打印结果。

我们测试实验室的示例输出如下:

Program returned: 0
Elapsed time: 0.169825 seconds 

请记住,在您的环境中,结果可能会有所不同。

请注意,这个示例演示了使用计时器测量执行时间的一种方法。根据您的需求,您可以选择不同的计时器机制。

POSIX计时器的特性

让我们来看一下POSIX计时器具有的一些特性:

  • 功能强大且灵活:POSIX计时器提供了丰富的特性集,包括不同的计时器类型(例如间隔计时器和一次性计时器)、各种时钟来源以及对计时器行为的精确控制。
  • 底层控制:POSIX计时器提供对计时器设置的细粒度控制,例如信号处理和计时器到期行为。
  • 遗留支持:POSIX计时器是POSIX API的一部分,长期以来一直在UNIX-like系统中可用,如果您需要与遗留代码或特定的POSIX要求保持兼容性,则它们非常合适。
  • 平台特定:POSIX计时器并非在所有平台上都可用,因此如果可移植性是您关注的问题,最好切换到更合适的选择。

但是,在C++中,我们有什么更好的替代方案呢?我们将在下一节中看到。

在C++中处理时间

虽然POSIX计时器有其自身的优点,但在C++中,有些库提供了更高级别和更可移植的解决方案,用于计时和与时间相关的操作。

一个很好的示例是std::chrono库。这是一个C++库,为处理与时间相关的操作和测量提供了一套实用工具。它是标准库的一部分,并包含在<chrono>头文件中。std::chrono库提供了一种灵活和类型安全的机制,用于表示和操作时间持续时间、时间点、时钟和与时间相关的操作。通过使用std::chrono,您将受益于C++标准库提供的标准化、类型安全性、灵活性和集成性。与传统的POSIX方法相比,std::chrono的一些优势如下:

  • 标准化std::chrono是C++标准库的一部分,使其成为一个跨平台解决方案,可在不同的操作系统和编译器上一致地工作。相比之下,POSIX特定于UNIX-like系统,在所有平台上可能无法使用或行为不一致。
  • 类型安全性std::chrono提供了时间持续时间和时间点的类型安全表示。它提供了丰富的持续时间和时钟类型集,可以无缝使用在一起,使代码更安全和更具表现力。POSIX计时器虽然功能强大,但通常依赖于低级类型,如timespec结构体,这可能容易出错并需要手动转换。
  • 灵活性和表现力std::chrono提供了一个灵活且富有表现力的时间相关操作接口。它提供了方便的方式来对持续时间执行算术运算、在不同时间单位之间转换以及格式化时间值。POSIX计时器虽然适用于特定的计时要求,但缺乏std::chrono提供的高级抽象和实用工具。
  • 与标准库的集成std::chrono与C++标准库的其他部分无缝集成。它可以与算法、容器和并发设施一起使用,允许更加协调和高效的代码。POSIX计时器作为一个更低级别的接口,可能需要额外的工作才能与其他C++标准库组件集成。
  • 与现代C++功能的兼容性std::chrono受益于在现代C++中引入的进步和特性。它支持用户定义字面量、lambda函数和类型推导等特性,使编写简洁和富有表现力的代码变得更容易。POSIX计时器作为POSIX API的一部分,可能无法完全利用现代C++语言特性。

<chrono>库提供了一套全面的特性集,用于处理与时间相关的操作,如测量时间持续时间、表示时间点以及执行各种时间计算和转换。以下是std::chrono的一些关键组件和特性:

  • 时钟<chrono>定义了几种时钟类型,代表着不同的时间来源和不同的纪元。std::chrono::system_clock代表了系统范围的实时时钟(RTC),可以调整。std::chrono::steady_clock代表一个稳定的单调时钟,不受系统时间调整的影响,而std::chrono::high_resolution_clock代表具有最高可用分辨率的时钟(如果系统支持)。
  • 持续时间std::chrono::duration模板类表示一个时间间隔,即指定的时间段。持续时间是使用特定时间单位的滴答计数;例如,五小时的持续时间是单位 小时 的五个滴答。可以定义不同类型的持续时间,从年到纳秒。示例持续时间包括std::chrono::secondsstd::chrono::millisecondsstd::chrono::months
  • 时间点:时间点表示与特定时钟的纪元相比的特定时间点。std::chrono::time_point模板类由时钟和持续时间类型参数化。
  • 时间转换std::chrono允许在持续时间和时间点之间进行转换,以及涉及持续时间的算术运算。它提供了例如std::chrono::duration_cast的函数,以在不同持续时间之间转换,以及std::chrono::time_point_cast在不同时间点之间转换。
  • 时钟实用工具std::chrono提供了查询当前时间的实用工具,如std::chrono::system_clock::now(),它返回当前系统时间点。
  • Chrono字面量std::chronostd::literals::chrono_literals命名空间中提供了用户定义的、与时间相关的字面量。它们允许您使用带有时间单位的字面量创建std::chrono::duration对象。这使得在处理与时间相关的计算时代码更加易读和方便。
  • 日历std::chrono提供了日历功能,例如处理天、月和年。它还提供了闰年和闰秒的标记。
  • 时区std::chrono提供了根据地理位置不同的全球各个时区的信息。

通过使用std::chrono,您可以进行准确且可移植的时间测量,处理超时,计算时间差异,并以类型安全的方式处理与时间相关的操作。

重要说明

以下是C++参考文档中<chrono>头文件的链接:C++ <chrono>文档。

以下是一个示例,展示如何使用std::chrono来测量代码片段的执行时间:

#include <iostream>
#include <chrono>
using namespace std::chrono;
int main() {
    const auto start{steady_clock::now()}; // {1}
    just_busy_wait_f(); // {2}
    const auto end{steady_clock::now()}; // {3}
    const auto dur{duration_cast<milliseconds>(end -
      start)}; // {4}
    std::cout << "Execution time: " << dur.count() << "
      milliseconds\n"; // {5}
    return 0;
} 

在上述示例中,使用std::chrono::steady_clock来测量与前一个示例中相同函数的执行时间(见标记{2})。startend变量代表使用steady_clocknow()静态函数,在代码执行前后所取的时间点(见标记{1}{3})。使用std::chrono::duration_cast将时间点之间计算出的持续时间转换为毫秒(见标记{4})。

程序的输出应该类似于这样:

Program returned: 0
Execution time: 179 milliseconds 

正如您所见,std::chrono::duration类有一个count()方法,它返回特定持续时间中的单位数;见标记{5}

但让我们更深入地了解这是如何真正工作的。

使用时钟、计时器和比率

在继续介绍更多有关时钟和计时器的示例之前,我们首先需要更好地理解chrono库是如何定义持续时间的。

正如我们在前面的示例中看到的,持续时间是两个时间点之间的距离,称为时间点。在我们之前的示例中,这些是startend时间点。

Figure 8.1 – Timepoint and duration

图8.1 – 时间点和持续时间

持续时间本身是滴答数的计数和一个分数的组合,该分数表示从一个滴答到下一个滴答的时间(以秒为单位)。这个分数由std::ratio类表示。以下是一些例子:

using namespace std::chrono;
constexpr std::chrono::duration<int, std::ratio<1>>
  six_minutes_1{360};
constexpr std::chrono::duration<double, std::ratio<3600>>
  six_minutes_2{0.1};
constexpr std::chrono::minutes six_minutes_3{6};
constexpr auto six_minutes_4{6min};
std::cout << six_minutes_1 << '\n';
std::cout << six_minutes_2 << '\n';
std::cout << six_minutes_3 << '\n';
std::cout << six_minutes_4 << '\n';
static_assert(six_minutes_1 == six_minutes_2);
static_assert(six_minutes_2 == six_minutes_3);
static_assert(six_minutes_3 == six_minutes_4); 

在上述示例中,我们以几种方式定义了六分钟的持续时间。在six_minutes_1变量中,我们将这个持续时间指定为360秒的值。同样的持续时间也可以表示为1小时的1/10 - six_minutes_2变量。最后两个持续时间 - six_minutes_3six_minutes_4 - 使用std::chrono预定义的持续时间类型和字面量表示相同的六分钟持续时间。以下是前面代码块的输出:

360s
0.1h
6min
6min 

正如您所见,std::duration还提供了漂亮的格式化功能,这样一旦将持续时间传递给字符串或流操作符,它将添加相应的后缀,这样我们就可以看到持续时间的类型。

为了确保前面的持续时间真的对应于六分钟,我们使用了static_assert对它们进行了测试,如果它们不匹配,程序将失败。

重要说明

以下是C++参考文档中std::duration类的链接:C++ std::duration文档。

让我们回到之前的示例,稍作修改,并更仔细地查看timepoint对象:

using namespace std::chrono;
const time_point start{steady_clock::now()}; // {1}
const duration epoch_to_start{start.time_since_epoch()}; //
  {2}
std::cout << "Time since clock epoch: " << epoch_to_start
  << '\n'; // {3} 

正如您所见,我们再次构造了一个timepoint对象start,其中获取了其实例化时刻来自Linux系统的steady_clock的时间;见标记{1}std::chrono::time_point类存储了一个std::chrono::duration值,它实际上表示从时钟纪元开始的时间间隔。为了允许获取该值,std::chrono::duration类公开了一个方法,返回time_since_epoch()的持续时间,以纳秒为单位;见标记{2}

以下是我们测试环境中执行前述代码的结果。请记住,如果您执行此代码,结果可能会有所不同:

Time since clock epoch: 2080809926594ns

在某些用例中,持续时间以纳秒为单位可能不方便,例如我们的示例中计算代码块执行所需的时间。然而,将持续时间从更高精度的类型转换为更低精度的类型会导致精度丢失。因此,如果我们需要以分钟而不是纳秒来查看持续时间,我们不能只做这个:

using namespace std::chrono;
const minutes
  dur_minutes{steady_clock::now().time_since_epoch()}; 

这是因为前述代码将无法编译。其背后的原因是time_since_epoch()方法返回的持续时间精度为纳秒。如果我们将数据存储为分钟,我们肯定会失去精度。为了确保这不会被错误地完成,编译器会阻止我们。

但我们如何有意地将持续时间值从一种精度转换为另一种精度呢?正如我们在第一个示例中看到的,我们可以使用库提供的std::chrono::duration_cast函数。它使我们能够从具有更高精度的持续时间类型转换为具有更低精度的持续时间类型。让我们重新处理前述示例,看看这是如何工作的:

using namespace std::chrono;
auto dur_from_epoch{steady_clock::now()
  .time_since_epoch()}; // {1}
minutes dur_minutes{duration_cast<minutes>
  (dur_from_epoch)}; // {2}
std::cout << "Duration in nanoseconds: " << dur_from_epoch
  << '\n'; //{3}
std::cout << "Duration in minutes: " << dur_minutes <<
  '\n'; //{4} 

正如标记{1}中所见,我们再次从时钟纪元获取以纳秒为单位的持续时间。在标记{2}中,我们初始化了另一个持续时间变量,这次是以分钟为单位。为此,我们使用std::chrono::duration_cast<minutes>,它将源分辨率的值转换为目标分辨率,并将其截断为最接近的整数值。在我们的测试环境中,前述代码块的结果如下:

Duration in nanoseconds: 35206835643934ns
Duration in minutes: 586min 

我们可以看到,以纳秒为单位测量的持续时间大约相当于586.78分钟,但被截断为586分钟。

当然,我们可能也需要向上取整而不仅仅是向下截断值。幸运的是,chrono库提供了这种能力,即std::chrono::round方法,它正好做到了这一点。这是一个示例:

using namespace std::chrono;
seconds dur_sec_1{55s}; //{1}
seconds dur_sec_2{65s}; //{2}
minutes dur_min_1{round<minutes>(dur_sec_1)}; //{3}
minutes dur_min_2{round<minutes>(dur_sec_2)}; //{4}
std::cout << "Rounding up to " << dur_min_1 << '\n';
std::cout << "Rounding down to " << dur_min_2 << '\n'; 

在这个示例中,我们定义了两个持续时间变量,dur_sec_1dur_sec_2dur_sec_1初始化为55秒(见标记{1}),dur_sec_2初始化为65秒(见标记{2})。然后,使用std::chrono::round函数,我们用分钟的分辨率初始化了另外两个持续时间变量(见标记{3}{4})。两个持续时间变量都被四舍五入到一分钟:

Rounding up to 1min
Rounding down to 1min 

chrono库还提供了用于持续时间的ceilfloor方法。所有这些都可以在官方文档中找到。

重要说明

roundfloorceil方法的持续时间文档可以在以下链接中找到:std::chrono::duration::roundstd::chrono::duration::floorstd::chrono::duration::ceil

既然我们对时间操作有了更好的理解,让我们更仔细地看看std::chrono为我们提供的不同类型的时钟。

C++20中关于时钟的更多信息

我们在之前的示例中已经使用了std::chrono::steady_clock。这只是C++ chrono库中预定义的时钟之一,您可以使用它。std::chrono::steady_clock顾名思义,是一个稳定的时钟。这意味着它是一个单调时钟,其中时间只向前移动,其时间点值始终在增加。当我们想要测量时间间隔时,它适用于使用。它的纪元可能会有所不同。

另一个经常使用的时钟是std::chrono::system_clock。在Linux中,它代表系统测量的时间。这意味着它不能保证是单调的,而且可以随时调整。在Linux中,它的纪元与UNIX纪元相匹配。让我们看一个例子:

using namespace std::chrono;
time_point<system_clock> systemClockEpoch;
std::cout << std::format("system_clock epoch:
  {0:%F}T{0:%R%z}.", systemClockEpoch) << '\n'; 

前述示例打印出Linux系统时钟纪元,它对应于UNIX纪元 - 1970年1月1日00:00:00 UTC

system_clock epoch: 1970-01-01T00:00+0000.

请记住,std::chrono::system_clock不考虑闰秒,这些闰秒可以从测量的时间中增加或减去。通常,闰秒是对UTC的一秒调整,这可能会每年发生两次,以反映地球绕太阳旋转的准确性。

重要说明

有关闰秒的更多信息可以在此处找到。

C++20引入了几个更多预定义的时钟。其中一些是std::chrono::utc_clock,它测量UTC,以及std::chrono::tai_clock,它测量国际原子 时间TAI)。

重要说明

有关UTC和TAI的更多信息可以在此处和此处找到。

TAI时钟和UTC时钟之间的一个关键区别是,UTC时钟保证会考虑自时钟纪元以来所做的闰秒校正,但TAI时钟不会考虑这些。让我们看一个例子:

using namespace std::chrono;
tai_time tai{tai_clock::now()};
utc_time utc{utc_clock::now()};
std::cout << "International atomic time (TAI): " << tai <<
  '\n';
std::cout << "Coordinated universal time (UTC): " << utc <<
  '\n'; 

在前述示例中,我们从两个时钟 - utctai - 获取当前时间。以下是结果:

International atomic time (TAI): 2023-08-04 14:02:57.95506
Coordinated universal time (UTC): 2023-08-04 14:02:20.95506 

正如您所见,尽管两个时钟都在同一时间被调用,但它们显示的时间不同。它们的差异正好是37秒。这种差异来自自1972年引入闰秒以来所做的闰秒调整。

std::chrono::utc_clock应用了闰秒调整。通过使用chrono的UTC时钟,这些闰秒调整将自动为您完成,您不需要采取任何特殊行动。因此,chrono库提供了一种转换时钟类型的方法 - std::chrono::clock_cast,它将std::chrono::time_point值从一个时钟转换为另一个时钟。让我们看另一个例子:

using namespace std::chrono;
tai_time tai{tai_clock::now()};
std::cout << "International atomic time (TAI): " << tai <<
  '\n';
utc_time utc{clock_cast<utc_clock>(tai)};
std::cout << "Coordinated universal time (UTC): " << utc <<
  '\n'; 

正如您所见,由chrono的TAI时钟生成的time_point tai对象被转换为UTC时钟的时间点。结果如下:

International atomic time (TAI): 2023-08-04 14:16:22.72521
Coordinated universal time (UTC): 2023-08-04 14:15:45.72521 

正如我们所预期的,TAI时钟比UTC时钟快37秒。因此,UTC不能用于正确测量时间差异,因为可能会增加或减少闰秒。

重要说明

您可以在此处找到C++ chrono库中的所有预定义时钟。

现在,既然我们对计时和时钟有了良好的了解,让我们看看C++ chrono库为日历和时区提供了哪些功能。

使用日历和时区功能

C++20为标准库引入了对日历和时区操作的全新支持。当我们谈论日历操作时,这意味着以天、月和年为单位的操作。它们与时区概念一起,允许在不同时区之间转换时间,同时考虑到时区调整,如夏令时。

让我们定义一个日期,并借助chrono库打印它:

using namespace std::chrono;
year theYear{2023};
month theMonth{8};
day theDay{4};
std::cout << "Year: " << theYear;
std::cout << ", Month: " << theMonth;
std::cout << ", Day: " << theDay << '\n'; 

如您所见,std::chrono命名空间提供了yearmonthday类,使处理日期变得容易。这些类的好处在于,它们提供了严格的类型和边界检查、一些加法和减法操作符以及格式化功能。前述代码的结果如下:

Year: 2023, Month: Aug, Day: 04

如您所见,将Month变量传递给operator<<时会应用格式化,使月份的值打印为Aug。此外,这些类还提供了对应用值的验证和边界检查:

using namespace std::chrono;
std::cout << "Year: " << year{2023} ;
std::cout << ", Month: " << month{13};
std::cout << ", Day: " << day{32} << '\n'; 

在前述示例中,我们应用了无效的月份和日期。结果如下:

Year: 2023, Month: 13 is not a valid month, Day: 32 is not a valid day

如您所见,monthday值经过验证,当它们传递给operator<<时,它会打印出这些值不是有效的。

year类代表了格里高利历的一年,它使我们能够询问该年是否是闰年:

using namespace std::chrono;
sys_time now{system_clock::now()};
year_month_day today{floor<days>(now)};
std::cout << "Today is: " << today << '\n';
year thisYear{today.year()};
std::cout << "Year " << thisYear;
if (thisYear.is_leap()) {
    std::cout << " is a leap year\n";
} else {
    std::cout << " is not a leap year\n";
} 

在这个示例中,我们首先获取当前的系统时间 - now - 然后将其转换为year_month_day类型的对象。这个对象代表了一个方便的基于字段的时间点。它持有yearmonthday对象,并允许直接访问它们。它还支持从std::chrono::sys_days实例化,这实际上是以天为单位的系统时钟的时间点。因此,我们传递now时间点并创建today对象。然后,我们获取year对象 - thisYear - 并使用year类的is_leap()方法检查这是否是闰年:

Today is: 2023-08-05
Year 2023 is not a leap year 

正如预期的那样,2023年不是闰年。

chrono库大量使用operator/来创建日期。C++20为这个操作符的参数提供了大约40种重载。让我们看一个例子:

using namespace std::chrono;
year_month_day date1{July/5d/2023y};
year_month_day date2{1d/October/2023y};
year_month_day date3{2023y/January/27d};
std::cout << date1 << '\n';
std::cout << date2 << '\n';
std::cout << date3 << '\n'; 

如您所见,我们通过传递新引入的chrono字面量(对于月份、天和年份)以及operator/来创建一个year_month_day对象。chrono为创建天提供了方便的字面量;你只需在天数值后加上d。对于年份也是如此,你需要加上y就可以构造一个year对象。对于月份,chrono库为一年中的所有月份定义了命名常量。

重要说明

以下是chrono库中月份常量列表的链接:chrono库中的月份常量。

在实例化year_month_day对象时,我们使用operator/传递日期值。如前述示例所示,chrono支持多种组合的日、月和年值。所有这些都可以在标准文档中找到。

重要说明

以下是关于operator/的所有重载的文档,用于日期管理:日期管理的operator/重载。

我们示例中使用的所有重载都应该创建有效的year_month_date对象。让我们看看输出:

2023-07-05
2023-10-01
2023-01-27 

如我们所见,我们已经成功地借助chrono字面量和operator/创建了三个不同的有效日期。

在C++中处理时区

C++20的chrono库为处理时区提供了功能。它集成了IANA时区数据库,该数据库包含了全球许多地理位置的本地时间信息。

重要说明

有关IANA时区数据库的更多信息,请访问:IANA时区数据库。

使用chrono,您可以获取IANA数据库的副本,并浏览特定地理位置的信息:

using namespace std::chrono;
const tzdb& tzdb{get_tzdb()};
const std::vector<time_zone>& tzs{tzdb.zones};
for (const time_zone& tz : tzs) {
    std::cout << tz.name() << '\n';
} 

如示例所示,在std::chrono命名空间中,有一个方法 - get_tzdb() - 它返回对IANA数据库的引用。在数据库中,您可以找到有关其版本的信息,还可以获取所有可用std::chrono::time_zone对象的排序列表。

std::chrono::time_zone类存储了其特定地理区域和名称的时区之间转换的信息。前述示例的输出如下:

Africa/Abidjan
Africa/Accra
Africa/Addis_Ababa
Africa/Algiers
Africa/Asmara
Africa/Bamako
... 

现在,既然我们有了所有可用的时区,让我们尝试基于地理位置找到特定的一个,并看看那里的时间是什么样的:

using namespace std::chrono;
const tzdb& tzdb{get_tzdb()};
const std::vector<time_zone>& tzs{tzdb.zones};
const auto& res{std::find_if(tzs.begin(), tzs.end(), []
  (const time_zone& tz){
    std::string name{tz.name()};
    return name.ends_with("Sofia");
})};
if (res != tzs.end()) {
    try {
        const std::string_view myLocation{res->name()};
        const std::string_view london{"Europe/London"};
        const time_point now{system_clock::now()};
        const zoned_time zt_1{myLocation, now};
        const zoned_time zt_2{london, now};
        std::cout << myLocation << ": " << zt_1 << '\n';
        std::cout << london << ": " << zt_2 << '\n';
    } catch (const std::runtime_error& e) {
        std::cout << e.what() << '\n';
    }
} 

在这个示例中,我们再次获取了可用时区的列表,并尝试找到索菲亚(Sofia)城市的时区。然后,我们使用找到的时区的全名来创建另一个对象,该对象使用特定的地理位置和系统时间的值 - std::chrono::zoned_time。这个类代表了时区和时间点之间的逻辑配对。我们还为伦敦(London)城市创建了另一个zoned_time zt_2对象,它代表与zt_1相同的时间点,但在另一个地理位置。前述代码的结果如下:

Europe/Sofia: 2023-08-05 13:43:53.503184619 EEST
Europe/London: 2023-08-05 11:43:53.503184619 BST 

如您所见,两个对象都显示了有效的时间,但考虑到了它们的地理位置。这就是我们如何安全地获取特定地理位置的当前时间,同时也考虑到夏令时。

总结

在本章中,我们探索了Linux环境中可用的不同计时器。随后,我们了解了时钟纪元背后的重要性和UNIX时间的概念。接着,我们深入探讨了在Linux中准确测量时间的POSIX的实际实现。此外,我们研究了std::chrono的领域,并检查了C++为有效的时间相关操作所提供的一系列功能。我们的探索随后带我们详细了解了std::chrono框架内定义的持续时间、时间点和时钟。接下来,我们熟悉了std::chrono内可用的各种时钟类型。随着我们的旅程继续,我们开始探索std::chrono所提供的日历功能。最后,我们熟悉了时区,并提高了我们利用std::chrono所提供的工具无缝执行时间转换的熟练度。现在,我们已经准备好进入下一章,我们将更深入地探讨C++内存模型的细节。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1428152.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

市场复盘总结 20240202

仅用于记录当天的市场情况&#xff0c;用于统计交易策略的适用情况&#xff0c;以便程序回测 短线核心&#xff1a;不参与任何级别的调整&#xff0c;采用龙空龙模式 一支股票 10%的时候可以操作&#xff0c; 90%的时间适合空仓等待 昨日主题投资 连板进级率 6/30 20% 二进三…

vue全家桶之路由管理Vue-Router

一、前端路由的发展历程 1.认识前端路由 路由其实是网络工程中的一个术语&#xff1a; 在架构一个网络时&#xff0c;非常重要的两个设备就是路由器和交换机。当然&#xff0c;目前在我们生活中路由器也是越来越被大家所熟知&#xff0c;因为我们生活中都会用到路由器&#…

基于最新koa的Node.js后端API架构与MVC模式

Koa 是一个由 Express 原班人马打造的现代 Web 框架&#xff0c;用于 Node.js。它旨在提供一个更小、更富有表现力和更强大的基础&#xff0c;用于 Web 应用和 API 开发。Koa 不捆绑任何中间件&#xff0c;它提供了一个优雅的方法以组合不同的中间件来处理请求和响应。 Koa 的核…

【QT】坐标系统和坐标变换

目录 1 坐标变换函数 1.1 坐标平移 1.2 坐标旋转 1.3 缩放 1.4 状态保存与恢复 2 坐标变换绘图实例 2.1 绘制3个五角星的程序 2.2 绘制五角星的PainterPath的定义 3 视口和窗口 3.1 视口和窗口的定义与原理 3.2 视口和窗口的使用实例 4 绘图叠加的效果 1 坐标变换函数 QPainter…

链表与二叉树-数据结构

链表与二叉树-数据结构 创建叶子node节点建立二叉树三元组&#xff1a;只考虑稀疏矩阵中非0的元素&#xff0c;并且存储到一个类&#xff08;三元组&#xff09;的数组中。 创建叶子node节点 class Node{int no;Node next;public Node(int no){this.nono;} } public class Lb…

24.云原生ArgoCD高级之数据加密seale sealed

云原生专栏大纲 文章目录 数据加密之seale sealedBitnami Sealed Secrets介绍Bitnami Sealed Secrets工作流程安装sealed-secrets和kubeseal安装sealed-secrets-controller安装kubeseal通过kubeseal将sealed-secrets公钥拿出来通过kubeseal加密secrets替换kustomize下secret为…

Leetcode 热门百题斩(第二天)

介绍 针对leetcode的热门一百题&#xff0c;解决大多数实习生面试的基本算法题。通过我自己的思路和多种方法&#xff0c;供大家参考。 1.两数之和&#xff08;题号&#xff1a;1) 方法一 最先想到的就是两个for去遍历匹配。 class Solution {public int[] twoSum(int[]…

DPVS 多活部署架构部署

一、目标 利用DPVS部署一个基于OSPF/ECMP的提供HTTP服务的多活高可用的测试环境。 本次部署仅用于验证功能&#xff0c;不提供性能验证。 配置两台DPVS组成集群、两台REAL SERVER提供实际HTTP服务。 注&#xff1a;在虚拟环境里面&#xff0c;通过在一台虚拟服务器上面安装FR…

Linux - iptables 防火墙

一. 安全技术和防火墙 1.安全技术 入侵检测系统&#xff08;Intrusion Detection Systems&#xff09;&#xff1a;特点是不阻断任何网络访问&#xff0c;量化、定位来自内外网络的威胁情况&#xff0c;主要以提供报警和事后监督为主&#xff0c;提供有针对性的指导措施和安全…

阿里云如何找回域名,进行添加或删除?

权威域名管理介绍说明&#xff0c;包含添加域名、删除域名、找回域名、域名分组等操作介绍。 一、添加域名 非阿里云注册域名或子域名如需使用云解析DNS&#xff0c;需要通过添加域名功能&#xff0c;将主域名或子域名添加到云解析控制台&#xff0c;才可以启用域名解析服务。…

Redis -- hash哈希

马行软地易失蹄&#xff0c;人贪安逸易失志。 目录 关于Redis的hash hash命令 hset hget hexists hdel hkeys hvals hgetall hmget hlen hsetnx hash计数 hincrby hincrbyfloat 小结 关于Redis的hash 几乎所有的主流编程语言都提供了哈希&#xff08;hash&a…

基于极大似然法和最小二乘法系统参数辨识matlab仿真,包含GUI界面

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 1.极大似然法系统参数辨识 2. 最小二乘法系统参数辨识 5.完整程序 1.程序功能描述 分别对比基于极大似然法的系统参数辨识以及基于最小二乘法的系统参数辨识&#xff0c;输出起参数辨识收敛…

计算存储设备(Computational Storage Drive, CSD)

随着云计算、企业级应用以及物联网领域的飞速发展&#xff0c;当前的数据处理需求正以前所未有的规模增长&#xff0c;以满足存储行业不断变化的需求。这种增长导致网络带宽压力增大&#xff0c;并对主机计算资源&#xff08;如内存和CPU&#xff09;造成极大负担&#xff0c;进…

计算机网络实验二

目录 实验二 交换机的基本配置 1、实验目的 2、实验设备 &#xff08;1&#xff09;实验内容&#xff1a; &#xff08;2&#xff09;练习&#xff1a; 1.实验内容一&#xff1a;&#xff08;交换机的配置方式&#xff09; 2.实验内容二&#xff1a;&#xff08;交换机…

碳汇的概念

碳汇的概念 在理解碳汇之前&#xff0c;首先理解“汇”&#xff0c;汇是指存储温室气体、气溶胶或温室气体化合物的库&#xff0c;例如土壤、海洋、森林等都是地球的重要碳库。根据《联合国气候变化框架公约》的定义&#xff0c;“碳汇”指从大气中清除二氧化碳等温室气体的过…

有没有合适写毕业论文的AI工具?

最近挺多同学在忙着写毕业论文&#xff0c;不断在“提交-打回-修改-提交”过程里循环着&#xff0c;好不容易写完了&#xff0c;还得考虑论文查重的问题&#xff01;基哥作为一名曾经的毕业生&#xff0c;当然也体验过这种痛苦了。 但是&#xff0c;大人&#xff0c;时代变了&…

元素的显示与隐藏,精灵图,字体图标,CSSC三角

元素的显示与隐藏 类似网站广告&#xff0c;当我们点击关闭就不见了&#xff0c;但是我们重新刷新页面&#xff0c;会重新出现 本质&#xff1a;让元素在页面中隐藏或者显示出来。 1.display显示隐藏 2.visibility显示隐藏 3.overflow溢出显示隐藏 1.display属性&#xff08;…

使用STM32 DMA实现高效数据传输的设计与优化

使用STM32的DMA功能可以有效地实现高效的数据传输。在下面的解释中&#xff0c;我将介绍如何设计和优化使用STM32 DMA进行高效数据传输的方法。同时&#xff0c;我将提供一些示例代码来帮助您理解和实践。 ✅作者简介&#xff1a;热爱科研的嵌入式开发者&#xff0c;修心和技术…

缓存相关问题记录解决

缓存相关问题 在这里我不得不说明,我写的博客都是我自己用心写的,我自己用心记录的,我写的很详细,所以会有点冗长,所以如果你能看的下去的化,会有所收获,我不想写那种copy的文章,因为对我来说没什么益处,我写的这篇博客,就是为了记录我缓存的相关问题,还有我自己的感悟,所以如果…

机器学习_12_梯度下降法、拉格朗日、KKT

文章目录 1 梯度下降法1.1 导数、梯度1.2 梯度下降法1.3 梯度下降法的优化思想1.4 梯度下降法的调优策略1.5 BGD、SGD、MBGD1.5.1 BGD、SGD、MBGD的区别 2 有约束的最优化问题3 拉格朗日乘子法3.1 拉格朗日乘子法理解3.2 对偶问题 4 KKT条件4.1 KKT条件理解4.2 KKT公式理解4.3 …