如何使用 STL 表示 24 小时制?

How do I represent a 24-hour clock with the STL?

提问人:ulfben 提问时间:11/23/2022 最后编辑:Jarod42ulfben 更新时间:11/24/2022 访问量:195

问:

我正在寻找 STL 的解决方案,用于处理“一天中的时间”。我正在做一个简单的单元测试练习,其行为取决于当前时间是早上、晚上还是晚上。

在第一次迭代中,我使用了一个不起眼的整数作为某个“时间对象”的替身:

using TimeOfDay = int;
constexpr bool isBetween(TimeOfDay in, TimeOfDay min, TimeOfDay max) noexcept {
  return in >= min && in <= max; 
}
constexpr bool isMorning(TimeOfDay in) noexcept { 
  return isBetween(in, 6, 12); }
constexpr bool isEvening(TimeOfDay in) noexcept {
  return isBetween(in, 18, 22);
}
constexpr bool isNight(TimeOfDay in) noexcept {
  return isBetween(in, 22, 24) || isBetween(in, 0, 6);
}

constexpr string_view getGreetingFor(TimeOfDay time) noexcept {
  if (isMorning(time)) {
    return "Good morning "sv;
  }
  if (isEvening(time)) {
    return "Good evening "sv;
  }
  if (isNight(time)) {
    return "Good night "sv;
  }
  return "Hello "sv;
}

这可行,但有几种气味:

  • 根本不是表示 24 小时制的正确类型int
  • isNight()由于换行,需要不必要的复杂比较 (22-06)
  • 理想情况下,我希望能够实际使用系统时钟进行一些测试。
  • std::chrono::system_clock::now()返回一个 ,所以我的理想类型可能应该是可以与 比较的东西,或者很容易从 .std::chrono::time_pointtime_pointtime_point

任何指点将不胜感激!

(我正在使用 C++Latest 在 Visual Studio 中工作(C++ 工作草案的预览,所以大致是 C++23))

C++ STL 标准 C++-Chrono

评论

0赞 Ranoiaetep 11/23/2022
根据你现在的比较逻辑,你的实际上只是. 应该是包含小时、分钟和秒的对象,或者应该是当天的总秒数(小时*3600 + 分钟*60 + 秒)TimeOfDayHourTimeOfDay
0赞 Marek R 11/23/2022
检查这个库 github.com/HowardHinnant/date AFAIK,它应该成为C++标准的一部分。
0赞 n. m. could be an AI 11/23/2022
使用系统时钟进行测试是一个糟糕的主意。您想要可重复性。
1赞 Jarod42 11/23/2022
isNight() 需要不必要的复杂比较,由于换行 (22-06)”您可以反转比较:.return not isBetween(in, 7, 21);
0赞 ulfben 11/23/2022
@Jarod42;事后看来,这是一个美丽的修复。感谢您的建议和编辑帖子!

答:

1赞 Caleth 11/23/2022 #1

如果您谈论的是一天内的时间,与日期无关,请使用 std::chrono::d uration。对于整数小时,有别名 。std::chrono::hours

参见 std::chrono::hh_mm_ss

像这样的东西:

#include <iostream>
#include <chrono>

using namespace std::chrono;

int main()
{
    auto now = system_clock::now().time_since_epoch();
    auto today = duration_cast<days>(now);
    hh_mm_ss time { now - today };
    std::cout << time.hours() << time.minutes() << time.seconds();
}

在 coliru 上看到它

评论

0赞 Ranoiaetep 11/23/2022
注意应该优先于在纪元之前获得正确的结果。floorduration_cast
0赞 Jarod42 11/23/2022
不确定白天的模数是否正确:在夏令时甚至闰秒之间。
0赞 Ranoiaetep 11/23/2022 #2

“一天中的时间”应存储在 .要仅分离任何 的 h/m/s 信息,您可以执行以下操作:std::chrono::durationduration

auto time_info = my_duration - std::chrono::round<std::chrono::days>(my_duration);

同样,您可以分离具有相同接口的 的 h/m/s 信息:time_point

auto now = std::chrono::system_clock::now();
auto time_info = now - std::chrono::round<std::chrono::days>(now);

然后,可以使用帮助程序类来检查信息:hh_mm_ss

auto hms = std::chrono::hh_mm_ss{ time_info };
std::cout << hms.hours() << hms.minutes() << hms.seconds();

要检查 a 是否介于其他两个时间之间,您可以执行以下操作:duration

using TimeOfDay = std::chrono::seconds;

constexpr bool isBetween(TimeOfDay in, TimeOfDay min, TimeOfDay max) noexcept {
    return in >= min && in <= max; 
}

constexpr bool isMorning(TimeOfDay in) noexcept { 
    return isBetween(in, std::chrono::hours{6}, std::chrono::hours{12}); 
}

您还可以通过以下方式启用文本:using namespace std::chrono_literalsusing namespace std::literals

constexpr bool isMorning(TimeOfDay in) noexcept { 
    return isBetween(in, 6h, 12h); 
}

对于包装,您可以执行以下操作: ,但我认为您现在拥有的逻辑更简洁,更容易遵循。isEveningisBetween(in + 12h - days{1}, 10h, 18h)


如果需要,这可以很容易地转回:time_point

auto today = std::chrono::round<std::chrono::days>(std::chrono::system_clock::now());
auto now = today + time_info;
2赞 Howard Hinnant 11/24/2022 #3

我将从您正在寻找一天中的当地时间的假设开始。 表示 UTC。因此,我推荐一个辅助函数,它接受并返回一个表示自最近一个本地午夜以来经过的时间:chrono::system_clockstd::chrono::system_clock::time_pointstd::chrono::system_clock::duration

using TimeOfDay = std::chrono::system_clock::duration;

TimeOfDay
get_local_time_of_day(std::chrono::system_clock::time_point tp) noexcept
{
  using namespace std::chrono;
  auto time = current_zone()->to_local(tp);
  return time - floor<days>(time);
}

chrono::current_zone()返回指向计算机当前设置为的指针。这用于将 转换为本地 .这个本地是一个,但具有不同的时钟,意思是“本地时间”。chrono::time_zonesystem_clock::time_pointtime_pointtime_pointchrono::time_point

该表达式截断了 精度 ,这有效地为您提供了指向最近传递的本地午夜的指针。减去它,你就会得到一个自当地午夜以来的。这将具有与 相同的精度。floor<days>(time)time_pointdaystimechrono::durationdurationtp

此功能无法制作,因为将 UTC 转换为当地时间的规则掌握在您的政客手中,并且可能会发生变化(在许多国家/地区每年两次!constexpr

现在你可以这样写(et al):isMorning

bool isMorning(std::chrono::system_clock::time_point in) noexcept { 
  using namespace std::literals;
  return isBetween(get_local_time_of_day(in), 6h, 12h); }

并这样称呼它:

if (isMorning(system_clock::now())) ...

你的逻辑在我看来很好。并且您的代码可以保持原样(选择 的新定义)。isNightisBetweenTimeOfDay