Skip to content

Commit

Permalink
Merge pull request #2659 from LukashonakV/cpp20_clock
Browse files Browse the repository at this point in the history
c++20. clock chrono API. STL + format
Alexays authored Nov 12, 2023
2 parents c4330a1 + 86a3898 commit a51dd8f
Showing 8 changed files with 367 additions and 362 deletions.
2 changes: 1 addition & 1 deletion include/AModule.hpp
Original file line number Diff line number Diff line change
@@ -40,7 +40,7 @@ class AModule : public IModule {

private:
bool handleUserEvent(GdkEventButton *const &ev);

const bool isTooltip;
std::vector<int> pid_;
gdouble distance_scrolled_y_;
gdouble distance_scrolled_x_;
74 changes: 36 additions & 38 deletions include/modules/clock.hpp
Original file line number Diff line number Diff line change
@@ -6,38 +6,26 @@

namespace waybar::modules {

const std::string kCalendarPlaceholder = "calendar";
const std::string KTimezonedTimeListPlaceholder = "timezoned_time_list";

enum class WeeksSide {
LEFT,
RIGHT,
HIDDEN,
};
const std::string kCldPlaceholder{"calendar"};
const std::string kTZPlaceholder{"tz_list"};

enum class CldMode { MONTH, YEAR };
enum class WS { LEFT, RIGHT, HIDDEN };

class Clock final : public ALabel {
public:
Clock(const std::string&, const Json::Value&);
virtual ~Clock() = default;
auto update() -> void override;
auto doAction(const std::string& name) -> void override;
auto doAction(const std::string&) -> void override;

private:
util::SleeperThread thread_;
std::locale locale_;
std::vector<const date::time_zone*> time_zones_;
int current_time_zone_idx_;
bool is_calendar_in_tooltip_;
bool is_timezoned_list_in_tooltip_;

auto first_day_of_week() -> date::weekday;
const date::time_zone* current_timezone();
auto timezones_text(std::chrono::system_clock::time_point now) -> std::string;

/*Calendar properties*/
WeeksSide cldWPos_{WeeksSide::HIDDEN};
const std::locale locale_;
// tooltip
const std::string tlpFmt_;
std::string tlpText_{""}; // tooltip text to print
// Calendar
const bool cldInTooltip_; // calendar in tooltip
/*
0 - calendar.format.months
1 - calendar.format.weekdays
@@ -47,33 +35,43 @@ class Clock final : public ALabel {
5 - tooltip-format
*/
std::map<int, std::string const> fmtMap_;
uint cldMonCols_{3}; // calendar count month columns
int cldWnLen_{3}; // calendar week number length
const int cldMonColLen_{20}; // calendar month column length
WS cldWPos_{WS::HIDDEN}; // calendar week side to print
months cldCurrShift_{0}; // calendar months shift
year_month_day cldYearShift_; // calendar Year mode. Cached ymd
std::string cldYearCached_; // calendar Year mode. Cached calendar
year_month cldMonShift_; // calendar Month mode. Cached ym
std::string cldMonCached_; // calendar Month mode. Cached calendar
day cldBaseDay_{0}; // calendar Cached day. Is used when today is changing(midnight)
std::string cldText_{""}; // calendar text to print
CldMode cldMode_{CldMode::MONTH};
uint cldMonCols_{3}; // Count of the month in the row
int cldMonColLen_{20}; // Length of the month column
int cldWnLen_{3}; // Length of the week number
date::year_month_day cldYearShift_;
date::year_month cldMonShift_;
date::months cldCurrShift_{0};
date::months cldShift_{0};
std::string cldYearCached_{};
std::string cldMonCached_{};
date::day cldBaseDay_{0};
/*Calendar functions*/
auto get_calendar(const date::year_month_day& today, const date::year_month_day& ymd,
const date::time_zone* tz) -> const std::string;
/*Clock actions*/
auto get_calendar(const year_month_day& today, const year_month_day& ymd, const time_zone* tz)
-> const std::string;

// time zoned time in tooltip
const bool tzInTooltip_; // if need to print time zones text
std::vector<const time_zone*> tzList_; // time zones list
int tzCurrIdx_; // current time zone index for tzList_
std::string tzText_{""}; // time zones text to print
util::SleeperThread thread_;

auto getTZtext(sys_seconds now) -> std::string;
auto first_day_of_week() -> weekday;
// Module actions
void cldModeSwitch();
void cldShift_up();
void cldShift_down();
void tz_up();
void tz_down();

// ModuleActionMap
// Module Action Map
static inline std::map<const std::string, void (waybar::modules::Clock::*const)()> actionMap_{
{"mode", &waybar::modules::Clock::cldModeSwitch},
{"shift_up", &waybar::modules::Clock::cldShift_up},
{"shift_down", &waybar::modules::Clock::cldShift_down},
{"tz_up", &waybar::modules::Clock::tz_up},
{"tz_down", &waybar::modules::Clock::tz_down}};
};

} // namespace waybar::modules
45 changes: 33 additions & 12 deletions include/util/date.hpp
Original file line number Diff line number Diff line change
@@ -1,34 +1,52 @@
#pragma once

#include <fmt/format.h>
#include <chrono>

#if HAVE_CHRONO_TIMEZONES
#include <chrono>
#include <format>
#else
#include <date/tz.h>
#include <fmt/format.h>

/* Compatibility layer for <date/tz.h> on top of C++20 <chrono> */
namespace date {
#include <regex>
#endif

// Date
namespace date {
#if HAVE_CHRONO_TIMEZONES
using namespace std::chrono;
using namespace std;
#else

namespace literals {
using std::chrono::last;
}
using system_clock = std::chrono::system_clock;
using seconds = std::chrono::seconds;

inline auto format(const std::string& spec, const auto& ztime) {
return spec.empty() ? "" : std::vformat("{:L" + spec + "}", std::make_format_args(ztime));
template <typename T>
inline auto format(const char* spec, const T& arg) {
return date::format(std::regex_replace(spec, std::regex("\\{:L|\\}"), ""), arg);
}

inline auto format(const std::locale& loc, const std::string& spec, const auto& ztime) {
return spec.empty() ? "" : std::vformat(loc, "{:L" + spec + "}", std::make_format_args(ztime));
template <typename T>
inline auto format(const std::locale& loc, const char* spec, const T& arg) {
return date::format(loc, std::regex_replace(spec, std::regex("\\{:L|\\}"), ""), arg);
}

constexpr decltype(auto) operator""d(unsigned long long d) noexcept {
return date::operator""_d(d); // very verbose, but it works
}
#endif
} // namespace date

// Format
namespace waybar::util::date::format {
#if HAVE_CHRONO_TIMEZONES
using namespace std;
#else
#include <date/tz.h>
using namespace fmt;
#endif
} // namespace waybar::util::date::format

#if not HAVE_CHRONO_TIMEZONES
template <typename Duration, typename TimeZonePtr>
struct fmt::formatter<date::zoned_time<Duration, TimeZonePtr>> {
std::string_view specs;
@@ -58,3 +76,6 @@ struct fmt::formatter<date::zoned_time<Duration, TimeZonePtr>> {
return fmt::format_to(ctx.out(), "{}", date::format(fmt::to_string(specs), ztime));
}
};
#endif

using namespace date;
4 changes: 2 additions & 2 deletions man/waybar-clock.5.scd
Original file line number Diff line number Diff line change
@@ -85,7 +85,7 @@ $XDG_CONFIG_HOME/waybar/config ++
:[ same as format
:[ Tooltip on hover

View all valid format options in *strftime(3)* or have a look <https://fmt.dev/latest/syntax.html#chrono-specs>
View all valid format options in *strftime(3)* or have a look https://en.cppreference.com/w/cpp/chrono/duration/formatter

2. Addressed by *clock: calendar*
[- *Option*
@@ -156,7 +156,7 @@ View all valid format options in *strftime(3)* or have a look <https://fmt.dev/l
# FORMAT REPLACEMENTS

- *{calendar}*: Current month calendar
- *{timezoned_time_list}*: List of time in the rest timezones, if more than one timezone is set in the config
- *{tz_list}*: List of time in the rest timezones, if more than one timezone is set in the config

# EXAMPLES

17 changes: 16 additions & 1 deletion meson.build
Original file line number Diff line number Diff line change
@@ -126,7 +126,22 @@ gtk_layer_shell = dependency('gtk-layer-shell-0',
systemd = dependency('systemd', required: get_option('systemd'))

cpp_lib_chrono = compiler.compute_int('__cpp_lib_chrono', prefix : '#include <chrono>')
have_chrono_timezones = cpp_lib_chrono >= 201907
have_chrono_timezones = cpp_lib_chrono >= 201611

if have_chrono_timezones
code = '''
#include <chrono>
using namespace std::chrono;
int main(int argc, char** argv) {
const time_zone* tz;
return 0;
}
'''
if not compiler.links(code)
have_chrono_timezones = false
endif
endif

if have_chrono_timezones
tz_dep = declare_dependency()
else
5 changes: 2 additions & 3 deletions src/AModule.cpp
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ AModule::AModule(const Json::Value& config, const std::string& name, const std::
bool enable_click, bool enable_scroll)
: name_(std::move(name)),
config_(std::move(config)),
isTooltip{config_["tooltip"].isBool() ? config_["tooltip"].asBool() : true},
distance_scrolled_y_(0.0),
distance_scrolled_x_(0.0) {
// Configure module action Map
@@ -189,9 +190,7 @@ bool AModule::handleScroll(GdkEventScroll* e) {
return true;
}

bool AModule::tooltipEnabled() {
return config_["tooltip"].isBool() ? config_["tooltip"].asBool() : true;
}
bool AModule::tooltipEnabled() { return isTooltip; }

AModule::operator Gtk::Widget&() { return event_box_; }

484 changes: 215 additions & 269 deletions src/modules/clock.cpp

Large diffs are not rendered by default.

98 changes: 62 additions & 36 deletions test/date.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#include "util/date.hpp"

#include <chrono>
#include <ctime>
#include <iomanip>
#include <sstream>
@@ -20,13 +19,13 @@
#endif

using namespace std::literals::chrono_literals;

namespace fmt_lib = waybar::util::date::format;
/*
* Check that the date/time formatter with locale and timezone support is working as expected.
*/

const date::zoned_time<std::chrono::seconds> TEST_TIME = date::zoned_time{
"UTC", date::local_days{date::Monday[1] / date::January / 2022} + 13h + 4min + 5s};
const zoned_time<std::chrono::seconds> TEST_TIME{
"UTC", local_days{Monday[1] / January / 2022} + 13h + 4min + 5s};

/*
* Check if the date formatted with LC_TIME=en_US is within expectations.
@@ -52,10 +51,11 @@ static const bool LC_TIME_is_sane = []() {
TEST_CASE("Format UTC time", "[clock][util]") {
const auto loc = std::locale("C");
const auto tm = TEST_TIME;

CHECK(fmt::format(loc, "{}", tm).empty()); // no format specified
CHECK(fmt::format(loc, "{:%c %Z}", tm) == "Mon Jan 3 13:04:05 2022 UTC");
CHECK(fmt::format(loc, "{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405");
#if not HAVE_CHRONO_TIMEZONES
CHECK(fmt_lib::format(loc, "{}", tm).empty()); // no format specified
#endif
CHECK(fmt_lib::format(loc, "{:%c %Z}", tm) == "Mon Jan 3 13:04:05 2022 UTC");
CHECK(fmt_lib::format(loc, "{:%Y%m%d%H%M%S}", tm) == "20220103130405");

if (!LC_TIME_is_sane) {
SKIP("Locale support check failed, skip tests");
@@ -66,11 +66,15 @@ TEST_CASE("Format UTC time", "[clock][util]") {
try {
const auto loc = std::locale("en_US.UTF-8");

CHECK(fmt::format(loc, "{}", tm).empty()); // no format specified
CHECK_THAT(fmt::format(loc, "{:%c}", tm), // HowardHinnant/date#704
#if not HAVE_CHRONO_TIMEZONES
CHECK(fmt_lib::format(loc, "{}", tm).empty()); // no format specified
CHECK_THAT(fmt_lib::format(loc, "{:%c}", tm), // HowardHinnant/date#704
Catch::Matchers::StartsWith("Mon 03 Jan 2022 01:04:05 PM"));
CHECK(fmt::format(loc, "{:%x %X}", tm) == "01/03/2022 01:04:05 PM");
CHECK(fmt::format(loc, "{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405");
CHECK(fmt_lib::format(loc, "{:%x %X}", tm) == "01/03/2022 01:04:05 PM");
#else
CHECK(fmt_lib::format(loc, "{:%F %r}", tm) == "2022-01-03 01:04:05 PM");
#endif
CHECK(fmt_lib::format(loc, "{:%Y%m%d%H%M%S}", tm) == "20220103130405");
} catch (const std::runtime_error &) {
WARN("Locale en_US not found, skip tests");
}
@@ -79,11 +83,15 @@ TEST_CASE("Format UTC time", "[clock][util]") {
try {
const auto loc = std::locale("en_GB.UTF-8");

CHECK(fmt::format(loc, "{}", tm).empty()); // no format specified
CHECK_THAT(fmt::format(loc, "{:%c}", tm), // HowardHinnant/date#704
#if not HAVE_CHRONO_TIMEZONES
CHECK(fmt_lib::format(loc, "{}", tm).empty()); // no format specified
CHECK_THAT(fmt_lib::format(loc, "{:%c}", tm), // HowardHinnant/date#704
Catch::Matchers::StartsWith("Mon 03 Jan 2022 13:04:05"));
CHECK(fmt::format(loc, "{:%x %X}", tm) == "03/01/22 13:04:05");
CHECK(fmt::format(loc, "{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405");
CHECK(fmt_lib::format(loc, "{:%x %X}", tm) == "03/01/22 13:04:05");
#else
CHECK(fmt_lib::format(loc, "{:%F %T}", tm) == "2022-01-03 13:04:05");
#endif
CHECK(fmt_lib::format(loc, "{:%Y%m%d%H%M%S}", tm) == "20220103130405");
} catch (const std::runtime_error &) {
WARN("Locale en_GB not found, skip tests");
}
@@ -92,11 +100,15 @@ TEST_CASE("Format UTC time", "[clock][util]") {
try {
const auto loc = std::locale::global(std::locale("en_US.UTF-8"));

CHECK(fmt::format("{}", tm).empty()); // no format specified
CHECK_THAT(fmt::format("{:%c}", tm), // HowardHinnant/date#704
#if not HAVE_CHRONO_TIMEZONES
CHECK(fmt_lib::format("{}", tm).empty()); // no format specified
CHECK_THAT(fmt_lib::format("{:%c}", tm), // HowardHinnant/date#704
Catch::Matchers::StartsWith("Mon 03 Jan 2022 01:04:05 PM"));
CHECK(fmt::format("{:%x %X}", tm) == "01/03/2022 01:04:05 PM");
CHECK(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405");
CHECK(fmt_lib::format("{:%x %X}", tm) == "01/03/2022 01:04:05 PM");
#else
CHECK(fmt_lib::format("{:%F %r}", tm) == "2022-01-03 01:04:05 PM");
#endif
CHECK(fmt_lib::format("{:%Y%m%d%H%M%S}", tm) == "20220103130405");

std::locale::global(loc);
} catch (const std::runtime_error &) {
@@ -107,11 +119,13 @@ TEST_CASE("Format UTC time", "[clock][util]") {

TEST_CASE("Format zoned time", "[clock][util]") {
const auto loc = std::locale("C");
const auto tm = date::zoned_time{"America/New_York", TEST_TIME};
const auto tm = zoned_time{"America/New_York", TEST_TIME};

CHECK(fmt::format(loc, "{}", tm).empty()); // no format specified
CHECK(fmt::format(loc, "{:%c %Z}", tm) == "Mon Jan 3 08:04:05 2022 EST");
CHECK(fmt::format(loc, "{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405");
#if not HAVE_CHRONO_TIMEZONES
CHECK(fmt_lib::format(loc, "{}", tm).empty()); // no format specified
#endif
CHECK(fmt_lib::format(loc, "{:%c %Z}", tm) == "Mon Jan 3 08:04:05 2022 EST");
CHECK(fmt_lib::format(loc, "{:%Y%m%d%H%M%S}", tm) == "20220103080405");

if (!LC_TIME_is_sane) {
SKIP("Locale support check failed, skip tests");
@@ -122,11 +136,15 @@ TEST_CASE("Format zoned time", "[clock][util]") {
try {
const auto loc = std::locale("en_US.UTF-8");

CHECK(fmt::format(loc, "{}", tm).empty()); // no format specified
CHECK_THAT(fmt::format(loc, "{:%c}", tm), // HowardHinnant/date#704
#if not HAVE_CHRONO_TIMEZONES
CHECK(fmt_lib::format(loc, "{}", tm).empty()); // no format specified
CHECK_THAT(fmt_lib::format(loc, "{:%c}", tm), // HowardHinnant/date#704
Catch::Matchers::StartsWith("Mon 03 Jan 2022 08:04:05 AM"));
CHECK(fmt::format(loc, "{:%x %X}", tm) == "01/03/2022 08:04:05 AM");
CHECK(fmt::format(loc, "{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405");
CHECK(fmt_lib::format(loc, "{:%x %X}", tm) == "01/03/2022 08:04:05 AM");
#else
CHECK(fmt_lib::format(loc, "{:%F %r}", tm) == "2022-01-03 08:04:05 AM");
#endif
CHECK(fmt_lib::format(loc, "{:%Y%m%d%H%M%S}", tm) == "20220103080405");
} catch (const std::runtime_error &) {
WARN("Locale en_US not found, skip tests");
}
@@ -135,11 +153,15 @@ TEST_CASE("Format zoned time", "[clock][util]") {
try {
const auto loc = std::locale("en_GB.UTF-8");

CHECK(fmt::format(loc, "{}", tm).empty()); // no format specified
CHECK_THAT(fmt::format(loc, "{:%c}", tm), // HowardHinnant/date#704
#if not HAVE_CHRONO_TIMEZONES
CHECK(fmt_lib::format(loc, "{}", tm).empty()); // no format specified
CHECK_THAT(fmt_lib::format(loc, "{:%c}", tm), // HowardHinnant/date#704
Catch::Matchers::StartsWith("Mon 03 Jan 2022 08:04:05"));
CHECK(fmt::format(loc, "{:%x %X}", tm) == "03/01/22 08:04:05");
CHECK(fmt::format(loc, "{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405");
CHECK(fmt_lib::format(loc, "{:%x %X}", tm) == "03/01/22 08:04:05");
#else
CHECK(fmt_lib::format(loc, "{:%F %T}", tm) == "2022-01-03 08:04:05");
#endif
CHECK(fmt_lib::format(loc, "{:%Y%m%d%H%M%S}", tm) == "20220103080405");
} catch (const std::runtime_error &) {
WARN("Locale en_GB not found, skip tests");
}
@@ -148,11 +170,15 @@ TEST_CASE("Format zoned time", "[clock][util]") {
try {
const auto loc = std::locale::global(std::locale("en_US.UTF-8"));

CHECK(fmt::format("{}", tm).empty()); // no format specified
CHECK_THAT(fmt::format("{:%c}", tm), // HowardHinnant/date#704
#if not HAVE_CHRONO_TIMEZONES
CHECK(fmt_lib::format("{}", tm).empty()); // no format specified
CHECK_THAT(fmt_lib::format("{:%c}", tm), // HowardHinnant/date#704
Catch::Matchers::StartsWith("Mon 03 Jan 2022 08:04:05 AM"));
CHECK(fmt::format("{:%x %X}", tm) == "01/03/2022 08:04:05 AM");
CHECK(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405");
CHECK(fmt_lib::format("{:%x %X}", tm) == "01/03/2022 08:04:05 AM");
#else
CHECK(fmt_lib::format("{:%F %r}", tm) == "2022-01-03 08:04:05 AM");
#endif
CHECK(fmt_lib::format("{:%Y%m%d%H%M%S}", tm) == "20220103080405");

std::locale::global(loc);
} catch (const std::runtime_error &) {

0 comments on commit a51dd8f

Please sign in to comment.